Skip to content

Commit b0ff752

Browse files
authored
Merge pull request #718 from 0xMiden/otrho/fix-mem-store_sw
Fix a bug in `mem::store_sw` intrinsic which used incorrect masks in unaligned writes.
2 parents 04c86db + 160f599 commit b0ff752

File tree

2 files changed

+239
-8
lines changed
  • codegen/masm/intrinsics
  • tests/integration/src/codegen/intrinsics

2 files changed

+239
-8
lines changed

codegen/masm/intrinsics/mem.masm

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -293,17 +293,19 @@ end
293293
# Returns three 32-bit chunks [chunk_lo, chunk_mid, chunk_hi]
294294
export.offset_dw # [value_hi, value_lo, offset]
295295
dup.0
296-
dup.3 u32shr # [chunk_hi, value_hi, value_lo, offset]
296+
dup.3 u32shl # [chunk_hi, value_hi, value_lo, offset]
297+
297298
movdn.3 # [value_hi, value_lo, offset, chunk_hi]
298299
push.32 dup.3 u32wrapping_sub # [32 - offset, value_hi, value_lo, offset, chunk_hi]
299-
u32shl # [ chunk_mid_hi, value_lo, offset, chunk_hi]
300+
u32shr # [ chunk_mid_hi, value_lo, offset, chunk_hi]
300301
dup.1 # [ value_lo, chunk_mid_hi, value_lo, offset, chunk_hi]
301302
dup.3 # [ offset, value_lo, chunk_mid_hi, value_lo, offset, chunk_hi]
302-
u32shr # [ chunk_mid_lo, chunk_mid_hi, value_lo, offset, chunk_hi]
303+
u32shl # [ chunk_mid_lo, chunk_mid_hi, value_lo, offset, chunk_hi]
303304
u32or # [ chunk_mid, value_lo, offset, chunk_hi]
305+
304306
movdn.2 # [ value_lo, offset, chunk_mid, chunk_hi]
305307
push.32 movup.2 u32wrapping_sub # [32 - offset, value_lo, offset, chunk_mid, chunk_hi]
306-
u32shl # [ chunk_lo, chunk_mid, chunk_hi]
308+
u32shr # [ chunk_lo, chunk_mid, chunk_hi]
307309
end
308310

309311
# Load a machine double-word (64-bit value, in two 32-bit chunks) to the operand stack
@@ -400,15 +402,15 @@ export.store_sw # [addr, offset, value]
400402
push.32 dup.4 sub # [32 - bit_offset, e0, e1, addr, bit_offset, value]
401403

402404
# compute the masks
403-
push.4294967295 dup.1 u32shl # [mask_hi, rshift, e0, e1, addr, bit_offset, value]
404-
dup.0 u32not # [mask_lo, mask_hi, rshift, e0, e1, addr, bit_offset, value]
405+
push.4294967295 dup.1 u32shr # [mask_lo, rshift, e0, e1, addr, bit_offset, value]
406+
dup.0 u32not # [mask_hi, mask_lo, rshift, e0, e1, addr, bit_offset, value]
405407

406408
# manipulate the bits of the two target elements, such that the 32-bit word we're storing
407409
# is placed at the correct offset from the start of the memory cell when viewing the cell
408410
# as a set of 4 32-bit chunks
409411
#
410412
# First, mask out the bits we're going to overwrite in the two elements we loaded
411-
movup.4 u32and # [e1_masked, mask_hi, rshift, e0, addr, bit_offset, value]
413+
movup.4 u32and # [e1_masked, mask_lo, rshift, e0, addr, bit_offset, value]
412414
movup.3 movup.2 u32and # [e0_masked, e1_masked, rshift, addr, bit_offset, value]
413415

414416
# now, we need to shift/mask/split the 32-bit value into two elements, then
@@ -454,6 +456,7 @@ export.store_dw # [addr, offset, value_hi, value_lo]
454456
#
455457
# convert offset from bytes to bits
456458
swap.1 push.8 u32wrapping_mul swap.1 # [addr, bit_offset, value_hi, value_lo]
459+
457460
movdn.3 # [bit_offset, value_hi, value_lo, addr]
458461
dup.0 movdn.4 # [bit_offset, value_hi, value_lo, addr, bit_offset]
459462
movdn.2 # [value_hi, value_lo, bit_offset, addr, bit_offset]
@@ -463,7 +466,7 @@ export.store_dw # [addr, offset, value_hi, value_lo]
463466
push.32 movup.5 sub # [32 - bit_offset, lo, mid, hi, addr]
464467

465468
# compute the masks
466-
push.4294967295 swap.1 u32shl # [mask_hi, lo, mid, hi, addr]
469+
push.4294967295 swap.1 u32shr # [mask_hi, lo, mid, hi, addr]
467470
dup.0 u32not # [mask_lo, mask_hi, lo, mid, hi, addr]
468471

469472
# combine the bits of the lo and hi chunks with their corresponding elements, so that we

tests/integration/src/codegen/intrinsics/mem.rs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,3 +731,231 @@ fn store_u8() {
731731
_ => panic!("Unexpected test result: {res:?}"),
732732
}
733733
}
734+
735+
#[test]
736+
fn store_unaligned_u32() {
737+
// Use the start of the 17th page (1 page after the 16 pages reserved for the Rust stack)
738+
let write_to = 17 * 2u32.pow(16);
739+
let write_val = 0xddccbbaa_u32; // Little-endian bytes will be [AA BB CC DD].
740+
741+
// Generate a `test` module with `main` function that stores to a u32 offset.
742+
let signature = Signature::new(
743+
[AbiParam::new(Type::U32)],
744+
[AbiParam::new(Type::U32)], // Return u32 to satisfy test infrastructure
745+
);
746+
747+
// Compile once outside the test loop
748+
let (package, context) = compile_test_module(signature, |builder| {
749+
let block = builder.current_block();
750+
let idx_val = block.borrow().arguments()[0] as ValueRef;
751+
752+
// Set base pointer, add argument offset to it.
753+
let base_addr = builder.u32(write_to, SourceSpan::default());
754+
let write_addr = builder.add(base_addr, idx_val, SourceSpan::default()).unwrap();
755+
let ptr = builder
756+
.inttoptr(write_addr, Type::from(PointerType::new(Type::U32)), SourceSpan::default())
757+
.unwrap();
758+
759+
// Store test value to pointer.
760+
let write_val = builder.u32(write_val, SourceSpan::default());
761+
builder.store(ptr, write_val, SourceSpan::default()).unwrap();
762+
763+
// Return a constant to satisfy test infrastructure
764+
let result = builder.u32(1, SourceSpan::default());
765+
builder.ret(Some(result), SourceSpan::default()).unwrap();
766+
});
767+
768+
let run_test = |offs: u32, expected0: u32, expected1: u32| {
769+
// Initialise memory with some known bytes.
770+
let initializers = [Initializer::MemoryBytes {
771+
addr: write_to,
772+
bytes: &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
773+
}];
774+
775+
let output = eval_package::<u32, _, _>(
776+
&package,
777+
initializers,
778+
&[Felt::new(offs as u64)],
779+
context.session(),
780+
|trace| {
781+
// Get the overwritten words.
782+
let word0 = trace.read_from_rust_memory::<u32>(write_to).unwrap();
783+
let word1 = trace.read_from_rust_memory::<u32>(write_to + 4).unwrap();
784+
785+
eprintln!("word0: 0x{word0:0>8x}");
786+
eprintln!("word1: 0x{word1:0>8x}");
787+
788+
assert_eq!(
789+
word0, expected0,
790+
"expected 1st overwritten word to be {expected0}, got {word0}, with offset \
791+
{offs}"
792+
);
793+
794+
assert_eq!(
795+
word1, expected1,
796+
"expected 2nd overwritten word to be {expected1}, got {word1}, with offset \
797+
{offs}"
798+
);
799+
800+
Ok(())
801+
},
802+
)
803+
.unwrap();
804+
805+
assert_eq!(output, 1);
806+
};
807+
808+
// Overwrite 11 22 33 44 55 66 77 88 with bytes aa bb cc dd at offset 1:
809+
// Expect 11 aa bb cc | dd 66 77 88
810+
// or 0xccbbaa11 and 0x887766dd.
811+
run_test(1, 0xccbbaa11, 0x887766dd);
812+
813+
// Overwrite 11 22 33 44 55 66 77 88 with bytes aa bb cc dd at offset 2:
814+
// Expect 11 22 aa bb | cc dd 77 88
815+
// or 0xbbaa2211 and 0x8877ddcc.
816+
run_test(2, 0xbbaa2211, 0x8877ddcc);
817+
818+
// Overwrite 11 22 33 44 55 66 77 88 with bytes aa bb cc dd at offset 3:
819+
// Expect 11 22 33 aa | bb cc dd 88
820+
// or 0xaa332211 and 0x88ddccbb.
821+
run_test(3, 0xaa332211, 0x88ddccbb);
822+
}
823+
824+
#[test]
825+
fn store_unaligned_u64() {
826+
// Use the start of the 17th page (1 page after the 16 pages reserved for the Rust stack)
827+
let write_to = 17 * 2u32.pow(16);
828+
829+
// STORE_DW writes the high 32bit word to address and low 32bit word to address+1.
830+
// So a .store() of 0xddccbbaa_cdabffee writes 0xddccbbaa to addr and 0xcdabffee to addr+1.
831+
// Which in turn will be little-endian bytes [ AA BB CC DD EE FF AB CD ] at addr.
832+
let write_val = 0xddccbbaa_cdabffee_u64;
833+
834+
// Generate a `test` module with `main` function that stores to a u32 offset.
835+
let signature = Signature::new(
836+
[AbiParam::new(Type::U32)],
837+
[AbiParam::new(Type::U32)], // Return u32 to satisfy test infrastructure
838+
);
839+
840+
// Compile once outside the test loop
841+
let (package, context) = compile_test_module(signature, |builder| {
842+
let block = builder.current_block();
843+
let idx_val = block.borrow().arguments()[0] as ValueRef;
844+
845+
// Set base pointer, add argument offset to it.
846+
let base_addr = builder.u32(write_to, SourceSpan::default());
847+
let write_addr = builder.add(base_addr, idx_val, SourceSpan::default()).unwrap();
848+
let ptr = builder
849+
.inttoptr(write_addr, Type::from(PointerType::new(Type::U64)), SourceSpan::default())
850+
.unwrap();
851+
852+
// Store test value to pointer.
853+
let write_val = builder.u64(write_val, SourceSpan::default());
854+
builder.store(ptr, write_val, SourceSpan::default()).unwrap();
855+
856+
// Return a constant to satisfy test infrastructure
857+
let result = builder.u32(1, SourceSpan::default());
858+
builder.ret(Some(result), SourceSpan::default()).unwrap();
859+
});
860+
861+
let run_test = |offs: u32, expected0: u32, expected1: u32, expected2: u32, expected3: u32| {
862+
// Initialise memory with some known bytes.
863+
let initializers = [Initializer::MemoryBytes {
864+
addr: write_to,
865+
bytes: &[
866+
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
867+
0x17, 0x18,
868+
],
869+
}];
870+
871+
let output = eval_package::<u32, _, _>(
872+
&package,
873+
initializers,
874+
&[Felt::new(offs as u64)],
875+
context.session(),
876+
|trace| {
877+
// Get the overwritten words.
878+
let word0 = trace.read_from_rust_memory::<u32>(write_to).unwrap();
879+
let word1 = trace.read_from_rust_memory::<u32>(write_to + 4).unwrap();
880+
let word2 = trace.read_from_rust_memory::<u32>(write_to + 8).unwrap();
881+
let word3 = trace.read_from_rust_memory::<u32>(write_to + 12).unwrap();
882+
883+
eprintln!("word0: 0x{word0:0>8x}");
884+
eprintln!("word1: 0x{word1:0>8x}");
885+
eprintln!("word2: 0x{word2:0>8x}");
886+
eprintln!("word3: 0x{word3:0>8x}");
887+
888+
assert_eq!(
889+
word0, expected0,
890+
"expected 1st overwritten word to be {expected0}, got {word0}, with offset \
891+
{offs}"
892+
);
893+
894+
assert_eq!(
895+
word1, expected1,
896+
"expected 2nd overwritten word to be {expected1}, got {word1}, with offset \
897+
{offs}"
898+
);
899+
900+
assert_eq!(
901+
word2, expected2,
902+
"expected 3rd overwritten word to be {expected2}, got {word2}, with offset \
903+
{offs}"
904+
);
905+
906+
assert_eq!(
907+
word3, expected3,
908+
"expected 4th overwritten word to be {expected3}, got {word3}, with offset \
909+
{offs}"
910+
);
911+
912+
Ok(())
913+
},
914+
)
915+
.unwrap();
916+
917+
assert_eq!(output, 1);
918+
};
919+
920+
// Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18
921+
// with bytes aa bb cc dd ee ff ab cd at offset 1:
922+
// Expect 01 aa bb cc dd ee ff ab cd 12 13 14 15 16 17 18
923+
// or 0xccbbaa01, 0xabffeedd, 0x141312cd, 0x18171615
924+
run_test(1, 0xccbbaa01, 0xabffeedd, 0x141312cd, 0x18171615);
925+
926+
// Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18
927+
// with bytes aa bb cc dd ee ff ab cd at offset 2:
928+
// Expect 01 02 aa bb cc dd ee ff ab cd 13 14 15 16 17 18
929+
// or 0xbbaa0201, 0xffeeddcc, 0x1413cdab, 0x18171615
930+
run_test(2, 0xbbaa0201, 0xffeeddcc, 0x1413cdab, 0x18171615);
931+
932+
// Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18
933+
// with bytes aa bb cc dd ee ff ab cd at offset 3:
934+
// Expect 01 02 03 aa bb cc dd ee ff ab cd 14 15 16 17 18
935+
// or 0xaa030201, 0xeeddccbb, 0x14cdabff, 0x18171615
936+
run_test(3, 0xaa030201, 0xeeddccbb, 0x14cdabff, 0x18171615);
937+
938+
// Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18
939+
// with bytes aa bb cc dd ee ff ab cd at offset 4:
940+
// Expect 01 02 03 04 aa bb cc dd ee ff ab cd 15 16 17 18
941+
// or 0x04030201, 0xddccbbaa, 0xcdabffee, 0x18171615
942+
run_test(4, 0x04030201, 0xddccbbaa, 0xcdabffee, 0x18171615);
943+
944+
// Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18
945+
// with bytes aa bb cc dd ee ff ab cd at offset 5:
946+
// Expect 01 02 03 04 05 aa bb cc dd ee ff ab cd 16 17 18
947+
// or 0x04030201, 0xccbbaa05, 0xabffeedd, 0x181716cd
948+
run_test(5, 0x04030201, 0xccbbaa05, 0xabffeedd, 0x181716cd);
949+
950+
// Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18
951+
// with bytes aa bb cc dd ee ff ab cd at offset 6:
952+
// Expect 01 02 03 04 05 06 aa bb cc dd ee ff ab cd 17 18
953+
// or 0x04030201, 0xbbaa0605, 0xffeeddcc, 0x1817cdab
954+
run_test(6, 0x04030201, 0xbbaa0605, 0xffeeddcc, 0x1817cdab);
955+
956+
// Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18
957+
// with bytes aa bb cc dd ee ff ab cd at offset 7:
958+
// Expect 01 02 03 04 05 06 07 aa bb cc dd ee ff ab cd 18
959+
// or 0x04030201, 0xaa070605, 0xeeddccbb, 0x18cdabff
960+
run_test(7, 0x04030201, 0xaa070605, 0xeeddccbb, 0x18cdabff);
961+
}

0 commit comments

Comments
 (0)