Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d4707c3
fix: byte-version of the `memcpy` (iteration guard)
greenhat Mar 12, 2026
f137a87
fix: handle unaligned `u16` memory windows
greenhat Mar 12, 2026
07c2968
fix: guard zero-count `memcpy` and `memset`
greenhat Mar 16, 2026
42f3861
test: use little-endian bytes in `u16` memory checks
greenhat Mar 16, 2026
6caac99
refactor: extract counted loop emission
greenhat Mar 16, 2026
e3588f9
fix: limit cross-element `u16` memory access
greenhat Mar 17, 2026
744b707
test: cover unaligned `u16` offsets and immediates
greenhat Mar 17, 2026
125fa5c
test: add aligned byte `memcpy` coverage
greenhat Mar 17, 2026
54b4f74
test: strengthen unaligned u16 emitter assertions
greenhat Mar 17, 2026
8c82689
test: cover signed unaligned i16 memory access
greenhat Mar 17, 2026
83b71fa
refactor: share split-element u16 branch emission
greenhat Mar 17, 2026
da32974
docs: complete memcpy semantics comment
greenhat Mar 17, 2026
35c1cd5
fix: assert word alignment in memcpy word fast paths
greenhat Mar 17, 2026
efbc276
Clarify 16-bit memcpy helper naming
greenhat Mar 18, 2026
9ca4499
Clean up memcpy word-copy fast path flow
greenhat Mar 18, 2026
6639266
Deduplicate unaligned 16-bit intrinsic tests
greenhat Mar 18, 2026
1826cee
Cover memcpy word-copy fast paths
greenhat Mar 18, 2026
5a786e8
Document cross-element 16-bit store window
greenhat Mar 18, 2026
c69fb7d
Restrict memcpy word fast paths to byte pointers
greenhat Mar 18, 2026
eac758a
Introduce raw MASM branch block builder
greenhat Mar 18, 2026
a0ef2e6
Rename aligned byte memcpy regression
greenhat Mar 18, 2026
9362cfe
Add signed 16-bit immediate path tests
greenhat Mar 18, 2026
a38a75b
Refactor u16 memory access lowering
greenhat Mar 18, 2026
7369401
Add constant-address u16 load coverage
greenhat Mar 18, 2026
93f42b7
Remove redundant u16 load tests
greenhat Mar 18, 2026
17b7e4a
chore: address PR 1004 review notes
greenhat Mar 25, 2026
2aa50e4
chore: add error messages to all emitted `Instruction::Assert*` ops
greenhat Mar 25, 2026
c803c42
fix: build after the rebase
greenhat Mar 27, 2026
4a529fd
fix: migrate the #1003 fix to the VM v0.22
greenhat Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 49 additions & 11 deletions codegen/masm/intrinsics/mem.masm
Original file line number Diff line number Diff line change
Expand Up @@ -167,26 +167,44 @@ pub proc load_sw # [addr, offset]
# load the element containing the data we want
mem_load
else # [addr, offset]
# convert the byte offset to a bit offset
swap.1 push.8 u32wrapping_mul swap.1 # [addr, bit_offset]
# the load crosses an element boundary
#
# 1. load the first element
dup.0 mem_load # [e0, addr, offset]
dup.0 mem_load # [e0, addr, bit_offset]
# 2. load the second element
swap.1 # [addr, e0, offset]
push.1 u32overflowing_add # [overflowed, addr + 1, e0, offset]
assertz mem_load # [e1, e0, offset]
# shift low bits
push.32 dup.3 # [offset, 32, e1, e0, offset]
u32overflowing_sub assertz # [32 - offset, e1, e0, offset]
u32shr # [lo, e0, offset]
# shift high bits left by the offset
swap.2 # [offset, e0, lo]
u32shl # [hi, lo]
swap.1 # [addr, e0, bit_offset]
push.1 u32overflowing_add # [overflowed, addr + 1, e0, bit_offset]
assertz mem_load # [e1, e0, bit_offset]
# Reconstruct the 32-bit window whose first byte begins at the original byte pointer.
# `e0` contributes the low part after shifting right, and `e1` contributes the carried
# high part after shifting left into the vacated bits.
swap.1 # [e0, e1, bit_offset]
dup.2 # [bit_offset, e0, e1, bit_offset]
u32shr # [lo, e1, bit_offset]
movup.2 # [bit_offset, lo, e1]
push.32 swap.1 # [bit_offset, 32, lo, e1]
u32overflowing_sub assertz # [32 - bit_offset, lo, e1]
movup.2 swap.1 # [32 - bit_offset, e1, lo]
u32shl # [hi, lo]
# combine the two halves
u32or # [result]
end
end

# Load a 16-bit integer from the given native pointer tuple.
#
# A native pointer tuple consists of an element address where the data begins, and a byte offset,
# which is the offset of the first byte, in the 32-bit representation of that element.
#
# Stack transition: [addr, offset] -> [value]
pub proc load_u16(addr: ptr<felt, addrspace(felt)>, offset: u8) -> u16
exec.load_sw
push.65535
u32and
end

# This handles emitting code that handles aligning an unaligned 64-bit value which is split across
# three elements.
#
Expand Down Expand Up @@ -436,6 +454,26 @@ pub proc store_sw # [addr, offset, value]
end
end

# Store a 16-bit integer to the given native pointer tuple.
#
# A native pointer tuple consists of an element address where the data begins, and a byte offset,
# which is the offset of the first byte, in the 32-bit representation of that element.
#
# Stack transition: [addr, offset, value] -> []
pub proc store_u16(addr: ptr<felt, addrspace(felt)>, offset: u8, value: u16)
# Load the current 32-bit window at the destination, keep its upper half, then overwrite the
# target two bytes before delegating the write-back to `store_sw`.
dup.1 dup.1 exec.load_sw # [window, addr, offset, value]
push.4294901760 # 0xffff0000
u32and # [masked_window, addr, offset, value]
movup.3 # [value, masked_window, addr, offset]
push.65535
u32and # [value16, masked_window, addr, offset]
u32or # [combined, addr, offset]
swap.2 swap.1 # [addr, offset, combined]
exec.store_sw
end

# Store two 32-bit words to the given native pointer tuple.
#
# A native pointer tuple consists of an element address where the data begins, and a byte offset,
Expand Down
12 changes: 9 additions & 3 deletions codegen/masm/src/emit/felt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ impl OpEmitter<'_> {
/// `[a, ..] => [a, ..]`
#[inline(always)]
pub fn assert_felt_is_zero(&mut self, span: SourceSpan) {
self.emit_all([masm::Instruction::Dup0, masm::Instruction::Assertz], span);
self.emit_all(
[
masm::Instruction::Dup0,
Self::assertz_with_message_inst("expected felt value to be zero", span),
],
span,
);
}

/// Convert a field element to i128 by zero-extension.
Expand Down Expand Up @@ -85,7 +91,7 @@ impl OpEmitter<'_> {
// Split into u32 limbs
masm::Instruction::U32Split,
// Assert most significant 32 bits are unused
masm::Instruction::Assertz,
Self::assertz_with_message_inst("felt value does not fit in 32 bits", span),
],
span,
);
Expand All @@ -105,7 +111,7 @@ impl OpEmitter<'_> {
// Split into u32 limbs
masm::Instruction::U32Split,
// Assert most significant 32 bits are unused
masm::Instruction::Assertz,
Self::assertz_with_message_inst("felt value does not fit in 32 bits", span),
],
span,
);
Expand Down
16 changes: 12 additions & 4 deletions codegen/masm/src/emit/int128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ impl OpEmitter<'_> {
//
// What remains on the stack at this point are the low 64-bits,
// which is also our result.
self.emit_n(2, masm::Instruction::Assertz, span);
self.emit_n(
2,
Self::assertz_with_message_inst("128-bit value does not fit in u64", span),
span,
);
}

/// Convert a 128-bit value to u32
Expand All @@ -95,7 +99,11 @@ impl OpEmitter<'_> {
//
// What remains on the stack at this point are the low 32-bits,
// which is also our result.
self.emit_n(3, masm::Instruction::Assertz, span);
self.emit_n(
3,
Self::assertz_with_message_inst("128-bit value does not fit in u32", span),
span,
);
}

/// Convert a unsigned 128-bit value to i64
Expand Down Expand Up @@ -139,7 +147,7 @@ impl OpEmitter<'_> {
[
// Assert that both 32-bit limbs of the most significant 64 bits match,
// consuming them in the process
masm::Instruction::AssertEq,
Self::assert_eq_with_message_inst("128-bit value does not fit in i64", span),
// At this point, the stack is: [is_signed, x1, x0]
//
// Select an expected value for the sign bit based on the is_signed flag
Expand All @@ -158,7 +166,7 @@ impl OpEmitter<'_> {
// any other combination will trap.
//
// [x1, x0]
masm::Instruction::AssertEq,
Self::assert_eq_with_message_inst("128-bit value does not fit in i64", span),
],
span,
);
Expand Down
32 changes: 25 additions & 7 deletions codegen/masm/src/emit/int32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ impl OpEmitter<'_> {
#[inline]
pub fn assert_signed_int32(&mut self, span: SourceSpan) {
self.is_signed_int32(span);
self.emit(masm::Instruction::Assert, span);
self.emit(Self::assert_with_message_inst("expected a signed i32 value", span), span);
}

/// Emits code to assert that a 32-bit value on the operand stack does not have the i32 sign bit
Expand All @@ -119,7 +119,7 @@ impl OpEmitter<'_> {
#[inline]
pub fn assert_unsigned_int32(&mut self, span: SourceSpan) {
self.is_signed_int32(span);
self.emit(masm::Instruction::Assertz, span);
self.emit(Self::assertz_with_message_inst("expected a non-negative i32 value", span), span);
}

/// Assert that the 32-bit value on the stack is a valid i32 value
Expand All @@ -131,7 +131,7 @@ impl OpEmitter<'_> {
// the value is <= i32::MIN, which is 1 more than i32::MAX.
self.push_i32(i32::MIN, span);
self.emit(masm::Instruction::U32Lte, span);
self.emit(masm::Instruction::Assert, span);
self.emit(Self::assert_with_message_inst("value does not fit in i32", span), span);
}

/// Emits code to assert that a 32-bit value on the operand stack is equal to the given constant
Expand All @@ -148,7 +148,10 @@ impl OpEmitter<'_> {
[
masm::Instruction::Dup0,
masm::Instruction::EqImm(Felt::new(value as u64).into()),
masm::Instruction::Assert,
Self::assert_with_message_inst(
format!("expected u32 value to equal {value}"),
span,
),
],
span,
);
Expand All @@ -164,7 +167,13 @@ impl OpEmitter<'_> {
/// `[expected, input, ..] => [input, ..]`
#[inline]
pub fn assert_eq_u32(&mut self, span: SourceSpan) {
self.emit_all([masm::Instruction::Dup1, masm::Instruction::AssertEq], span);
self.emit_all(
[
masm::Instruction::Dup1,
Self::assert_eq_with_message_inst("expected u32 values to be equal", span),
],
span,
);
}

/// Emits code to select a constant u32 value, using the `n`th value on the operand
Expand Down Expand Up @@ -244,7 +253,10 @@ impl OpEmitter<'_> {
// Apply the mask
masm::Instruction::U32And,
// Assert that the masked bits and the mask are equal
masm::Instruction::AssertEq,
Self::assert_eq_with_message_inst(
format!("value does not fit in signed {n}-bit range"),
span,
),
],
span,
);
Expand Down Expand Up @@ -293,7 +305,13 @@ impl OpEmitter<'_> {
self.emit_push(mask, span);
self.emit(masm::Instruction::U32And, span);
// Assert the masked value is all 0s
self.emit(masm::Instruction::Assertz, span);
self.emit(
Self::assertz_with_message_inst(
format!("value does not fit in unsigned {n}-bit range"),
span,
),
span,
);
}

/// Convert an i32/u32 value on the stack to an unsigned N-bit integer value
Expand Down
40 changes: 31 additions & 9 deletions codegen/masm/src/emit/int64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ impl OpEmitter<'_> {
// Assert that value is <= P, then unsplit the limbs to get a felt
self.push_u64(P, span);
self.lt_u64(span);
self.emit(masm::Instruction::Assert, span);
self.emit(Self::assert_with_message_inst("u64 value does not fit in felt", span), span);
// `u32unsplit` expects `[hi, lo]` on the stack; u64 values are represented as `[lo, hi]`.
self.emit(masm::Instruction::Swap1, span);
self.u32unsplit(span);
Expand All @@ -41,14 +41,27 @@ impl OpEmitter<'_> {
// Bring `hi` to the top of the stack and assert it is zero. This consumes `hi`,
// leaving only `lo` on the stack.
masm::Instruction::Swap1,
masm::Instruction::Assertz,
// Assert hi bits are zero
Self::assertz_with_message_inst(
format!("u64 value does not fit in unsigned {n}-bit range"),
span,
),
// Check that the remaining bits fit in range
masm::Instruction::Dup0,
],
span,
);
self.emit_push(Felt::new(2u64.pow(n) - 1), span);
self.emit_all([masm::Instruction::U32Lte, masm::Instruction::Assert], span);
self.emit_all(
[
masm::Instruction::U32Lte,
Self::assert_with_message_inst(
format!("u64 value does not fit in unsigned {n}-bit range"),
span,
),
],
span,
);
}

/// Convert an i64 value to a signed N-bit integer, where N <= 32
Expand All @@ -75,7 +88,10 @@ impl OpEmitter<'_> {
self.emit_all(
[
// [is_unsigned, x_lo]
masm::Instruction::AssertEq,
Self::assert_eq_with_message_inst(
format!("i64 value does not fit in signed {n}-bit range"),
span,
),
// [x_lo, is_unsigned, x_lo]
masm::Instruction::Dup1,
],
Expand Down Expand Up @@ -104,7 +120,10 @@ impl OpEmitter<'_> {
// [expected_sign_bits, sign_bits, x_lo]
masm::Instruction::CDrop,
// [x_lo]
masm::Instruction::AssertEq,
Self::assert_eq_with_message_inst(
format!("i64 value does not fit in signed {n}-bit range"),
span,
),
],
span,
);
Expand Down Expand Up @@ -220,7 +239,7 @@ impl OpEmitter<'_> {
// the value is <= i64::MIN, which is 1 more than i64::MAX.
self.push_i64(i64::MIN, span);
self.lte_u64(span);
self.emit(masm::Instruction::Assert, span);
self.emit(Self::assert_with_message_inst("value does not fit in i64", span), span);
}

/// Duplicate the i64/u64 value on top of the stack
Expand Down Expand Up @@ -428,7 +447,7 @@ impl OpEmitter<'_> {
match overflow {
Overflow::Checked => {
self.raw_exec("::miden::core::math::u64::overflowing_add", span);
self.emit(masm::Instruction::Assertz, span);
self.emit(Self::assertz_with_message_inst("u64 addition overflowed", span), span);
}
Overflow::Unchecked | Overflow::Wrapping => {
self.raw_exec("::miden::core::math::u64::wrapping_add", span);
Expand Down Expand Up @@ -493,7 +512,10 @@ impl OpEmitter<'_> {
match overflow {
Overflow::Checked => {
self.raw_exec("::miden::core::math::u64::overflowing_sub", span);
self.emit(masm::Instruction::Assertz, span);
self.emit(
Self::assertz_with_message_inst("u64 subtraction overflowed", span),
span,
);
}
Overflow::Unchecked | Overflow::Wrapping => {
self.raw_exec("::miden::core::math::u64::wrapping_sub", span);
Expand Down Expand Up @@ -575,7 +597,7 @@ impl OpEmitter<'_> {
masm::Instruction::Drop,
// Bring overflow back to the top and assert it is zero
masm::Instruction::MovUp2,
masm::Instruction::Assertz,
Self::assertz_with_message_inst("u64 multiplication overflowed", span),
],
span,
);
Expand Down
Loading
Loading