Skip to content

Commit

Permalink
feat: implement exported procedures
Browse files Browse the repository at this point in the history
  • Loading branch information
bobbinth committed Feb 22, 2022
1 parent c878f04 commit 90a908a
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 160 deletions.
23 changes: 21 additions & 2 deletions assembly/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ impl AssemblyError {
}
}

pub fn prc_export_not_allowed(token: &Token, label: &str) -> Self {
AssemblyError {
message: format!("exported procedures not allowed in this context: {}", label),
step: token.pos(),
op: token.to_string(),
}
}

// IMPORTS AND MODULES
// --------------------------------------------------------------------------------------------

Expand All @@ -242,9 +250,20 @@ impl AssemblyError {
}
}

pub fn circular_module_dependency(token: &Token, module_stack: &[String]) -> Self {
pub fn circular_module_dependency(token: &Token, module_chain: &[String]) -> Self {
AssemblyError {
message: format!(
"circular module dependency in the following chain: {:?}",
module_chain
),
step: token.pos(),
op: token.to_string(),
}
}

pub fn invalid_module_path(token: &Token, module_path: &str) -> Self {
AssemblyError {
message: format!("circular module dependency in the following chain: {:?}", module_stack),
message: format!("invalid module import path: {}", module_path),
step: token.pos(),
op: token.to_string(),
}
Expand Down
38 changes: 18 additions & 20 deletions assembly/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use vm_core::program::{blocks::CodeBlock, Library, Script};
use vm_stdlib::StdLibrary;
use winter_utils::collections::{BTreeMap, Vec};
use winter_utils::collections::BTreeMap;

mod context;
use context::AssemblyContext;
Expand All @@ -9,7 +9,7 @@ mod procedures;
use procedures::Procedure;

mod parsers;
use parsers::{parse_code_blocks, parse_proc_blocks};
use parsers::{combine_blocks, parse_code_blocks};

mod tokens;
use tokens::{Token, TokenStream};
Expand Down Expand Up @@ -67,13 +67,11 @@ impl Assembler {
// parse locally defined procedures (if any), and add these procedures to the current
// context
while let Some(token) = tokens.read() {
match token.parts()[0] {
Token::PROC => {
let proc = Procedure::parse(&mut tokens, &context)?;
context.add_local_proc(proc);
}
let proc = match token.parts()[0] {
Token::PROC | Token::EXPORT => Procedure::parse(&mut tokens, &context, false)?,
_ => break,
}
};
context.add_local_proc(proc);
}

// make sure script body is present
Expand Down Expand Up @@ -120,7 +118,7 @@ impl Assembler {

// check if a module with the same path is currently being parsed somewhere up
// the chain; if it is, then we have a circular dependency.
if let Some(_) = dep_chain.iter().position(|v| v == module_path) {
if dep_chain.iter().any(|v| v == module_path) {
dep_chain.push(module_path.clone());
return Err(AssemblyError::circular_module_dependency(token, dep_chain));
}
Expand Down Expand Up @@ -185,13 +183,11 @@ impl Assembler {
// parse procedures defined in the module, and add these procedures to the current
// context
while let Some(token) = tokens.read() {
match token.parts()[0] {
Token::PROC => {
let proc = Procedure::parse(&mut tokens, &context)?;
context.add_local_proc(proc);
}
let proc = match token.parts()[0] {
Token::PROC | Token::EXPORT => Procedure::parse(&mut tokens, &context, true)?,
_ => break,
}
};
context.add_local_proc(proc);
}

// make sure there are no dangling instructions after all procedures have been read
Expand All @@ -200,12 +196,14 @@ impl Assembler {
return Err(AssemblyError::dangling_ops_after_module(token, path));
}

// extract only the exported local procedures from the context, and insert them
// into `self.parsed_procedures`
let path = path.to_string();
let module_procs = context.into_local_procs();
// extract the exported local procedures from the context
let mut module_procs = context.into_local_procs();
module_procs.retain(|_, p| p.is_export());

// insert exported procedures into `self.parsed_procedures`
// TODO: figure out how to do this using interior mutability
unsafe {
// TODO: figure out how to do this using interior mutability
let path = path.to_string();
let mutable_self = &mut *(self as *const _ as *mut Assembler);
mutable_self.parsed_modules.insert(path, module_procs);
}
Expand Down
38 changes: 3 additions & 35 deletions assembly/src/parsers/blocks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::{
parse_op_token, AssemblyContext, AssemblyError, CodeBlock, Operation, Token, TokenStream,
};
use vm_core::Felt;
use winter_utils::{collections::Vec, group_vector_elements};

// BLOCK PARSER
Expand Down Expand Up @@ -36,37 +35,6 @@ pub fn parse_code_blocks(
}
}

pub fn parse_proc_blocks(
tokens: &mut TokenStream,
context: &AssemblyContext,
num_proc_locals: u32,
) -> Result<CodeBlock, AssemblyError> {
// parse the procedure body
let body = parse_code_blocks(tokens, context, num_proc_locals)?;

if num_proc_locals == 0 {
// if no allocation of locals is required, return the procedure body
return Ok(body);
}

let mut blocks = Vec::new();
let locals_felt = Felt::new(num_proc_locals as u64);

// allocate procedure locals before the procedure body
let alloc_ops = vec![Operation::Push(locals_felt), Operation::FmpUpdate];
blocks.push(CodeBlock::new_span(alloc_ops));

// add the procedure body code block
blocks.push(body);

// deallocate procedure locals after the procedure body
let dealloc_ops = vec![Operation::Push(-locals_felt), Operation::FmpUpdate];
blocks.push(CodeBlock::new_span(dealloc_ops));

// combine the local memory alloc/dealloc blocks with the procedure body code block
Ok(combine_blocks(blocks))
}

// CODE BLOCK PARSER
// ================================================================================================

Expand Down Expand Up @@ -270,7 +238,7 @@ impl BlockParser {
token.validate_end()?;
None
}
Token::BEGIN | Token::PROC => None,
Token::USE | Token::EXPORT | Token::PROC | Token::BEGIN => None,
_ => Some(Self::Span),
},
};
Expand All @@ -279,10 +247,10 @@ impl BlockParser {
}
}

// HELPER FUNCTIONS
// UTILITY FUNCTIONS
// ================================================================================================

fn combine_blocks(mut blocks: Vec<CodeBlock>) -> CodeBlock {
pub fn combine_blocks(mut blocks: Vec<CodeBlock>) -> CodeBlock {
// merge consecutive Span blocks
let mut merged_blocks: Vec<CodeBlock> = Vec::with_capacity(blocks.len());
blocks.drain(0..).for_each(|block| {
Expand Down
2 changes: 1 addition & 1 deletion assembly/src/parsers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{AssemblyContext, AssemblyError, Token, TokenStream};
pub use blocks::{parse_code_blocks, parse_proc_blocks};
pub use blocks::{combine_blocks, parse_code_blocks};
use vm_core::{program::blocks::CodeBlock, Felt, FieldElement, Operation, StarkField};

mod blocks;
Expand Down
70 changes: 0 additions & 70 deletions assembly/src/procedures.rs

This file was deleted.

128 changes: 128 additions & 0 deletions assembly/src/procedures/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use super::{
combine_blocks, parse_code_blocks, AssemblyContext, AssemblyError, CodeBlock, Token,
TokenStream,
};
use vm_core::{Felt, Operation};

// PROCEDURE
// ================================================================================================

/// Contains metadata and code of a procedure.
pub struct Procedure {
label: String,
is_export: bool,
#[allow(dead_code)]
num_locals: u32,
code_root: CodeBlock,
}

impl Procedure {
// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

/// Returns a root of this procedure's MAST.
pub fn code_root(&self) -> &CodeBlock {
&self.code_root
}

/// Returns a label of this procedure.
pub fn label(&self) -> &str {
&self.label
}

/// Returns `true` if this is an exported procedure.
pub fn is_export(&self) -> bool {
self.is_export
}

// PARSER
// --------------------------------------------------------------------------------------------

/// Parses and returns a single procedure from the provided token stream.
///
/// # Errors
/// Returns an error if:
/// - The token stream does not contain a procedure header token at the current position.
/// - Parsing of procedure header token fails (e.g., invalid procedure label).
/// - The procedure is an exported procedure and `allow_export` is false.
/// - A procedure with the same label already exists in the provided context.
/// - Parsing of procedure body fails for any reason.
/// - The procedure body does not terminate with the `END` token.
pub fn parse(
tokens: &mut TokenStream,
context: &AssemblyContext,
allow_export: bool,
) -> Result<Self, AssemblyError> {
let proc_start = tokens.pos();

// read procedure name and consume the procedure header token
let header = tokens.read().expect("missing procedure header");
let (label, num_locals, is_export) = header.parse_proc()?;
if !allow_export && is_export {
return Err(AssemblyError::prc_export_not_allowed(header, &label));
}
if context.contains_proc(&label) {
return Err(AssemblyError::duplicate_proc_label(header, &label));
}
tokens.advance();

// parse procedure body, and handle memory allocation/deallocation of locals if any are declared
let code_root = parse_proc_blocks(tokens, context, num_locals)?;

// consume the 'end' token
match tokens.read() {
None => Err(AssemblyError::unmatched_proc(
tokens.read_at(proc_start).expect("no proc token"),
)),
Some(token) => match token.parts()[0] {
Token::END => token.validate_end(),
_ => Err(AssemblyError::unmatched_proc(
tokens.read_at(proc_start).expect("no proc token"),
)),
},
}?;
tokens.advance();

// build and return the procedure
Ok(Self {
label,
is_export,
num_locals,
code_root,
})
}
}

// HELPER FUNCTIONS
// ================================================================================================

pub fn parse_proc_blocks(
tokens: &mut TokenStream,
context: &AssemblyContext,
num_proc_locals: u32,
) -> Result<CodeBlock, AssemblyError> {
// parse the procedure body
let body = parse_code_blocks(tokens, context, num_proc_locals)?;

if num_proc_locals == 0 {
// if no allocation of locals is required, return the procedure body
return Ok(body);
}

let mut blocks = Vec::new();
let locals_felt = Felt::new(num_proc_locals as u64);

// allocate procedure locals before the procedure body
let alloc_ops = vec![Operation::Push(locals_felt), Operation::FmpUpdate];
blocks.push(CodeBlock::new_span(alloc_ops));

// add the procedure body code block
blocks.push(body);

// deallocate procedure locals after the procedure body
let dealloc_ops = vec![Operation::Push(-locals_felt), Operation::FmpUpdate];
blocks.push(CodeBlock::new_span(dealloc_ops));

// combine the local memory alloc/dealloc blocks with the procedure body code block
Ok(combine_blocks(blocks))
}

0 comments on commit 90a908a

Please sign in to comment.