Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement procref instruction #1113

Merged
merged 6 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
## 0.8.0 (TBD)

#### Assembly
- Expanded capabilities of the `debug` decorator. Added `debug.mem` and `debug.local` variations.
- Expanded capabilities of the `debug` decorator. Added `debug.mem` and `debug.local` variations (#1103).
- Introduced the `emit.<event_id>` assembly instruction (#1119).
- Introduced the `procref.<proc_name>` assembly instruction (#1113).

#### Stdlib
- Introduced `std::utils` module with `is_empty_word` procedure. Refactored `std::collections::smt`
Expand Down
12 changes: 11 additions & 1 deletion assembly/src/assembler/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,23 @@ impl AssemblyContext {

/// Returns the name of the procedure by its ID from the procedure map.
pub fn get_imported_procedure_name(&self, id: &ProcedureId) -> Option<ProcedureName> {
if let Some(module) = self.module_stack.first() {
if let Some(module) = self.module_stack.last() {
module.proc_map.get(id).cloned()
} else {
None
}
}

/// Returns the [Procedure] by its index from the vector of local procedures.
pub fn get_local_procedure(&self, idx: u16) -> Result<&Procedure, AssemblyError> {
let module_context = self.module_stack.last().expect("no modules");
module_context
.compiled_procs
.get(idx as usize)
.map(|named_proc| named_proc.inner())
.ok_or_else(|| AssemblyError::local_proc_not_found(idx, &module_context.path))
}

// STATE MUTATORS
// --------------------------------------------------------------------------------------------

Expand Down
2 changes: 2 additions & 0 deletions assembly/src/assembler/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ impl Assembler {
Instruction::SysCall(id) => self.syscall(id, ctx),
Instruction::DynExec => self.dynexec(),
Instruction::DynCall => self.dyncall(),
Instruction::ProcRefLocal(idx) => self.procref_local(*idx, ctx, span),
Instruction::ProcRefImported(id) => self.procref_imported(id, ctx, span),

// ----- debug decorators -------------------------------------------------------------
Instruction::Breakpoint => {
Expand Down
38 changes: 37 additions & 1 deletion assembly/src/assembler/instruction/procedures.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::{Assembler, AssemblyContext, AssemblyError, CodeBlock, ProcedureId, RpoDigest};
use super::{
super::Vec, Assembler, AssemblyContext, AssemblyError, CodeBlock, Operation, ProcedureId,
RpoDigest, SpanBuilder,
};

// PROCEDURE INVOCATIONS
// ================================================================================================
Expand Down Expand Up @@ -133,4 +136,37 @@ impl Assembler {
// create a new CALL block whose target is DYN
Ok(Some(CodeBlock::new_dyncall()))
}

pub(super) fn procref_local(
&self,
proc_idx: u16,
context: &mut AssemblyContext,
span: &mut SpanBuilder,
) -> Result<Option<CodeBlock>, AssemblyError> {
// get root of the compiled local procedure
let proc_root = context.get_local_procedure(proc_idx)?.mast_root();
// create an array with `Push` operations containing root elements
let ops: Vec<Operation> = proc_root.iter().map(|elem| Operation::Push(*elem)).collect();
span.add_ops(ops)
}

pub(super) fn procref_imported(
&self,
proc_id: &ProcedureId,
context: &mut AssemblyContext,
span: &mut SpanBuilder,
) -> Result<Option<CodeBlock>, AssemblyError> {
// make sure the procedure is in procedure cache
self.ensure_procedure_is_in_cache(proc_id, context)?;

// get the procedure from the assembler
let proc_cache = self.proc_cache.borrow();
let proc = proc_cache.get_by_id(proc_id).expect("procedure not in cache");

// get root of the cimported procedure
let proc_root = proc.mast_root();
// create an array with `Push` operations containing root elements
let ops: Vec<Operation> = proc_root.iter().map(|elem| Operation::Push(*elem)).collect();
span.add_ops(ops)
}
}
8 changes: 8 additions & 0 deletions assembly/src/ast/nodes/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ impl fmt::Display for FormattableInstruction<'_> {
write!(f, "call.")?;
display_hex_bytes(f, &root.as_bytes())?;
}
Instruction::ProcRefLocal(index) => {
let proc_name = self.context.local_proc(*index as usize);
write!(f, "procref.{proc_name}")?;
}
Instruction::ProcRefImported(proc_id) => {
let (_, path) = self.context.imported_proc(proc_id);
write!(f, "procref.{path}")?;
}
_ => {
// Not a procedure call. Use the normal formatting
write!(f, "{}", self.instruction)?;
Expand Down
4 changes: 4 additions & 0 deletions assembly/src/ast/nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ pub enum Instruction {
SysCall(ProcedureId),
DynExec,
DynCall,
ProcRefLocal(u16),
ProcRefImported(ProcedureId),

// ----- debug decorators ---------------------------------------------------------------------
Breakpoint,
Expand Down Expand Up @@ -598,6 +600,8 @@ impl fmt::Display for Instruction {
Self::SysCall(proc_id) => write!(f, "syscall.{proc_id}"),
Self::DynExec => write!(f, "dynexec"),
Self::DynCall => write!(f, "dyncall"),
Self::ProcRefLocal(index) => write!(f, "procref.{index}"),
Self::ProcRefImported(proc_id) => write!(f, "procref.{proc_id}"),

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

// ----- debugging --------------------------------------------------------------------
OpCode::Debug => {
Expand Down
6 changes: 4 additions & 2 deletions assembly/src/ast/nodes/serde/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,14 @@ pub enum OpCode {
SysCall = 246,
DynExec = 247,
DynCall = 248,
ProcRefLocal = 249,
ProcRefImported = 250,

// ----- debugging ----------------------------------------------------------------------------
Debug = 249,
Debug = 251,

// ----- emit --------------------------------------------------------------------------------
Emit = 250,
Emit = 252,

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

// ----- debug decorators -------------------------------------------------------------
Self::Breakpoint => {
Expand Down
21 changes: 21 additions & 0 deletions assembly/src/ast/parsers/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,26 @@ impl ParserContext<'_> {
}
}

// PROCREF PARSERS
// --------------------------------------------------------------------------------------------

/// Parse a `procref` token into an instruction node.
pub fn parse_procref(&mut self, token: &Token) -> Result<Node, ParsingError> {
match token.parse_invocation(token.parts()[0])? {
InvocationTarget::ProcedureName(proc_name) => {
let index = self.get_local_proc_index(proc_name, token)?;
let inner = Instruction::ProcRefLocal(index);
Ok(Node::Instruction(inner))
}
InvocationTarget::ProcedurePath { name, module } => {
let proc_id = self.import_info.add_invoked_proc(&name, module, token)?;
let inner = Instruction::ProcRefImported(proc_id);
Ok(Node::Instruction(inner))
}
_ => Err(ParsingError::invalid_param(token, 1)),
}
}

// PROCEDURE PARSERS
// --------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -622,6 +642,7 @@ impl ParserContext<'_> {
"syscall" => self.parse_syscall(op),
"dynexec" => simple_instruction(op, DynExec),
"dyncall" => simple_instruction(op, DynCall),
"procref" => self.parse_procref(op),

// ----- constant statements ----------------------------------------------------------
"const" => Err(ParsingError::const_invalid_scope(op)),
Expand Down
135 changes: 134 additions & 1 deletion assembly/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
ast::{ModuleAst, ProgramAst},
Assembler, AssemblyContext, Library, LibraryNamespace, LibraryPath, Module, Version,
Assembler, AssemblyContext, AssemblyError, Library, LibraryNamespace, LibraryPath, MaslLibrary,
Module, ProcedureName, Version,
};
use core::slice::Iter;

Expand Down Expand Up @@ -216,6 +217,138 @@ fn call_without_path() {
.unwrap();
}

// PROGRAM WITH PROCREF
// ================================================================================================

#[test]
fn procref_call() {
// instantiate assembler
let assembler = Assembler::default();

// compile first module
let module_path1 = LibraryPath::new("module::path::one").unwrap();
let module_source1 = ModuleAst::parse(
"
export.aaa
push.7.8
end

export.foo
push.1.2
end",
)
.unwrap();

let _roots1 = assembler
.compile_module(
&module_source1,
Some(&module_path1),
&mut AssemblyContext::for_module(false),
)
.unwrap();

// compile second module
let module_path2 = LibraryPath::new("module::path::two").unwrap();
let module_source2 = ModuleAst::parse(
"
use.module::path::one
export.one::foo

export.bar
procref.one::aaa
end",
)
.unwrap();

let _roots2 = assembler
.compile_module(
&module_source2,
Some(&module_path2),
&mut AssemblyContext::for_module(false),
)
.unwrap();

// compile program with procref calls
let program_source = ProgramAst::parse(
"
use.module::path::two

proc.baz.4
push.3.4
end

begin
procref.two::bar
procref.two::foo
procref.baz
end",
)
.unwrap();

let _compiled_program = assembler
.compile_in_context(
&program_source,
&mut AssemblyContext::for_program(Some(&program_source)),
)
.unwrap();
}

#[test]
fn get_proc_name_of_unknown_module() {
// Module `two` is unknown. This program should return
// `AssemblyError::imported_proc_module_not_found` error with `bar` procedure name.
let module_source1 = "
use.module::path::two

export.foo
procref.two::bar
end";
let module_ast1 = ModuleAst::parse(module_source1).unwrap();
let module_path1 = LibraryPath::new("module::path::one").unwrap();
let module1 = Module::new(module_path1, module_ast1);

let masl_lib = MaslLibrary::new(
LibraryNamespace::new("module").unwrap(),
Version::default(),
false,
vec![module1],
vec![],
)
.unwrap();

// instantiate assembler
let assembler = Assembler::default().with_library(&masl_lib).unwrap();

// compile program with procref calls
let program_source = ProgramAst::parse(
"
use.module::path::one

begin
procref.one::foo
end",
)
.unwrap();

let compilation_error = assembler
.compile_in_context(
&program_source,
&mut AssemblyContext::for_program(Some(&program_source)),
)
.err()
.unwrap();

let expected_error = AssemblyError::imported_proc_module_not_found(
&crate::ProcedureId([
17, 137, 148, 17, 42, 108, 60, 23, 205, 115, 62, 70, 16, 121, 221, 142, 51, 247, 250,
43,
]),
ProcedureName::try_from("bar").ok(),
);

assert_eq!(compilation_error, expected_error);
}

// CONSTANTS
// ================================================================================================

Expand Down
9 changes: 5 additions & 4 deletions docs/src/user_docs/assembly/io_operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ In both case the values must still encode valid field elements.

| Instruction | Stack_input | Stack_output | Notes |
| ------------------------------- | ------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| clk <br> - *(1 cycle)* | [ ... ] | [t, ... ] | $t \leftarrow clock\_value()$ <br> Pushes the current value of the clock cycle counter onto the stack. |
| sdepth <br> - *(1 cycle)* | [ ... ] | [d, ... ] | $d \leftarrow stack.depth()$ <br> Pushes the current depth of the stack onto the stack. |
| caller <br> - *(1 cycle)* | [A, b, ... ] | [H, b, ... ] | $H \leftarrow context.fn\_hash()$ <br> Overwrites the top four stack items with the hash of a function which initiated the current SYSCALL. <br> Executing this instruction outside of SYSCALL context will fail. |
| locaddr.*i* <br> - *(2 cycles)* | [ ... ] | [a, ... ] | $a \leftarrow address\_of(i)$ <br> Pushes the absolute memory address of local memory at index $i$ onto the stack. |
| clk <br> - *(1 cycle)* | [ ... ] | [t, ... ] | $t \leftarrow clock\_value()$ <br> Pushes the current value of the clock cycle counter onto the stack. |
| sdepth <br> - *(1 cycle)* | [ ... ] | [d, ... ] | $d \leftarrow stack.depth()$ <br> Pushes the current depth of the stack onto the stack. |
| caller <br> - *(1 cycle)* | [A, b, ... ] | [H, b, ... ] | $H \leftarrow context.fn\_hash()$ <br> Overwrites the top four stack items with the hash of a function which initiated the current SYSCALL. <br> Executing this instruction outside of SYSCALL context will fail. |
| locaddr.*i* <br> - *(2 cycles)* | [ ... ] | [a, ... ] | $a \leftarrow address\_of(i)$ <br> Pushes the absolute memory address of local memory at index $i$ onto the stack. |
| procref.*name* <br> - *(4 cycles)* | [ ... ] | [A, ... ] | $A \leftarrow mast\_root()$ <br> Pushes MAST root of the procedure with name $name$ onto the stack. |

### Nondeterministic inputs

Expand Down
Loading
Loading