Skip to content

Commit

Permalink
Merge pull request #1496 from 0xPolygonMiden/plafer-1457-emit-instr
Browse files Browse the repository at this point in the history
Introduce `Emit` instruction
  • Loading branch information
plafer authored Sep 18, 2024
2 parents f06923b + 0e2d4c4 commit 080597b
Show file tree
Hide file tree
Showing 24 changed files with 177 additions and 77 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Added `miden_core::utils::sync::racy_lock` module (#1463).
- Updated `miden_core::utils` to re-export `std::sync::LazyLock` and `racy_lock::RacyLock as LazyLock` for std and no_std environments, respectively (#1463).
- Made the undocumented behavior of the VM with regard to undefined behavior of u32 operations, stricter (#1480)
- Introduced the `Emit` instruction (#1496)

#### Fixes

Expand Down
9 changes: 5 additions & 4 deletions air/src/constraints/stack/op_flags/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ impl<E: FieldElement> OpFlags<E> {

// degree 6 flags do not use the first two bits (op_bits[0], op_bits[1])
degree4_op_flags[0] = not_2_not_3; // MRUPDATE
degree4_op_flags[1] = yes_2_not_3; // PUSH
degree4_op_flags[1] = yes_2_not_3; // (unused)
degree4_op_flags[2] = not_2_yes_3; // SYSCALL
degree4_op_flags[3] = yes_2_yes_3; // CALL

Expand All @@ -292,6 +292,7 @@ impl<E: FieldElement> OpFlags<E> {
+ degree5_op_flags[1] // MPVERIFY
+ degree5_op_flags[6] // SPAN
+ degree5_op_flags[7] // JOIN
+ degree5_op_flags[10] // EMIT
+ degree4_op_flags[6] // RESPAN
+ degree4_op_flags[7] // HALT
+ degree4_op_flags[3] // CALL
Expand Down Expand Up @@ -375,7 +376,7 @@ impl<E: FieldElement> OpFlags<E> {
+ degree7_op_flags[22]
+ degree7_op_flags[26];

right_shift_flags[0] = f011 + degree4_op_flags[1] + movupn_flag;
right_shift_flags[0] = f011 + degree5_op_flags[11] + movupn_flag; // degree 5: PUSH

right_shift_flags[1] = right_shift_flags[0] + degree6_op_flags[4]; // degree 6: U32SPLIT

Expand All @@ -395,7 +396,7 @@ impl<E: FieldElement> OpFlags<E> {
right_shift_flags[15] = right_shift_flags[8];

// Flag if the stack has been shifted to the right.
let right_shift = f011 + degree4_op_flags[1] + degree6_op_flags[4]; // PUSH; U32SPLIT
let right_shift = f011 + degree5_op_flags[11] + degree6_op_flags[4]; // PUSH; U32SPLIT

// Flag if the stack has been shifted to the left.
let left_shift =
Expand Down Expand Up @@ -907,7 +908,7 @@ impl<E: FieldElement> OpFlags<E> {
/// Operation Flag of PUSH operation.
#[inline(always)]
pub fn push(&self) -> E {
self.degree4_op_flags[get_op_index(Operation::Push(ONE).op_code())]
self.degree5_op_flags[get_op_index(Operation::Push(ONE).op_code())]
}

/// Operation Flag of CALL operation.
Expand Down
5 changes: 3 additions & 2 deletions air/src/constraints/stack/op_flags/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ fn degree_4_op_flags() {
fn composite_flags() {
// ------ no change 0 ---------------------------------------------------------------------

let op_no_change_0 = [Operation::MpVerify(0), Operation::Span, Operation::Halt];
let op_no_change_0 =
[Operation::MpVerify(0), Operation::Span, Operation::Halt, Operation::Emit(42)];
for op in op_no_change_0 {
// frame initialised with an op operation.
let frame = generate_evaluation_frame(op.op_code().into());
Expand All @@ -168,7 +169,7 @@ fn composite_flags() {
assert_eq!(op_flags.left_shift(), ZERO);
assert_eq!(op_flags.top_binary(), ZERO);

if op == Operation::MpVerify(0) {
if op == Operation::MpVerify(0) || op == Operation::Emit(42) {
assert_eq!(op_flags.control_flow(), ZERO);
} else if op == Operation::Span || op == Operation::Halt {
assert_eq!(op_flags.control_flow(), ONE);
Expand Down
4 changes: 2 additions & 2 deletions air/src/trace/main_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ impl MainTrace {
[b6, b5, b4] == [ZERO, ONE, ONE]||
// u32SPLIT 100_1000
([b6, b5, b4, b3, b2, b1, b0] == [ONE, ZERO, ZERO, ONE, ZERO, ZERO, ZERO]) ||
// PUSH i.e., 110_0100
([b6, b5, b4, b3, b2, b1, b0] == [ONE, ONE, ZERO, ZERO, ONE, ZERO, ZERO])
// PUSH i.e., 101_1011
([b6, b5, b4, b3, b2, b1, b0] == [ONE, ZERO, ONE, ONE, ZERO, ONE, ONE])
}

// STACK COLUMNS
Expand Down
2 changes: 1 addition & 1 deletion assembly/src/assembler/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ impl Assembler {

// ----- emit instruction -------------------------------------------------------------
Instruction::Emit(event_id) => {
block_builder.push_decorator(Decorator::Event(event_id.expect_value()))?;
block_builder.push_op(Operation::Emit(event_id.expect_value()));
},

// ----- trace instruction ------------------------------------------------------------
Expand Down
37 changes: 37 additions & 0 deletions assembly/src/assembler/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use alloc::vec::Vec;

use pretty_assertions::assert_eq;
use vm_core::{
assert_matches,
crypto::hash::RpoDigest,
mast::{MastForest, MastNode},
Program,
};
Expand Down Expand Up @@ -158,6 +161,40 @@ fn nested_blocks() -> Result<(), Report> {
Ok(())
}

/// Ensures that the arguments of `emit` do indeed modify the digest of a basic block
#[test]
fn emit_instruction_digest() {
let context = TestContext::new();

let program_source = r#"
proc.foo
emit.1
end
proc.bar
emit.2
end
begin
# specific impl irrelevant
exec.foo
exec.bar
end
"#;

let program = context.assemble(program_source).unwrap();

let procedure_digests: Vec<RpoDigest> = program.mast_forest().procedure_digests().collect();

// foo, bar and entrypoint
assert_eq!(3, procedure_digests.len());

// Ensure that foo, bar and entrypoint all have different digests
assert_ne!(procedure_digests[0], procedure_digests[1]);
assert_ne!(procedure_digests[0], procedure_digests[2]);
assert_ne!(procedure_digests[1], procedure_digests[2]);
}

/// Since `foo` and `bar` have the same body, we only expect them to be added once to the program.
#[test]
fn duplicate_procedure() {
Expand Down
2 changes: 1 addition & 1 deletion assembly/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ fn decorators_external() -> TestResult {
let expected = "\
begin
trace(0)
external.0x178d0a56b911f3eb23bbf18fb9f130130ba0c5d321e420610f64bb41790ca070
external.0xe776df8dc02329acc43a09fe8e510b44a87dfd876e375ad383891470ece4f6de
trace(1)
end";
let program = Assembler::new(context.source_manager())
Expand Down
10 changes: 5 additions & 5 deletions core/src/mast/node/basic_block_node/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,11 @@ fn operation_or_decorator_iterator() {

// Note: there are 2 decorators after the last instruction
let decorators = vec![
(0, Decorator::Event(0)), // ID: 0
(0, Decorator::Event(1)), // ID: 1
(3, Decorator::Event(2)), // ID: 2
(4, Decorator::Event(3)), // ID: 3
(4, Decorator::Event(4)), // ID: 4
(0, Decorator::Trace(0)), // ID: 0
(0, Decorator::Trace(1)), // ID: 1
(3, Decorator::Trace(2)), // ID: 2
(4, Decorator::Trace(3)), // ID: 3
(4, Decorator::Trace(4)), // ID: 4
];

let node =
Expand Down
9 changes: 1 addition & 8 deletions core/src/mast/serialization/decorator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,6 @@ impl DecoratorInfo {

Ok(Decorator::Debug(DebugOptions::LocalInterval(start, second, end)))
},
EncodedDecoratorVariant::Event => {
let value = data_reader.read_u32()?;

Ok(Decorator::Event(value))
},
EncodedDecoratorVariant::Trace => {
let value = data_reader.read_u32()?;

Expand Down Expand Up @@ -245,7 +240,6 @@ pub enum EncodedDecoratorVariant {
DebugOptionsMemAll,
DebugOptionsMemInterval,
DebugOptionsLocalInterval,
Event,
Trace,
}

Expand Down Expand Up @@ -298,7 +292,6 @@ impl From<&Decorator> for EncodedDecoratorVariant {
DebugOptions::MemInterval(..) => Self::DebugOptionsMemInterval,
DebugOptions::LocalInterval(..) => Self::DebugOptionsLocalInterval,
},
Decorator::Event(_) => Self::Event,
Decorator::Trace(_) => Self::Trace,
}
}
Expand Down Expand Up @@ -432,7 +425,7 @@ impl DecoratorDataBuilder {
},
DebugOptions::StackAll | DebugOptions::MemAll => None,
},
Decorator::Event(value) | Decorator::Trace(value) => {
Decorator::Trace(value) => {
self.decorator_data.extend(value.to_le_bytes());

Some(data_offset)
Expand Down
6 changes: 3 additions & 3 deletions core/src/mast/serialization/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@ fn confirm_operation_and_decorator_structure() {
Operation::MrUpdate => (),
Operation::FriE2F4 => (),
Operation::RCombBase => (),
Operation::Emit(_) => (),
};

match Decorator::Event(0) {
match Decorator::Trace(0) {
Decorator::Advice(advice) => match advice {
AdviceInjector::MerkleNodeMerge => (),
AdviceInjector::MerkleNodeToStack => (),
Expand Down Expand Up @@ -136,7 +137,6 @@ fn confirm_operation_and_decorator_structure() {
DebugOptions::MemInterval(..) => (),
DebugOptions::LocalInterval(..) => (),
},
Decorator::Event(_) => (),
Decorator::Trace(_) => (),
};
}
Expand Down Expand Up @@ -236,6 +236,7 @@ fn serialize_deserialize_all_nodes() {
Operation::MrUpdate,
Operation::FriE2F4,
Operation::RCombBase,
Operation::Emit(42),
];

let num_operations = operations.len();
Expand Down Expand Up @@ -288,7 +289,6 @@ fn serialize_deserialize_all_nodes() {
(15, Decorator::Debug(DebugOptions::MemAll)),
(15, Decorator::Debug(DebugOptions::MemInterval(0, 16))),
(17, Decorator::Debug(DebugOptions::LocalInterval(1, 2, 3))),
(num_operations, Decorator::Event(45)),
(num_operations, Decorator::Trace(55)),
];

Expand Down
4 changes: 0 additions & 4 deletions core/src/operations/decorators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ pub enum Decorator {
/// Prints out information about the state of the VM based on the specified options. This
/// decorator is executed only in debug mode.
Debug(DebugOptions),
/// Emits an event to the host.
Event(u32),
/// Emits a trace to the host.
Trace(u32),
}
Expand All @@ -60,7 +58,6 @@ impl Decorator {
Blake3_256::hash(&bytes_to_hash)
},
Self::Debug(debug) => Blake3_256::hash(debug.to_string().as_bytes()),
Self::Event(event) => Blake3_256::hash(&event.to_le_bytes()),
Self::Trace(trace) => Blake3_256::hash(&trace.to_le_bytes()),
}
}
Expand All @@ -80,7 +77,6 @@ impl fmt::Display for Decorator {
write!(f, "asmOp({}, {})", assembly_op.op(), assembly_op.num_cycles())
},
Self::Debug(options) => write!(f, "debug({options})"),
Self::Event(event_id) => write!(f, "event({})", event_id),
Self::Trace(trace_id) => write!(f, "trace({})", trace_id),
}
}
Expand Down
29 changes: 25 additions & 4 deletions core/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,11 @@ pub(super) mod opcode_constants {
pub const OPCODE_JOIN: u8 = 0b0101_0111;
pub const OPCODE_DYN: u8 = 0b0101_1000;
pub const OPCODE_RCOMBBASE: u8 = 0b0101_1001;
pub const OPCODE_EMIT: u8 = 0b0101_1010;
pub const OPCODE_PUSH: u8 = 0b0101_1011;

pub const OPCODE_MRUPDATE: u8 = 0b0110_0000;
pub const OPCODE_PUSH: u8 = 0b0110_0100;
/* unused: 0b0110_0100 */
pub const OPCODE_SYSCALL: u8 = 0b0110_1000;
pub const OPCODE_CALL: u8 = 0b0110_1100;
pub const OPCODE_END: u8 = 0b0111_0000;
Expand Down Expand Up @@ -156,6 +158,16 @@ pub enum Operation {
/// instruction.
Clk = OPCODE_CLK,

/// Emits an event id (`u32` value) to the host.
///
/// We interpret the event id as follows:
/// - 16 most significant bits identify the event source,
/// - 16 least significant bits identify the actual event.
///
/// Similar to Noop, this operation does not change the state of user stack. The immediate
/// value affects the program MAST root computation.
Emit(u32) = OPCODE_EMIT,

// ----- flow control operations -------------------------------------------------------------
/// Marks the beginning of a join block.
Join = OPCODE_JOIN,
Expand Down Expand Up @@ -570,8 +582,9 @@ impl Operation {

/// Returns an immediate value carried by this operation.
pub fn imm_value(&self) -> Option<Felt> {
match self {
Self::Push(imm) => Some(*imm),
match *self {
Self::Push(imm) => Some(imm),
Self::Emit(imm) => Some(imm.into()),
_ => None,
}
}
Expand Down Expand Up @@ -718,6 +731,8 @@ impl fmt::Display for Operation {
Self::MStream => write!(f, "mstream"),
Self::Pipe => write!(f, "pipe"),

Self::Emit(value) => write!(f, "emit({value})"),

// ----- cryptographic operations -----------------------------------------------------
Self::HPerm => write!(f, "hperm"),
Self::MpVerify(err_code) => write!(f, "mpverify({err_code})"),
Expand All @@ -737,9 +752,10 @@ impl Serializable for Operation {
Operation::Assert(err_code)
| Operation::MpVerify(err_code)
| Operation::U32assert2(err_code) => {
err_code.to_le_bytes().write_into(target);
err_code.write_into(target);
},
Operation::Push(value) => value.as_int().write_into(target),
Operation::Emit(value) => value.write_into(target),

// Note: we explicitly write out all the operations so that whenever we make a
// modification to the `Operation` enum, we get a compile error here. This
Expand Down Expand Up @@ -947,6 +963,11 @@ impl Deserializable for Operation {

Self::Push(value_felt)
},
OPCODE_EMIT => {
let value = source.read_u32()?;

Self::Emit(value)
},
OPCODE_SYSCALL => Self::SysCall,
OPCODE_CALL => Self::Call,
OPCODE_END => Self::End,
Expand Down
Loading

0 comments on commit 080597b

Please sign in to comment.