Replies: 3 comments 6 replies
-
As discussed in #298 , it is desirable to decouple the assembler from stdlib. The following is a variation over a foundational idea proposed by @bobbinth /// A procedure is a mapping from a name to parseable contents
pub trait Procedure {
type Name;
type Contents;
}
/// The index is a pure function that takes a domain & name, and injectively maps to an index
pub trait IndexedProcedure {
type Domain;
type Index: Clone;
type Procedure: Procedure;
fn index(domain: &Self::Domain, name: &<Self::Procedure as Procedure>::Name) -> Self::Index;
}
/// A source provider will map an index to the source contents. As a regular source file, it might
/// contain multiple procedures, each indexed by its own unique idx.
pub trait SourceProvider {
type Error;
type Procedure: IndexedProcedure;
fn contents(
&self,
index: &<Self::Procedure as IndexedProcedure>::Index,
) -> Result<
&<<Self::Procedure as IndexedProcedure>::Procedure as Procedure>::Contents,
Self::Error,
>;
}
/// The assembler will take a procedure contents and parse these into an AST.
///
/// The main restriction imposed by this scheme is that the assembler should behave as a pure
/// function; hence, it should have no state.
pub trait Assembler {
type Error: From<<Self::SourceProvider as SourceProvider>::Error>;
type SourceProvider: SourceProvider;
type Ast;
fn assemble(
contents: &<<<Self::SourceProvider as SourceProvider>::Procedure as IndexedProcedure>::Procedure as Procedure>::Contents,
) -> Result<Self::Ast, Self::Error>;
fn assemble_from_source(
provider: &Self::SourceProvider,
index: &<<Self::SourceProvider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Result<Self::Ast, Self::Error> {
let contents = provider.contents(index)?;
Self::assemble(contents)
}
}
/// This error variant will be returned when an append+fetch returns None.
pub enum ProcedureProviderError {
InconsistentAppend,
}
/// A procedure provider will be a mapping from an index to a parsed AST.
pub trait ProcedureProvider {
type Error: From<<Self::Assembler as Assembler>::Error> + From<ProcedureProviderError>;
type Assembler: Assembler;
fn append(
&self,
index: <<<Self::Assembler as Assembler>:: SourceProvider as SourceProvider>::Procedure as IndexedProcedure>::Index,
ast: <Self::Assembler as Assembler>::Ast,
);
fn fetch(
&self,
index: &<<<Self::Assembler as Assembler>:: SourceProvider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Option<<Self::Assembler as Assembler>::Ast>;
fn fetch_or_assemble(
&self,
sources: &<Self::Assembler as Assembler>::SourceProvider,
index: &<<<Self::Assembler as Assembler>:: SourceProvider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Result<<Self::Assembler as Assembler>::Ast, Self::Error> {
match self.fetch(index) {
Some(ast) => Ok(ast),
None => {
let ast = Self::Assembler::assemble_from_source(sources, index)?;
self.append(index.clone(), ast);
self.fetch(&index)
.ok_or_else(|| ProcedureProviderError::InconsistentAppend.into())
}
}
}
}
/// This is a set of concrete implementations of the traits scheme
pub mod concrete {
use std::collections::HashMap;
use std::{mem, sync};
/// An index is normally a truncated representation of the tokens of some procedure, stripped
/// of the tokens that will not compose the AST such as comments, style indentation,
/// linebreaks, etc.
pub type Index = [u8; 20];
pub enum Error {
ProcedureNotFound,
InconsistentAppend,
}
impl From<super::ProcedureProviderError> for Error {
fn from(value: super::ProcedureProviderError) -> Self {
match value {
super::ProcedureProviderError::InconsistentAppend => Self::InconsistentAppend,
}
}
}
/// The AST in this example is a 1:1 with the source contents.
#[derive(Debug, Clone)]
pub struct Ast {
pub contents: String,
}
/// A procedure is just a name+contents tuple
pub struct Procedure {
pub name: String,
pub contents: String,
}
impl super::Procedure for Procedure {
type Name = String;
type Contents = String;
}
/// The indexed procedure will just bind a domain+index to a procedure.
///
/// In this example, we XOR the chunks of the domain+name so we injectively compute its index
pub struct IndexedProcedure {
pub domain: String,
pub index: Index,
pub procedure: Procedure,
}
impl super::IndexedProcedure for IndexedProcedure {
type Domain = String;
type Index = Index;
type Procedure = Procedure;
fn index(domain: &String, name: &String) -> Index {
// simple & cheap injective XOR based function
format!("{}::{}", domain, name)
.as_bytes()
.chunks(mem::size_of::<Index>())
.fold(Index::default(), |mut idx, chunk| {
idx.iter_mut().zip(chunk.iter()).for_each(|(i, c)| *i ^= *c);
idx
})
}
}
/// This source provider emulates a disk with a source file and will return procedure contents
/// based on an index
pub struct SourceProvider {
pub contents: HashMap<Index, String>,
}
impl super::SourceProvider for SourceProvider {
type Error = Error;
type Procedure = IndexedProcedure;
fn contents(&self, index: &Index) -> Result<&String, Error> {
self.contents.get(index).ok_or(Error::ProcedureNotFound)
}
}
/// The assembler is a set of pure functions that will construct the AST, provided contents
pub struct Assembler;
impl super::Assembler for Assembler {
type Error = Error;
type SourceProvider = SourceProvider;
type Ast = Ast;
fn assemble(contents: &String) -> Result<Self::Ast, Self::Error> {
Ok(Ast {
contents: contents.clone(),
})
}
}
/// A procedure provider will map indexes to parsed ASTs
pub struct ProcedureProvider {
procedures: sync::Arc<sync::Mutex<HashMap<Index, Ast>>>,
}
impl super::ProcedureProvider for ProcedureProvider {
type Error = Error;
type Assembler = Assembler;
fn append(&self, index: Index, ast: Ast) {
// unwrap is used here for the sake of simplicity. alternatively, we can use
// parking_lot for a robust infallible RwLock implementation
//
// https://docs.rs/parking_lot/latest/parking_lot/type.RwLock.html
self.procedures.lock().unwrap().insert(index, ast);
}
fn fetch(&self, index: &Index) -> Option<Ast> {
self.procedures.lock().unwrap().get(index).cloned()
}
}
} |
Beta Was this translation helpful? Give feedback.
-
I redesigned the initial proposal taking the comments into account and came with this new model. While the model is being evaluated, will in parallel build an example over that /// A procedure is a mapping from a name to parseable contents
pub trait Procedure {
type Name;
type Contents;
}
/// The index is a pure function that takes a domain & name, and injectively maps to an index
pub trait IndexedProcedure {
type Domain;
type Index: Clone;
type Procedure: Procedure;
fn index(domain: &Self::Domain, name: &<Self::Procedure as Procedure>::Name) -> Self::Index;
}
/// A source provider will map an index to the source contents. As a regular source file, it might
/// contain multiple procedures, each indexed by its own unique idx.
pub trait SourceProvider {
type Error;
type Procedure: IndexedProcedure;
fn contents(
&self,
index: &<Self::Procedure as IndexedProcedure>::Index,
) -> Result<
&<<Self::Procedure as IndexedProcedure>::Procedure as Procedure>::Contents,
Self::Error,
>;
}
pub trait Ast: Clone + IntoIterator<Item = Self> {
type Provider: SourceProvider;
fn indexable(
&self,
) -> Option<(
&<<Self::Provider as SourceProvider>::Procedure as IndexedProcedure>::Domain,
&<<<Self::Provider as SourceProvider>::Procedure as IndexedProcedure>::Procedure as Procedure>::Name,
)>;
fn index(
&self,
) -> Option<<<Self::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index> {
self.indexable().map(|(domain, name)| {
<<Self::Provider as SourceProvider>::Procedure as IndexedProcedure>::index(domain, name)
})
}
}
pub trait Parser {
type Error: From<<<Self::Ast as Ast>::Provider as SourceProvider>::Error>;
type Ast: Ast;
fn parse(
contents: &<<<<Self::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Procedure as Procedure>::Contents,
) -> Result<Self::Ast, Self::Error>;
fn parse_from_source(
provider: &<Self::Ast as Ast>::Provider,
index: &<<<Self::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Result<Self::Ast, Self::Error> {
let contents = provider.contents(index)?;
Self::parse(contents)
}
}
/// This error variant will be returned when an append+fetch returns None.
pub enum ProcedureProviderError {
InconsistentAppend,
}
pub trait ProcedureProvider {
type Error: From<<Self::Parser as Parser>::Error> + From<ProcedureProviderError>;
type Parser: Parser;
fn append(
&self,
index: <<<<Self::Parser as Parser>::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
ast: <Self::Parser as Parser>::Ast,
);
fn fetch(
&self,
index: &<<<<Self::Parser as Parser>::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Option<<Self::Parser as Parser>::Ast>;
fn fetch_or_parse(
&self,
sources: &<<Self::Parser as Parser>::Ast as Ast>::Provider,
index: &<<<<Self::Parser as Parser>::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Result<<Self::Parser as Parser>::Ast, Self::Error> {
match self.fetch(index) {
Some(ast) => Ok(ast),
None => {
let ast = Self::Parser::parse_from_source(sources, index)?;
self.append(index.clone(), ast);
self.fetch(&index)
.ok_or_else(|| ProcedureProviderError::InconsistentAppend.into())
}
}
}
}
pub trait AssemblerContext {
type IndexedAst: From<<<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast>
+ FromIterator<Self::IndexedAst>;
type Provider: ProcedureProvider;
type Mast;
fn append(
&self,
index: <<<<<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
ast: <<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast,
);
fn build(&self, ast: Self::IndexedAst) -> Self::Mast;
/// Traverse the AST in postorder, mutating the call nodes into indexed call
/// e | e
/// / \ | / \
/// c d |--> i d, i |-> c
/// / \ | / \
/// a b | a b
fn reduce(
&self,
ast: <<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast,
) -> Self::IndexedAst {
ast.into_iter()
.map(|node| {
if let Some(index) = node.index() {
self.append(index, node.clone());
}
Self::IndexedAst::from(node)
})
.collect()
}
fn compile(
&self,
ast: <<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast,
) -> Self::Mast {
let ast = self.reduce(ast);
self.build(ast)
}
} |
Beta Was this translation helpful? Give feedback.
-
Update with example. We don't need to follow this structure, but it might be beneficial to diff from the current implementation and extract the points that are advantageous to decouple stdlib from assembly. /// A procedure is a mapping from a name to parseable contents
pub trait Procedure {
type Name;
type Contents;
}
/// The index is a pure function that takes a domain & name, and injectively maps to an index
pub trait IndexedProcedure {
type Domain;
type Index: Clone;
type Procedure: Procedure;
fn index(domain: &Self::Domain, name: &<Self::Procedure as Procedure>::Name) -> Self::Index;
}
/// A source provider will map an index to the source contents. As a regular source file, it might
/// contain multiple procedures, each indexed by its own unique idx.
pub trait SourceProvider {
type Error;
type Procedure: IndexedProcedure;
fn contents(
&self,
index: &<Self::Procedure as IndexedProcedure>::Index,
) -> Result<
&<<Self::Procedure as IndexedProcedure>::Procedure as Procedure>::Contents,
Self::Error,
>;
}
pub trait Ast: Clone + IntoIterator<Item = Self> {
type Provider: SourceProvider;
fn indexable(
&self,
) -> Option<(
&<<Self::Provider as SourceProvider>::Procedure as IndexedProcedure>::Domain,
&<<<Self::Provider as SourceProvider>::Procedure as IndexedProcedure>::Procedure as Procedure>::Name,
)>;
fn index(
&self,
) -> Option<<<Self::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index> {
self.indexable().map(|(domain, name)| {
<<Self::Provider as SourceProvider>::Procedure as IndexedProcedure>::index(domain, name)
})
}
}
pub trait Parser {
type Error: From<<<Self::Ast as Ast>::Provider as SourceProvider>::Error>;
type Ast: Ast;
fn parse(
contents: &<<<<Self::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Procedure as Procedure>::Contents,
) -> Result<Self::Ast, Self::Error>;
fn parse_from_source(
provider: &<Self::Ast as Ast>::Provider,
index: &<<<Self::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Result<Self::Ast, Self::Error> {
let contents = provider.contents(index)?;
Self::parse(contents)
}
}
/// This error variant will be returned when an append+fetch returns None.
pub enum ProcedureProviderError {
InconsistentAppend,
}
pub trait ProcedureProvider {
type Error: From<<Self::Parser as Parser>::Error> + From<ProcedureProviderError>;
type Parser: Parser;
fn append(
&self,
index: <<<<Self::Parser as Parser>::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
ast: <Self::Parser as Parser>::Ast,
);
fn fetch(
&self,
index: &<<<<Self::Parser as Parser>::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Option<<Self::Parser as Parser>::Ast>;
fn fetch_or_parse(
&self,
sources: &<<Self::Parser as Parser>::Ast as Ast>::Provider,
index: &<<<<Self::Parser as Parser>::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
) -> Result<<Self::Parser as Parser>::Ast, Self::Error> {
match self.fetch(index) {
Some(ast) => Ok(ast),
None => {
let ast = Self::Parser::parse_from_source(sources, index)?;
self.append(index.clone(), ast);
self.fetch(&index)
.ok_or_else(|| ProcedureProviderError::InconsistentAppend.into())
}
}
}
}
pub trait Assembler {
type Error: From<<Self::Provider as ProcedureProvider>::Error>;
type IndexedAst: From<<<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast>
+ FromIterator<Self::IndexedAst>;
type Provider: ProcedureProvider;
type Mast;
fn append(
&self,
index: <<<<<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast as Ast>::Provider as SourceProvider>::Procedure as IndexedProcedure>::Index,
ast: <<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast,
);
fn build(&self, ast: Self::IndexedAst) -> Result<Self::Mast, Self::Error>;
/// Traverse the AST in postorder, mutating the call nodes into indexed call
/// e | e
/// / \ | / \
/// c d |--> i d, i |-> c
/// / \ | / \
/// a b | a b
fn reduce(
&self,
ast: <<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast,
) -> Self::IndexedAst {
ast.into_iter()
.map(|node| {
if let Some(index) = node.index() {
self.append(index, node.clone());
}
Self::IndexedAst::from(node)
})
.collect()
}
fn compile(
&self,
ast: <<Self::Provider as ProcedureProvider>::Parser as Parser>::Ast,
) -> Result<Self::Mast, Self::Error> {
let ast = self.reduce(ast);
self.build(ast)
}
}
pub mod concrete {
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::{mem, vec};
use crate::{IndexedProcedure as _, ProcedureProvider as _};
/// An index is normally a truncated representation of the tokens of some procedure, stripped
/// of the tokens that will not compose the AST such as comments, style indentation,
/// linebreaks, etc.
pub type Index = [u8; 20];
pub enum Error {
ProcedureNotFound,
InconsistentAppend,
}
impl From<super::ProcedureProviderError> for Error {
fn from(value: super::ProcedureProviderError) -> Self {
match value {
super::ProcedureProviderError::InconsistentAppend => Self::InconsistentAppend,
}
}
}
#[derive(Clone)]
pub enum Ast {
Noop,
Call { domain: String, name: String },
Node(Vec<Self>),
}
impl IntoIterator for Ast {
type Item = Self;
type IntoIter = vec::IntoIter<Self>;
fn into_iter(self) -> Self::IntoIter {
match self {
Ast::Noop => vec![Ast::Noop],
Ast::Call { domain, name } => vec![Ast::Call { domain, name }],
Ast::Node(node) => node,
}
.into_iter()
}
}
impl super::Ast for Ast {
type Provider = SourceProvider;
fn indexable(&self) -> Option<(&String, &String)> {
match self {
Ast::Call { domain, name } => Some((domain, name)),
_ => None,
}
}
}
/// A procedure is just a name+contents tuple
pub struct Procedure {
pub name: String,
pub contents: Ast,
}
impl super::Procedure for Procedure {
type Name = String;
type Contents = Ast;
}
/// The indexed procedure will just bind a domain+index to a procedure.
///
/// In this example, we XOR the chunks of the domain+name so we injectively compute its index
pub struct IndexedProcedure {
pub domain: String,
pub index: Index,
pub procedure: Procedure,
}
impl super::IndexedProcedure for IndexedProcedure {
type Domain = String;
type Index = Index;
type Procedure = Procedure;
fn index(domain: &String, name: &String) -> Index {
// simple & cheap injective XOR based function
format!("{}::{}", domain, name)
.as_bytes()
.chunks(mem::size_of::<Index>())
.fold(Index::default(), |mut idx, chunk| {
idx.iter_mut().zip(chunk.iter()).for_each(|(i, c)| *i ^= *c);
idx
})
}
}
/// This source provider emulates a disk with a source file and will return procedure contents
/// based on an index
pub struct SourceProvider {
pub contents: HashMap<Index, Ast>,
}
impl super::SourceProvider for SourceProvider {
type Error = Error;
type Procedure = IndexedProcedure;
fn contents(&self, index: &Index) -> Result<&Ast, Error> {
self.contents.get(index).ok_or(Error::ProcedureNotFound)
}
}
pub struct Parser;
impl super::Parser for Parser {
type Error = Error;
type Ast = Ast;
fn parse(contents: &Ast) -> Result<Ast, Error> {
// for the sake of simplicity, we just transparently parse here
Ok(contents.clone())
}
}
pub struct ProcedureProvider {
pub procedures: Arc<Mutex<HashMap<Index, Ast>>>,
}
impl super::ProcedureProvider for ProcedureProvider {
type Error = Error;
type Parser = Parser;
fn append(&self, index: Index, ast: Ast) {
// unwrap is used here for the sake of simplicity. alternatively, we can use
// parking_lot for a robust infallible RwLock implementation
//
// https://docs.rs/parking_lot/latest/parking_lot/type.RwLock.html
self.procedures.lock().unwrap().insert(index, ast);
}
fn fetch(&self, index: &Index) -> Option<Ast> {
self.procedures.lock().unwrap().get(index).cloned()
}
}
#[derive(Clone)]
pub enum IndexedAst {
Noop,
Call(Index),
Node(Vec<Self>),
}
impl From<Ast> for IndexedAst {
fn from(ast: Ast) -> Self {
match ast {
Ast::Noop => Self::Noop,
Ast::Call { domain, name } => Self::Call(IndexedProcedure::index(&domain, &name)),
Ast::Node(node) => Self::Node(node.into_iter().map(Self::from).collect()),
}
}
}
impl FromIterator<IndexedAst> for IndexedAst {
fn from_iter<T: IntoIterator<Item = IndexedAst>>(iter: T) -> Self {
let nodes: Vec<_> = iter.into_iter().collect();
let len = nodes.len();
match len {
0 => Self::Noop,
1 => nodes[0].clone(),
_ => Self::Node(nodes),
}
}
}
impl IntoIterator for IndexedAst {
type Item = Self;
type IntoIter = vec::IntoIter<Self>;
fn into_iter(self) -> Self::IntoIter {
match self {
IndexedAst::Noop => vec![Self::Noop],
IndexedAst::Call(index) => vec![Self::Call(index)],
IndexedAst::Node(nodes) => nodes,
}
.into_iter()
}
}
pub enum Instruction {
Noop,
}
pub struct Mast {
pub instructions: Vec<Instruction>,
}
impl From<Instruction> for Mast {
fn from(instruction: Instruction) -> Self {
Mast {
instructions: vec![instruction],
}
}
}
impl FromIterator<Mast> for Mast {
fn from_iter<T: IntoIterator<Item = Mast>>(iter: T) -> Self {
let instructions = iter.into_iter().fold(vec![], |mut instructions, mut mast| {
instructions.append(&mut mast.instructions);
instructions
});
Self { instructions }
}
}
impl FromIterator<Ast> for Mast {
fn from_iter<T: IntoIterator<Item = Ast>>(iter: T) -> Self {
Self {
instructions: iter
.into_iter()
.map(|ast| match ast {
Ast::Noop => vec![Instruction::Noop],
// this is probably a bug in the cache since indexed mast should be
// filtered out in the build routine
Ast::Call { .. } => vec![],
Ast::Node(node) => node.into_iter().collect::<Self>().instructions,
})
.map(Vec::into_iter)
.flatten()
.collect(),
}
}
}
pub struct Assembler {
pub provider: ProcedureProvider,
pub context: Arc<Mutex<HashMap<Index, IndexedAst>>>,
}
impl super::Assembler for Assembler {
type Error = Error;
type IndexedAst = IndexedAst;
type Provider = ProcedureProvider;
type Mast = Mast;
fn append(&self, index: Index, ast: Ast) {
self.provider.append(index, ast);
}
fn build(&self, ast: IndexedAst) -> Result<Mast, Error> {
match ast {
IndexedAst::Noop => Ok(Instruction::Noop.into()),
// for simplicity, not supporting recursive calls
IndexedAst::Call(index) => {
let ast = self
.context
.lock()
.unwrap()
.get(&index)
.cloned()
.ok_or(Error::ProcedureNotFound)?;
self.build(ast)
}
IndexedAst::Node(nodes) => Ok(nodes
.into_iter()
.map(|a| self.build(a))
.collect::<Result<_, _>>()?),
}
}
}
} |
Beta Was this translation helpful? Give feedback.
-
We have several inter-related updates planned for v0.3 release (#361 ) which aim to improve Miden assembly. I thought it would make sense to describe the goals and challenges in more detail.
First, let's go over the intended use of Miden assembly:
Currently, Miden assembly is written and stored in plain text. This creates a couple of challenges:
An obvious question would be: why not compile Miden assembly to MAST and then just serialize/deserialize MAST as needed. There are a couple of reasons for this (though we should probably examine if they are still as valid as I originally thought):
So, the approach we currently are thinking of implementing is to serialize Miden assembly into binary format. This solves our challenges as follows:
Overall, serializing/deserializing Miden assembly should work pretty well. However, there are a few challenges. All these challenges are related to handling
exec
,call
, and (in the future)syscall
instructions. Specifically, the question is: how should we specify call targets for these instructions.In the plain-text source, we use strings (fully qualified paths). When a procedure from standard library is invoked in a program (via the
exec
instruction) we'd look up the module to which this procedure belongs, and then in that module would find the procedure by name. But what should we do in the binary format?For code which is stored locally (e.g., stdlib), we can probably still use strings. In fact, this would probably be the simplest thing to do. However, for the code which goes on chain, using string-based references is probably not a good idea. So, there are might be a few ways to do better.
First, for local procedures (procedures declared in the same module), we could just use numerical indexes. For example:
Procedure
foo
would have index 0, and procedurebar
would have index 1.Second, for procedure calls to well-known libraries (e.g., stdlib), we could use something like a 20-byte hash of the fully-qualified path. This would mean that when instantiating a library in memory, we'd need to create a map which would allow us to look up module and procedure names by hash. But I think that shouldn't be difficult to do and there could be interesting ways of optimizing this lookup process.
Third, in some cases, we'll need to make calls by MAST root. For example, this is something that would happen when a note script invokes one of interface methods of an account via
call
instruction. Thus, to execute such programs we'd need to maintain a map of MAST roots to corresponding procedure definitions (we'd probably need to do this anyway to be able to execute public account methods).To summarize the above:
We could have different variants of relevant instructions. For example,
exec
instruction could have 4 different variants:Similar could be applied to
call
andsyscall
though more thinking is needed to understand whether all of the above options are relevant to these instructions.Also, we could potentially get rid of the first variant (the fully-qualified path) to reduce the number of variants.
Beta Was this translation helpful? Give feedback.
All reactions