Skip to content

Commit

Permalink
Merge pull request #1078 from 0xPolygonMiden/grjte-asm-dynexec
Browse files Browse the repository at this point in the history
Add `dynexec` assembly instruction, tests, and docs
  • Loading branch information
bobbinth authored Oct 4, 2023
2 parents 3c6c540 + 8ff713f commit 48b0eb0
Show file tree
Hide file tree
Showing 14 changed files with 234 additions and 8 deletions.
2 changes: 2 additions & 0 deletions assembly/src/assembler/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ impl Assembler {
Instruction::CallMastRoot(root) => self.call_mast_root(root, ctx),
Instruction::CallImported(id) => self.call_imported(id, ctx),
Instruction::SysCall(id) => self.syscall(id, ctx),
Instruction::DynExec => self.dynexec(),
Instruction::DynCall => self.dyncall(),

// ----- debug decorators -------------------------------------------------------------
Instruction::Breakpoint => {
Expand Down
10 changes: 10 additions & 0 deletions assembly/src/assembler/instruction/procedures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,14 @@ impl Assembler {
// create a new SYSCALL block for the procedure call and return
Ok(Some(CodeBlock::new_syscall(proc.mast_root())))
}

pub(super) fn dynexec(&self) -> Result<Option<CodeBlock>, AssemblyError> {
// create a new DYN block for the dynamic code execution and return
Ok(Some(CodeBlock::new_dyn()))
}

pub(super) fn dyncall(&self) -> Result<Option<CodeBlock>, AssemblyError> {
// create a new CALL block whose target is DYN
Ok(Some(CodeBlock::new_dyncall()))
}
}
4 changes: 4 additions & 0 deletions assembly/src/ast/nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ pub enum Instruction {
CallMastRoot(RpoDigest),
CallImported(ProcedureId),
SysCall(ProcedureId),
DynExec,
DynCall,

// ----- debug decorators ---------------------------------------------------------------------
Breakpoint,
Expand Down Expand Up @@ -591,6 +593,8 @@ impl fmt::Display for Instruction {
}
Self::CallImported(proc_id) => write!(f, "call.{proc_id}"),
Self::SysCall(proc_id) => write!(f, "syscall.{proc_id}"),
Self::DynExec => write!(f, "dynexec"),
Self::DynCall => write!(f, "dyncall"),

// ----- debug decorators -------------------------------------------------------------
Self::Breakpoint => write!(f, "breakpoint"),
Expand Down
2 changes: 2 additions & 0 deletions assembly/src/ast/nodes/serde/deserialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ impl Deserializable for Instruction {
OpCode::CallMastRoot => Ok(Instruction::CallMastRoot(RpoDigest::read_from(source)?)),
OpCode::CallImported => Ok(Instruction::CallImported(ProcedureId::read_from(source)?)),
OpCode::SysCall => Ok(Instruction::SysCall(ProcedureId::read_from(source)?)),
OpCode::DynExec => Ok(Instruction::DynExec),
OpCode::DynCall => Ok(Instruction::DynCall),

// ----- debugging --------------------------------------------------------------------
OpCode::Debug => {
Expand Down
4 changes: 3 additions & 1 deletion assembly/src/ast/nodes/serde/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,11 @@ pub enum OpCode {
CallMastRoot = 244,
CallImported = 245,
SysCall = 246,
DynExec = 247,
DynCall = 248,

// ----- debugging ----------------------------------------------------------------------------
Debug = 247,
Debug = 249,

// ----- control flow -------------------------------------------------------------------------
IfElse = 253,
Expand Down
2 changes: 2 additions & 0 deletions assembly/src/ast/nodes/serde/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ impl Serializable for Instruction {
OpCode::SysCall.write_into(target);
imported.write_into(target)
}
Self::DynExec => OpCode::DynExec.write_into(target),
Self::DynCall => OpCode::DynCall.write_into(target),

// ----- debug decorators -------------------------------------------------------------
Self::Breakpoint => {
Expand Down
2 changes: 2 additions & 0 deletions assembly/src/ast/parsers/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ impl ParserContext<'_> {
"exec" => self.parse_exec(op),
"call" => self.parse_call(op),
"syscall" => self.parse_syscall(op),
"dynexec" => simple_instruction(op, DynExec),
"dyncall" => simple_instruction(op, DynCall),

// ----- constant statements ----------------------------------------------------------
"const" => Err(ParsingError::const_invalid_scope(op)),
Expand Down
22 changes: 22 additions & 0 deletions assembly/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,28 @@ fn program_with_exported_procedure() {
assert!(assembler.compile(source).is_err());
}

// PROGRAMS WITH DYNAMIC CODE BLOCKS
// ================================================================================================

#[test]
fn program_with_dynamic_code_execution() {
let assembler = super::Assembler::default();
let source = "begin dynexec end";
let program = assembler.compile(source).unwrap();
let expected = "begin dyn end";
assert_eq!(expected, format!("{program}"));
}

#[test]
fn program_with_dynamic_code_execution_in_new_context() {
let assembler = super::Assembler::default();
let source = "begin dyncall end";
let program = assembler.compile(source).unwrap();
let expected =
"begin call.0xc75c340ec6a69e708457544d38783abbb604d881b7dc62d00bfc2b10f52808e6 end";
assert_eq!(expected, format!("{program}"));
}

// MAST ROOT CALLS
// ================================================================================================

Expand Down
7 changes: 6 additions & 1 deletion core/src/program/blocks/dyn_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ impl Dyn {

/// Returns a hash of this code block.
pub fn hash(&self) -> Digest {
Self::dyn_hash()
}

/// Returns a hash of this code block.
pub fn dyn_hash() -> Digest {
DYN_CONSTANT
}
}
Expand All @@ -57,7 +62,7 @@ impl Default for Dyn {

impl fmt::Display for Dyn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Dyn")?;
write!(f, "dyn")?;

Ok(())
}
Expand Down
5 changes: 5 additions & 0 deletions core/src/program/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ impl CodeBlock {
Self::Dyn(Dyn::new())
}

/// TODO: add comments
pub fn new_dyncall() -> Self {
Self::Call(Call::new(Dyn::dyn_hash()))
}

/// TODO: add comments
pub fn new_proxy(code_hash: Digest) -> Self {
Self::Proxy(Proxy::new(code_hash))
Expand Down
22 changes: 22 additions & 0 deletions docs/src/user_docs/assembly/code_organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,28 @@ begin
end
```

#### Dynamic procedure invocation
It is also possible to invoke procedures dynamically - i.e., without specifying target procedure labels at compile time. There are two instructions, `dynexec` and `dyncall`, which can be used to execute dynamically-specified code targets. Both instructions expect [MAST root](../../design/programs.md) of the target to be provided via the stack. The difference between `dynexec` and `dyncall` is that `dyncall` will [change context](./execution_contexts.md) before executing the dynamic code target, while `dynexec` will cause the code target to be executed in the current context.

Dynamic code execution in the same context is achieved by setting the top $4$ elements of the stack to the hash of the dynamic code block and then executing the following instruction:

```
dynexec
```

This causes the VM to do the following:

1. Read the top 4 elements of the stack to get the hash of the dynamic target (leaving the stack unchanged).
2. Execute the code block which hashes to the specified target. The VM must know the specified code block and hash (they must be in the CodeBlockTable of the executing Program).

Dynamic code execution in a new context can be achieved similarly by setting the top $4$ elements of the stack to the hash of the dynamic code block and then executing the following instruction:

```
dyncall
```

> **Note**: In both cases, the stack is left unchanged. Therefore, if the dynamic code is intended to manipulate the stack, it should start by either dropping or moving the code block hash from the top of the stack.
### Modules
A *module* consists of one or more procedures. There are two types of modules: *library modules* and *executable modules* (also called *programs*).

Expand Down
2 changes: 1 addition & 1 deletion docs/src/user_docs/assembly/flow_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ while.true
# push the boolean false to the stack, finishing the loop for the next iteration
push.0
end
```
```
143 changes: 143 additions & 0 deletions miden/tests/integration/flow_control/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,146 @@ fn simple_syscall() {

test.prove_and_verify(vec![1, 2], false);
}

// DYNAMIC CODE EXECUTION
// ================================================================================================

#[test]
fn simple_dyn_exec() {
let program_source = "
proc.foo
# drop the top 4 values, since that will be the code hash when we call this dynamically
dropw
add
end
begin
# call foo directly so it will get added to the CodeBlockTable
padw
call.foo
# move the first result of foo out of the way
movdn.4
# use dynexec to call foo again via its hash, which is on the stack
dynexec
end";

// The hash of foo can be obtained from the code block table by:
// let program = test.compile();
// let cb_table = program.cb_table();
// Result:
// [BaseElement(14592192105906586403), BaseElement(9256464248508904838),
// BaseElement(17436090329036592832), BaseElement(10814467189528518943)]
// Integer values can be obtained via Felt::from_mont(14592192105906586403).as_int(), etc.
// As ints:
// [16045159387802755434, 10308872899350860082, 17306481765929021384, 16642043361554117790]

let test = Test {
source: program_source.to_string(),
kernel: None,
stack_inputs: StackInputs::try_from_values([
3,
// put the hash of foo on the stack
16045159387802755434,
10308872899350860082,
17306481765929021384,
16642043361554117790,
1,
2,
])
.unwrap(),
advice_inputs: AdviceInputs::default(),
in_debug_mode: false,
libraries: Vec::default(),
};

test.expect_stack(&[6]);

test.prove_and_verify(
vec![
3,
16045159387802755434,
10308872899350860082,
17306481765929021384,
16642043361554117790,
1,
2,
],
false,
);
}

#[test]
fn simple_dyncall() {
let program_source = "
proc.foo
# drop the top 4 values, since that will be the code hash when we call this dynamically
dropw
# test that the execution context has changed
mem_load.0 assertz
# add the two values on top of the stack
add
end
begin
# write to memory so we can test that `call` and `dyncall` change the execution context
push.5 mem_store.0
# call foo directly so it will get added to the CodeBlockTable
padw
call.foo
# move the first result of foo out of the way
movdn.4
# use dyncall to call foo again via its hash, which is on the stack
dyncall
end";

// The hash of foo can be obtained from the code block table by:
// let program = test.compile();
// let cb_table = program.cb_table();
// Result:
// [BaseElement(3961142802598954486), BaseElement(5305628994393606376),
// BaseElement(7971171833137344204), BaseElement(10465350313512331391)]
// Integer values can be obtained via Felt::from_mont(14592192105906586403).as_int(), etc.
// As ints:
// [8324248212344458853, 17691992706129158519, 18131640149172243086, 16129275750103409835]

let test = Test {
source: program_source.to_string(),
kernel: None,
stack_inputs: StackInputs::try_from_values([
3,
// put the hash of foo on the stack
8324248212344458853,
17691992706129158519,
18131640149172243086,
16129275750103409835,
1,
2,
])
.unwrap(),
advice_inputs: AdviceInputs::default(),
in_debug_mode: false,
libraries: Vec::default(),
};

test.expect_stack(&[6]);

test.prove_and_verify(
vec![
3,
8324248212344458853,
17691992706129158519,
18131640149172243086,
16129275750103409835,
1,
2,
],
false,
);
}
15 changes: 10 additions & 5 deletions processor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,16 @@ where

self.start_call_block(block)?;

// get function body from the code block table and execute it
let fn_body = cb_table
.get(block.fn_hash())
.ok_or_else(|| ExecutionError::CodeBlockNotFound(block.fn_hash()))?;
self.execute_code_block(fn_body, cb_table)?;
// if this is a dyncall, execute the dynamic code block
if block.fn_hash() == Dyn::dyn_hash() {
self.execute_dyn_block(&Dyn::new(), cb_table)?;
} else {
// get function body from the code block table and execute it
let fn_body = cb_table
.get(block.fn_hash())
.ok_or_else(|| ExecutionError::CodeBlockNotFound(block.fn_hash()))?;
self.execute_code_block(fn_body, cb_table)?;
}

self.end_call_block(block)
}
Expand Down

0 comments on commit 48b0eb0

Please sign in to comment.