Emerge Yul recompiler (#1)

Provide a modified (and incomplete) version of ZKSync zksolc that can compile the most basic contracts
This commit is contained in:
Cyrill Leutwiler
2024-03-12 12:06:02 +01:00
committed by GitHub
parent d238d8f39e
commit cffa14a4d2
247 changed files with 35357 additions and 4905 deletions
+64
View File
@@ -0,0 +1,64 @@
//!
//! The Yul IR parser error.
//!
use std::collections::BTreeSet;
use crate::yul::lexer::token::location::Location;
///
/// The Yul IR parser error.
///
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// An invalid token received from the lexer.
#[error("{location} Expected one of {expected:?}, found `{found}`")]
InvalidToken {
/// The invalid token location.
location: Location,
/// The list of expected tokens.
expected: Vec<&'static str>,
/// The invalid token.
found: String,
},
/// A reserved keyword cannot be used as an identifier.
#[error("{location} The identifier `{identifier}` is reserved")]
ReservedIdentifier {
/// The invalid token location.
location: Location,
/// The invalid identifier.
identifier: String,
},
/// Invalid number of function arguments.
#[error("{location} Function `{identifier}` must have {expected} arguments, found {found}")]
InvalidNumberOfArguments {
/// The invalid function location.
location: Location,
/// The invalid function name.
identifier: String,
/// The expected number of arguments.
expected: usize,
/// The actual number of arguments.
found: usize,
},
/// Invalid object name.
#[error(
"{location} Objects must be named as '<name>' (deploy) and '<name>_deployed' (runtime)"
)]
InvalidObjectName {
/// The invalid token location.
location: Location,
/// The expected identifier.
expected: String,
/// The invalid identifier.
found: String,
},
/// Invalid attributes.
#[error("{location} Found invalid LLVM attributes: {values:?}")]
InvalidAttributes {
/// The invalid token location.
location: Location,
/// The list of invalid attributes.
values: BTreeSet<String>,
},
}
@@ -0,0 +1,127 @@
//!
//! The YUL source code identifier.
//!
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::r#type::Type;
///
/// The YUL source code identifier.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Identifier {
/// The location.
pub location: Location,
/// The inner string.
pub inner: String,
/// The type, if it has been explicitly specified.
pub r#type: Option<Type>,
}
impl Identifier {
///
/// A shortcut constructor.
///
pub fn new(location: Location, inner: String) -> Self {
Self {
location,
inner,
r#type: None,
}
}
///
/// A shortcut constructor for a typed identifier.
///
pub fn new_with_type(location: Location, inner: String, r#type: Option<Type>) -> Self {
Self {
location,
inner,
r#type,
}
}
///
/// Parses the identifier list where the types cannot be specified.
///
pub fn parse_list(
lexer: &mut Lexer,
mut initial: Option<Token>,
) -> Result<(Vec<Self>, Option<Token>), Error> {
let mut result = Vec::new();
let mut expected_comma = false;
loop {
let token = crate::yul::parser::take_or_next(initial.take(), lexer)?;
match token {
Token {
location,
lexeme: Lexeme::Identifier(identifier),
..
} if !expected_comma => {
result.push(Self::new(location, identifier.inner));
expected_comma = true;
}
Token {
lexeme: Lexeme::Symbol(Symbol::Comma),
..
} if expected_comma => {
expected_comma = false;
}
token => return Ok((result, Some(token))),
}
}
}
///
/// Parses the identifier list where the types may be optionally specified.
///
pub fn parse_typed_list(
lexer: &mut Lexer,
mut initial: Option<Token>,
) -> Result<(Vec<Self>, Option<Token>), Error> {
let mut result = Vec::new();
let mut expected_comma = false;
loop {
let token = crate::yul::parser::take_or_next(initial.take(), lexer)?;
match token {
Token {
lexeme: Lexeme::Identifier(identifier),
location,
..
} if !expected_comma => {
let r#type = match lexer.peek()? {
Token {
lexeme: Lexeme::Symbol(Symbol::Colon),
..
} => {
lexer.next()?;
Some(Type::parse(lexer, None)?)
}
_ => None,
};
result.push(Self::new_with_type(location, identifier.inner, r#type));
expected_comma = true;
}
Token {
lexeme: Lexeme::Symbol(Symbol::Comma),
..
} if expected_comma => {
expected_comma = false;
}
token => return Ok((result, Some(token))),
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
//!
//! The YUL code block.
//!
pub mod error;
pub mod identifier;
pub mod statement;
pub mod r#type;
use crate::yul::lexer::error::Error as LexerError;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
///
/// Returns the `token` value if it is `Some(_)`, otherwise takes the next token from the `stream`.
///
pub fn take_or_next(mut token: Option<Token>, lexer: &mut Lexer) -> Result<Token, LexerError> {
match token.take() {
Some(token) => Ok(token),
None => lexer.next(),
}
}
@@ -0,0 +1,185 @@
//!
//! The assignment expression statement.
//!
use std::collections::HashSet;
use inkwell::types::BasicType;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::identifier::Identifier;
use crate::yul::parser::statement::expression::Expression;
///
/// The Yul assignment expression statement.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Assignment {
/// The location.
pub location: Location,
/// The variable bindings.
pub bindings: Vec<Identifier>,
/// The initializing expression.
pub initializer: Expression,
}
impl Assignment {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let (location, identifier) = match token {
Token {
location,
lexeme: Lexeme::Identifier(identifier),
..
} => (location, identifier),
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{identifier}"],
found: token.lexeme.to_string(),
}
.into());
}
};
let length = identifier.inner.len();
match lexer.peek()? {
Token {
lexeme: Lexeme::Symbol(Symbol::Assignment),
..
} => {
lexer.next()?;
Ok(Self {
location,
bindings: vec![Identifier::new(location, identifier.inner)],
initializer: Expression::parse(lexer, None)?,
})
}
Token {
lexeme: Lexeme::Symbol(Symbol::Comma),
..
} => {
let (identifiers, next) = Identifier::parse_list(
lexer,
Some(Token::new(location, Lexeme::Identifier(identifier), length)),
)?;
match crate::yul::parser::take_or_next(next, lexer)? {
Token {
lexeme: Lexeme::Symbol(Symbol::Assignment),
..
} => {}
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec![":="],
found: token.lexeme.to_string(),
}
.into());
}
}
Ok(Self {
location,
bindings: identifiers,
initializer: Expression::parse(lexer, None)?,
})
}
token => Err(ParserError::InvalidToken {
location: token.location,
expected: vec![":=", ","],
found: token.lexeme.to_string(),
}
.into()),
}
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
self.initializer.get_missing_libraries()
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Assignment
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn into_llvm(
mut self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
let value = match self.initializer.into_llvm(context)? {
Some(value) => value,
None => return Ok(()),
};
if self.bindings.len() == 1 {
let identifier = self.bindings.remove(0);
let pointer = context
.current_function()
.borrow()
.get_stack_pointer(identifier.inner.as_str())
.ok_or_else(|| {
anyhow::anyhow!(
"{} Assignment to an undeclared variable `{}`",
identifier.location,
identifier.inner,
)
})?;
context.build_store(pointer, value.to_llvm())?;
return Ok(());
}
let llvm_type = value.to_llvm().into_struct_value().get_type();
let tuple_pointer = context.build_alloca(llvm_type, "assignment_pointer");
context.build_store(tuple_pointer, value.to_llvm())?;
for (index, binding) in self.bindings.into_iter().enumerate() {
let field_pointer = context.build_gep(
tuple_pointer,
&[
context.field_const(0),
context
.integer_type(era_compiler_common::BIT_LENGTH_X32)
.const_int(index as u64, false),
],
context.field_type().as_basic_type_enum(),
format!("assignment_binding_{index}_gep_pointer").as_str(),
);
let binding_pointer = context
.current_function()
.borrow()
.get_stack_pointer(binding.inner.as_str())
.ok_or_else(|| {
anyhow::anyhow!(
"{} Assignment to an undeclared variable `{}`",
binding.location,
binding.inner,
)
})?;
let value = context.build_load(
field_pointer,
format!("assignment_binding_{index}_value").as_str(),
)?;
context.build_store(binding_pointer, value)?;
}
Ok(())
}
}
@@ -0,0 +1,285 @@
//!
//! The source code block.
//!
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::statement::assignment::Assignment;
use crate::yul::parser::statement::expression::Expression;
use crate::yul::parser::statement::Statement;
///
/// The Yul source code block.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Block {
/// The location.
pub location: Location,
/// The block statements.
pub statements: Vec<Statement>,
}
impl Block {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let mut statements = Vec::new();
let location = match token {
Token {
lexeme: Lexeme::Symbol(Symbol::BracketCurlyLeft),
location,
..
} => location,
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{"],
found: token.lexeme.to_string(),
}
.into());
}
};
let mut remaining = None;
loop {
match crate::yul::parser::take_or_next(remaining.take(), lexer)? {
token @ Token {
lexeme: Lexeme::Keyword(_),
..
} => {
let (statement, next) = Statement::parse(lexer, Some(token))?;
remaining = next;
statements.push(statement);
}
token @ Token {
lexeme: Lexeme::Literal(_),
..
} => {
statements
.push(Expression::parse(lexer, Some(token)).map(Statement::Expression)?);
}
token @ Token {
lexeme: Lexeme::Identifier(_),
..
} => match lexer.peek()? {
Token {
lexeme: Lexeme::Symbol(Symbol::Assignment),
..
} => {
statements.push(
Assignment::parse(lexer, Some(token)).map(Statement::Assignment)?,
);
}
Token {
lexeme: Lexeme::Symbol(Symbol::Comma),
..
} => {
statements.push(
Assignment::parse(lexer, Some(token)).map(Statement::Assignment)?,
);
}
_ => {
statements.push(
Expression::parse(lexer, Some(token)).map(Statement::Expression)?,
);
}
},
token @ Token {
lexeme: Lexeme::Symbol(Symbol::BracketCurlyLeft),
..
} => statements.push(Block::parse(lexer, Some(token)).map(Statement::Block)?),
Token {
lexeme: Lexeme::Symbol(Symbol::BracketCurlyRight),
..
} => break,
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{keyword}", "{expression}", "{identifier}", "{", "}"],
found: token.lexeme.to_string(),
}
.into());
}
}
}
Ok(Self {
location,
statements,
})
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
let mut libraries = HashSet::new();
for statement in self.statements.iter() {
libraries.extend(statement.get_missing_libraries());
}
libraries
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Block
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn into_llvm(
self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
let current_function = context.current_function().borrow().name().to_owned();
let current_block = context.basic_block();
let mut functions = Vec::with_capacity(self.statements.len());
let mut local_statements = Vec::with_capacity(self.statements.len());
for statement in self.statements.into_iter() {
match statement {
Statement::FunctionDefinition(mut statement) => {
statement.declare(context)?;
functions.push(statement);
}
statement => local_statements.push(statement),
}
}
for function in functions.into_iter() {
function.into_llvm(context)?;
}
context.set_current_function(current_function.as_str())?;
context.set_basic_block(current_block);
for statement in local_statements.into_iter() {
if context.basic_block().get_terminator().is_some() {
break;
}
match statement {
Statement::Block(block) => {
block.into_llvm(context)?;
}
Statement::Expression(expression) => {
expression.into_llvm(context)?;
}
Statement::VariableDeclaration(statement) => statement.into_llvm(context)?,
Statement::Assignment(statement) => statement.into_llvm(context)?,
Statement::IfConditional(statement) => statement.into_llvm(context)?,
Statement::Switch(statement) => statement.into_llvm(context)?,
Statement::ForLoop(statement) => statement.into_llvm(context)?,
Statement::Continue(_location) => {
context.build_unconditional_branch(context.r#loop().continue_block);
break;
}
Statement::Break(_location) => {
context.build_unconditional_branch(context.r#loop().join_block);
break;
}
Statement::Leave(_location) => {
context.build_unconditional_branch(
context.current_function().borrow().return_block(),
);
break;
}
statement => anyhow::bail!(
"{} Unexpected local statement: {:?}",
statement.location(),
statement
),
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error;
use crate::yul::parser::statement::object::Object;
#[test]
fn error_invalid_token_bracket_curly_left() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
(
return(0, 0)
}
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(11, 17),
expected: vec!["{keyword}", "{expression}", "{identifier}", "{", "}"],
found: "(".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_token_statement() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
:=
return(0, 0)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(11, 17),
expected: vec!["{keyword}", "{expression}", "{identifier}", "{", "}"],
found: ":=".to_owned(),
}
.into())
);
}
}
@@ -0,0 +1,118 @@
//!
//! The YUL code.
//!
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::keyword::Keyword;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::statement::block::Block;
///
/// The YUL code entity, which is the first block of the object.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Code {
/// The location.
pub location: Location,
/// The main block.
pub block: Block,
}
impl Code {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let location = match token {
Token {
lexeme: Lexeme::Keyword(Keyword::Code),
location,
..
} => location,
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["code"],
found: token.lexeme.to_string(),
}
.into());
}
};
let block = Block::parse(lexer, None)?;
Ok(Self { location, block })
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
self.block.get_missing_libraries()
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Code
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn into_llvm(
self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
self.block.into_llvm(context)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error;
use crate::yul::parser::statement::object::Object;
#[test]
fn error_invalid_token_code() {
let input = r#"
object "Test" {
data {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(3, 5),
expected: vec!["code"],
found: "data".to_owned(),
}
.into())
);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,491 @@
//!
//! The function name.
//!
use serde::Deserialize;
use serde::Serialize;
///
/// The function name.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Name {
/// The user-defined function.
UserDefined(String),
/// `x + y`
Add,
/// `x - y`
Sub,
/// `x * y`
Mul,
/// `x / y` or `0` if `y == 0`
Div,
/// `x % y` or `0` if `y == 0`
Mod,
/// `x / y`, for signed numbers in twos complement, `0` if `y == 0`
Sdiv,
/// `x % y`, for signed numbers in twos complement, `0` if `y == 0`
Smod,
/// `1` if `x < y`, `0` otherwise
Lt,
/// `1` if `x > y`, `0` otherwise
Gt,
/// `1` if `x == y`, `0` otherwise
Eq,
/// `1` if `x == 0`, `0` otherwise
IsZero,
/// `1` if `x < y`, `0` otherwise, for signed numbers in twos complement
Slt,
/// `1` if `x > y`, `0` otherwise, for signed numbers in twos complement
Sgt,
/// bitwise "or" of `x` and `y`
Or,
/// bitwise "xor" of `x` and `y`
Xor,
/// bitwise "not" of `x` (every bit of `x` is negated)
Not,
/// bitwise "and" of `x` and `y`
And,
/// logical shift left `y` by `x` bits
Shl,
/// logical shift right `y` by `x` bits
Shr,
/// signed arithmetic shift right `y` by `x` bits
Sar,
/// `n`th byte of `x`, where the most significant byte is the `0`th byte
Byte,
/// discard value x
Pop,
/// `(x + y) % m` with arbitrary precision arithmetic, `0` if `m == 0`
AddMod,
/// `(x * y) % m` with arbitrary precision arithmetic, `0` if `m == 0`
MulMod,
/// `x` to the power of `y`
Exp,
/// sign extend from `(i*8+7)`th bit counting from least significant
SignExtend,
/// `keccak(mem[p…(p+n)))`
Keccak256,
/// `mem[p…(p+32))`
MLoad,
/// `mem[p…(p+32)) := v`
MStore,
/// `mem[p] := v & 0xff` (only modifies a single byte)
MStore8,
/// heap memory copy
MCopy,
/// `storage[p]`
SLoad,
/// `storage[p] := v`
SStore,
/// transient `storage[p]`
TLoad,
/// transient `storage[p] := v`
TStore,
/// `loadimmutable` storage read
LoadImmutable,
/// `setimmutable` storage write
SetImmutable,
/// call data starting from position `p` (32 bytes)
CallDataLoad,
/// size of call data in bytes
CallDataSize,
/// copy `s` bytes from calldata at position `f` to memory at position `t`
CallDataCopy,
/// size of the code of the current contract / execution context
CodeSize,
/// copy `s` bytes from code at position `f` to mem at position `t`
CodeCopy,
/// size of the code at address `a`
ExtCodeSize,
/// code hash of address `a`
ExtCodeHash,
/// size of the last returndata
ReturnDataSize,
/// copy `s` bytes from returndata at position `f` to mem at position `t`
ReturnDataCopy,
/// end execution, return data `mem[p…(p+s))`
Return,
/// end execution, revert state changes, return data `mem[p…(p+s))`
Revert,
/// stop execution, identical to `return(0, 0)`
Stop,
/// end execution with invalid instruction
Invalid,
/// log without topics and data `mem[p…(p+s))`
Log0,
/// log with topic t1 and data `mem[p…(p+s))`
Log1,
/// log with topics t1, t2 and data `mem[p…(p+s))`
Log2,
/// log with topics t1, t2, t3 and data `mem[p…(p+s))`
Log3,
/// log with topics t1, t2, t3, t4 and data `mem[p…(p+s))`
Log4,
/// call contract at address a with input `mem[in…(in+insize))` providing `g` gas and `v` wei
/// and output area `mem[out…(out+outsize))` returning 0 on error (e.g. out of gas)
/// and 1 on success
/// [See more](https://docs.soliditylang.org/en/v0.8.2/yul.html#yul-call-return-area)
Call,
/// identical to call but only use the code from a and stay in the context of the current
/// contract otherwise
CallCode,
/// identical to `callcode` but also keeps `caller` and `callvalue`
DelegateCall,
/// identical to `call(g, a, 0, in, insize, out, outsize)` but do not allows state modifications
StaticCall,
/// create new contract with code `mem[p…(p+n))` and send `v` wei and return the new address
///
/// Passes bytecode to the system contracts.
Create,
/// create new contract with code `mem[p…(p+n))` at address
/// `keccak256(0xff . this . s . keccak256(mem[p…(p+n)))` and send `v` wei and return the
/// new address, where `0xff` is a 1-byte value, this is the current contracts address as a
/// 20-byte value and `s` is a big-endian 256-bit value
///
/// Passes bytecode to the system contracts.
Create2,
/// create new contract with code `mem[p…(p+n))` and send `v` wei and return the new address
///
/// Passes hash to the system contracts.
ZkCreate,
/// create new contract with code `mem[p…(p+n))` at address
/// `keccak256(0xff . this . s . keccak256(mem[p…(p+n)))` and send `v` wei and return the
/// new address, where `0xff` is a 1-byte value, this is the current contracts address as a
/// 20-byte value and `s` is a big-endian 256-bit value
///
/// Passes hash to the system contracts.
ZkCreate2,
/// returns the size in the data area
DataSize,
/// is equivalent to `CodeCopy`
DataCopy,
/// returns the offset in the data area
DataOffset,
/// `linkersymbol` is a stub call
LinkerSymbol,
/// `memoryguard` is a stub call
MemoryGuard,
/// address of the current contract / execution context
Address,
/// call sender (excluding `delegatecall`)
Caller,
/// wei sent together with the current call
CallValue,
/// gas still available to execution
Gas,
/// wei balance at address `a`
Balance,
/// equivalent to `balance(address())`, but cheaper
SelfBalance,
/// block gas limit of the current block
GasLimit,
/// gas price of the transaction
GasPrice,
/// transaction sender
Origin,
/// ID of the executing chain (EIP 1344)
ChainId,
/// current block number
Number,
/// timestamp of the current block in seconds since the epoch
Timestamp,
/// hash of block nr b - only for last 256 blocks excluding current
BlockHash,
/// versioned hash of transactions i-th blob
BlobHash,
/// difficulty of the current block
Difficulty,
/// https://eips.ethereum.org/EIPS/eip-4399
Prevrandao,
/// current mining beneficiary
CoinBase,
/// size of memory, i.e. largest accessed memory index
MSize,
/// verbatim instruction with 0 inputs and 0 outputs
/// only works in the Yul mode, so it is mostly used as a tool for extending Yul for zkSync
Verbatim {
/// the number of input arguments
input_size: usize,
/// the number of output arguments
output_size: usize,
},
/// current blocks base fee (EIP-3198 and EIP-1559)
BaseFee,
/// current blocks blob base fee (EIP-7516 and EIP-4844)
BlobBaseFee,
/// current position in code
Pc,
/// like `codecopy(t, f, s)` but take code at address `a`
ExtCodeCopy,
/// end execution, destroy current contract and send funds to `a`
SelfDestruct,
/// The eponymous EraVM Yul extension instruction.
ZkToL1,
/// The eponymous EraVM Yul extension instruction.
ZkCodeSource,
/// The eponymous EraVM Yul extension instruction.
ZkPrecompile,
/// The eponymous EraVM Yul extension instruction.
ZkMeta,
/// The eponymous EraVM Yul extension instruction.
ZkSetContextU128,
/// The eponymous EraVM Yul extension instruction.
ZkSetPubdataPrice,
/// The eponymous EraVM Yul extension instruction.
ZkIncrementTxCounter,
/// The eponymous EraVM Yul extension instruction.
ZkEventInitialize,
/// The eponymous EraVM Yul extension instruction.
ZkEventWrite,
/// The eponymous EraVM Yul extension instruction.
ZkMimicCall,
/// The eponymous EraVM Yul extension instruction.
ZkSystemMimicCall,
/// The eponymous EraVM Yul extension instruction.
ZkMimicCallByRef,
/// The eponymous EraVM Yul extension instruction.
ZkSystemMimicCallByRef,
/// The eponymous EraVM Yul extension instruction.
ZkRawCall,
/// The eponymous EraVM Yul extension instruction.
ZkRawCallByRef,
/// The eponymous EraVM Yul extension instruction.
ZkSystemCall,
/// The eponymous EraVM Yul extension instruction.
ZkSystemCallByRef,
/// The eponymous EraVM Yul extension instruction.
ZkStaticRawCall,
/// The eponymous EraVM Yul extension instruction.
ZkStaticRawCallByRef,
/// The eponymous EraVM Yul extension instruction.
ZkStaticSystemCall,
/// The eponymous EraVM Yul extension instruction.
ZkStaticSystemCallByRef,
/// The eponymous EraVM Yul extension instruction.
ZkDelegateRawCall,
/// The eponymous EraVM Yul extension instruction.
ZkDelegateRawCallByRef,
/// The eponymous EraVM Yul extension instruction.
ZkDelegateSystemCall,
/// The eponymous EraVM Yul extension instruction.
ZkDelegateSystemCallByRef,
/// The eponymous EraVM Yul extension instruction.
ZkLoadCalldataIntoActivePtr,
/// The eponymous EraVM Yul extension instruction.
ZkLoadReturndataIntoActivePtr,
/// The eponymous EraVM Yul extension instruction.
ZkPtrAddIntoActive,
/// The eponymous EraVM Yul extension instruction.
ZkPtrShrinkIntoActive,
/// The eponymous EraVM Yul extension instruction.
ZkPtrPackIntoActive,
/// The eponymous EraVM Yul extension instruction.
ZkMultiplicationHigh,
/// The eponymous EraVM Yul extension instruction.
ZkGlobalLoad,
/// The eponymous EraVM Yul extension instruction.
ZkGlobalExtraAbiData,
/// The eponymous EraVM Yul extension instruction.
ZkGlobalStore,
}
impl Name {
///
/// Tries parsing the verbatim instruction.
///
fn parse_verbatim(input: &str) -> Option<Self> {
let verbatim = input.strip_prefix("verbatim")?;
let regex = regex::Regex::new(r"_(\d+)i_(\d+)o").expect("Always valid");
let captures = regex.captures(verbatim)?;
let input_size: usize = captures.get(1)?.as_str().parse().ok()?;
let output_size: usize = captures.get(2)?.as_str().parse().ok()?;
Some(Self::Verbatim {
input_size,
output_size,
})
}
}
impl From<&str> for Name {
fn from(input: &str) -> Self {
if let Some(verbatim) = Self::parse_verbatim(input) {
return verbatim;
}
match input {
"add" => Self::Add,
"sub" => Self::Sub,
"mul" => Self::Mul,
"div" => Self::Div,
"mod" => Self::Mod,
"sdiv" => Self::Sdiv,
"smod" => Self::Smod,
"lt" => Self::Lt,
"gt" => Self::Gt,
"eq" => Self::Eq,
"iszero" => Self::IsZero,
"slt" => Self::Slt,
"sgt" => Self::Sgt,
"or" => Self::Or,
"xor" => Self::Xor,
"not" => Self::Not,
"and" => Self::And,
"shl" => Self::Shl,
"shr" => Self::Shr,
"sar" => Self::Sar,
"byte" => Self::Byte,
"pop" => Self::Pop,
"addmod" => Self::AddMod,
"mulmod" => Self::MulMod,
"exp" => Self::Exp,
"signextend" => Self::SignExtend,
"keccak256" => Self::Keccak256,
"mload" => Self::MLoad,
"mstore" => Self::MStore,
"mstore8" => Self::MStore8,
"mcopy" => Self::MCopy,
"sload" => Self::SLoad,
"sstore" => Self::SStore,
"tload" => Self::TLoad,
"tstore" => Self::TStore,
"loadimmutable" => Self::LoadImmutable,
"setimmutable" => Self::SetImmutable,
"calldataload" => Self::CallDataLoad,
"calldatasize" => Self::CallDataSize,
"calldatacopy" => Self::CallDataCopy,
"codesize" => Self::CodeSize,
"codecopy" => Self::CodeCopy,
"returndatasize" => Self::ReturnDataSize,
"returndatacopy" => Self::ReturnDataCopy,
"extcodesize" => Self::ExtCodeSize,
"extcodehash" => Self::ExtCodeHash,
"return" => Self::Return,
"revert" => Self::Revert,
"log0" => Self::Log0,
"log1" => Self::Log1,
"log2" => Self::Log2,
"log3" => Self::Log3,
"log4" => Self::Log4,
"call" => Self::Call,
"delegatecall" => Self::DelegateCall,
"staticcall" => Self::StaticCall,
"create" => Self::Create,
"create2" => Self::Create2,
"$zk_create" => Self::ZkCreate,
"$zk_create2" => Self::ZkCreate2,
"datasize" => Self::DataSize,
"dataoffset" => Self::DataOffset,
"datacopy" => Self::DataCopy,
"stop" => Self::Stop,
"invalid" => Self::Invalid,
"linkersymbol" => Self::LinkerSymbol,
"memoryguard" => Self::MemoryGuard,
"address" => Self::Address,
"caller" => Self::Caller,
"callvalue" => Self::CallValue,
"gas" => Self::Gas,
"balance" => Self::Balance,
"selfbalance" => Self::SelfBalance,
"gaslimit" => Self::GasLimit,
"gasprice" => Self::GasPrice,
"origin" => Self::Origin,
"chainid" => Self::ChainId,
"timestamp" => Self::Timestamp,
"number" => Self::Number,
"blockhash" => Self::BlockHash,
"blobhash" => Self::BlobHash,
"difficulty" => Self::Difficulty,
"prevrandao" => Self::Prevrandao,
"coinbase" => Self::CoinBase,
"basefee" => Self::BaseFee,
"blobbasefee" => Self::BlobBaseFee,
"msize" => Self::MSize,
"callcode" => Self::CallCode,
"pc" => Self::Pc,
"extcodecopy" => Self::ExtCodeCopy,
"selfdestruct" => Self::SelfDestruct,
"$zk_to_l1" => Self::ZkToL1,
"$zk_code_source" => Self::ZkCodeSource,
"$zk_precompile" => Self::ZkPrecompile,
"$zk_meta" => Self::ZkMeta,
"$zk_set_context_u128" => Self::ZkSetContextU128,
"$zk_set_pubdata_price" => Self::ZkSetPubdataPrice,
"$zk_increment_tx_counter" => Self::ZkIncrementTxCounter,
"$zk_event_initialize" => Self::ZkEventInitialize,
"$zk_event_write" => Self::ZkEventWrite,
"$zk_mimic_call" => Self::ZkMimicCall,
"$zk_system_mimic_call" => Self::ZkSystemMimicCall,
"$zk_mimic_call_byref" => Self::ZkMimicCallByRef,
"$zk_system_mimic_call_byref" => Self::ZkSystemMimicCallByRef,
"$zk_raw_call" => Self::ZkRawCall,
"$zk_raw_call_byref" => Self::ZkRawCallByRef,
"$zk_system_call" => Self::ZkSystemCall,
"$zk_system_call_byref" => Self::ZkSystemCallByRef,
"$zk_static_raw_call" => Self::ZkStaticRawCall,
"$zk_static_raw_call_byref" => Self::ZkStaticRawCallByRef,
"$zk_static_system_call" => Self::ZkStaticSystemCall,
"$zk_static_system_call_byref" => Self::ZkStaticSystemCallByRef,
"$zk_delegate_raw_call" => Self::ZkDelegateRawCall,
"$zk_delegate_raw_call_byref" => Self::ZkDelegateRawCallByRef,
"$zk_delegate_system_call" => Self::ZkDelegateSystemCall,
"$zk_delegate_system_call_byref" => Self::ZkDelegateSystemCallByRef,
"$zk_load_calldata_into_active_ptr" => Self::ZkLoadCalldataIntoActivePtr,
"$zk_load_returndata_into_active_ptr" => Self::ZkLoadReturndataIntoActivePtr,
"$zk_ptr_add_into_active" => Self::ZkPtrAddIntoActive,
"$zk_ptr_shrink_into_active" => Self::ZkPtrShrinkIntoActive,
"$zk_ptr_pack_into_active" => Self::ZkPtrPackIntoActive,
"$zk_multiplication_high" => Self::ZkMultiplicationHigh,
"$zk_global_load" => Self::ZkGlobalLoad,
"$zk_global_extra_abi_data" => Self::ZkGlobalExtraAbiData,
"$zk_global_store" => Self::ZkGlobalStore,
input => Self::UserDefined(input.to_owned()),
}
}
}
@@ -0,0 +1,841 @@
//!
//! Translates the verbatim simulations.
//!
use anyhow::Ok;
use crate::yul::parser::statement::expression::function_call::FunctionCall;
///
/// Translates the verbatim simulations.
///
pub fn verbatim<'ctx, D>(
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
call: &mut FunctionCall,
input_size: usize,
output_size: usize,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>>
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
if output_size > 1 {
anyhow::bail!(
"{} Verbatim instructions with multiple return values are not supported",
call.location
);
}
let mut arguments = call.pop_arguments::<D, 1>(context)?;
let identifier = arguments[0]
.original
.take()
.ok_or_else(|| anyhow::anyhow!("{} Verbatim literal is missing", call.location))?;
match identifier.as_str() {
identifier @ "to_l1" => {
const ARGUMENTS_COUNT: usize = 3;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_general::to_l1(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
arguments[2].into_int_value(),
)
.map(Some)
}
identifier @ "code_source" => {
const ARGUMENTS_COUNT: usize = 0;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
era_compiler_llvm_context::eravm_general::code_source(context).map(Some)
}
identifier @ "precompile" => {
const ARGUMENTS_COUNT: usize = 2;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_general::precompile(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
)
.map(Some)
}
identifier @ "meta" => {
const ARGUMENTS_COUNT: usize = 0;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
era_compiler_llvm_context::eravm_general::meta(context).map(Some)
}
identifier @ "mimic_call" => {
const ARGUMENTS_COUNT: usize = 3;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::mimic(
context,
context.llvm_runtime().mimic_call,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
arguments[2],
vec![context.field_const(0), context.field_const(0)],
)
.map(Some)
}
identifier @ "mimic_call_byref" => {
const ARGUMENTS_COUNT: usize = 2;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::mimic(
context,
context.llvm_runtime().mimic_call_byref,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
context.get_global_value(
era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER,
)?,
vec![context.field_const(0), context.field_const(0)],
)
.map(Some)
}
identifier @ "system_mimic_call" => {
const ARGUMENTS_COUNT: usize = 7;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::mimic(
context,
context.llvm_runtime().mimic_call,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
arguments[2],
vec![
arguments[3].into_int_value(),
arguments[4].into_int_value(),
arguments[5].into_int_value(),
arguments[6].into_int_value(),
],
)
.map(Some)
}
identifier @ "system_mimic_call_byref" => {
const ARGUMENTS_COUNT: usize = 6;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::mimic(
context,
context.llvm_runtime().mimic_call_byref,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
context.get_global_value(
era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER,
)?,
vec![
arguments[2].into_int_value(),
arguments[3].into_int_value(),
arguments[4].into_int_value(),
arguments[5].into_int_value(),
],
)
.map(Some)
}
identifier @ "raw_call" => {
const ARGUMENTS_COUNT: usize = 4;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
todo!()
//era_compiler_llvm_context::eravm_call::raw_far(
// context,
// context.llvm_runtime().far_call,
// arguments[0].into_int_value(),
// arguments[1],
// arguments[2].into_int_value(),
// arguments[3].into_int_value(),
//)
//.map(Some)
}
identifier @ "raw_call_byref" => {
const ARGUMENTS_COUNT: usize = 3;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::raw_far(
context,
context.llvm_runtime().far_call_byref,
arguments[0].into_int_value(),
context.get_global_value(
era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER,
)?,
arguments[1].into_int_value(),
arguments[2].into_int_value(),
)
.map(Some)
}
identifier @ "system_call" => {
unimplemented!()
}
identifier @ "system_call_byref" => {
const ARGUMENTS_COUNT: usize = 5;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::system(
context,
context.llvm_runtime().far_call_byref,
arguments[0].into_int_value(),
context.get_global_value(
era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER,
)?,
context.field_const(0),
context.field_const(0),
vec![
arguments[1].into_int_value(),
arguments[2].into_int_value(),
arguments[3].into_int_value(),
arguments[4].into_int_value(),
],
)
.map(Some)
}
identifier @ "raw_static_call" => {
const ARGUMENTS_COUNT: usize = 4;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::raw_far(
context,
context.llvm_runtime().static_call,
arguments[0].into_int_value(),
arguments[1],
arguments[2].into_int_value(),
arguments[3].into_int_value(),
)
.map(Some)
}
identifier @ "raw_static_call_byref" => {
const ARGUMENTS_COUNT: usize = 3;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::raw_far(
context,
context.llvm_runtime().static_call_byref,
arguments[0].into_int_value(),
context.get_global_value(
era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER,
)?,
arguments[1].into_int_value(),
arguments[2].into_int_value(),
)
.map(Some)
}
identifier @ "system_static_call" => {
const ARGUMENTS_COUNT: usize = 6;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::system(
context,
context.llvm_runtime().static_call,
arguments[0].into_int_value(),
arguments[1],
arguments[4].into_int_value(),
arguments[5].into_int_value(),
vec![arguments[2].into_int_value(), arguments[3].into_int_value()],
)
.map(Some)
}
identifier @ "system_static_call_byref" => {
const ARGUMENTS_COUNT: usize = 5;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::system(
context,
context.llvm_runtime().static_call_byref,
arguments[0].into_int_value(),
context.get_global_value(
era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER,
)?,
arguments[3].into_int_value(),
arguments[4].into_int_value(),
vec![arguments[1].into_int_value(), arguments[2].into_int_value()],
)
.map(Some)
}
identifier @ "raw_delegate_call" => {
const ARGUMENTS_COUNT: usize = 4;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::raw_far(
context,
context.llvm_runtime().delegate_call,
arguments[0].into_int_value(),
arguments[1],
arguments[2].into_int_value(),
arguments[3].into_int_value(),
)
.map(Some)
}
identifier @ "raw_delegate_call_byref" => {
const ARGUMENTS_COUNT: usize = 3;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::raw_far(
context,
context.llvm_runtime().delegate_call_byref,
arguments[0].into_int_value(),
context.get_global_value(
era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER,
)?,
arguments[1].into_int_value(),
arguments[2].into_int_value(),
)
.map(Some)
}
identifier @ "system_delegate_call" => {
const ARGUMENTS_COUNT: usize = 6;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::system(
context,
context.llvm_runtime().delegate_call,
arguments[0].into_int_value(),
arguments[1],
arguments[4].into_int_value(),
arguments[5].into_int_value(),
vec![arguments[2].into_int_value(), arguments[3].into_int_value()],
)
.map(Some)
}
identifier @ "system_delegate_call_byref" => {
const ARGUMENTS_COUNT: usize = 5;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_call::system(
context,
context.llvm_runtime().delegate_call_byref,
arguments[0].into_int_value(),
context.get_global_value(
era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER,
)?,
arguments[3].into_int_value(),
arguments[4].into_int_value(),
vec![arguments[1].into_int_value(), arguments[2].into_int_value()],
)
.map(Some)
}
identifier @ "set_context_u128" => {
const ARGUMENTS_COUNT: usize = 1;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_general::set_context_value(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
identifier @ "set_pubdata_price" => {
const ARGUMENTS_COUNT: usize = 1;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_general::set_pubdata_price(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
identifier @ "increment_tx_counter" => {
const ARGUMENTS_COUNT: usize = 0;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
era_compiler_llvm_context::eravm_general::increment_tx_counter(context).map(Some)
}
identifier @ "event_initialize" => {
const ARGUMENTS_COUNT: usize = 2;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_general::event(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
true,
)
.map(Some)
}
identifier @ "event_write" => {
const ARGUMENTS_COUNT: usize = 2;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_general::event(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
false,
)
.map(Some)
}
identifier @ "calldata_ptr_to_active" => {
const ARGUMENTS_COUNT: usize = 0;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
era_compiler_llvm_context::eravm_abi::calldata_ptr_to_active(context).map(Some)
}
identifier @ "return_data_ptr_to_active" => {
const ARGUMENTS_COUNT: usize = 0;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
era_compiler_llvm_context::eravm_abi::return_data_ptr_to_active(context).map(Some)
}
identifier @ "active_ptr_add_assign" => {
const ARGUMENTS_COUNT: usize = 1;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_abi::active_ptr_add_assign(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
identifier @ "active_ptr_shrink_assign" => {
const ARGUMENTS_COUNT: usize = 1;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_abi::active_ptr_shrink_assign(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
identifier @ "active_ptr_pack_assign" => {
const ARGUMENTS_COUNT: usize = 1;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_abi::active_ptr_pack_assign(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
identifier @ "mul_high" => {
const ARGUMENTS_COUNT: usize = 2;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_math::multiplication_512(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
)
.map(Some)
}
identifier @ "throw" => {
const ARGUMENTS_COUNT: usize = 0;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
era_compiler_llvm_context::eravm_utils::throw(context);
Ok(None)
}
identifier
if identifier.starts_with(
era_compiler_llvm_context::eravm_const::GLOBAL_VERBATIM_GETTER_PREFIX,
) =>
{
const ARGUMENTS_COUNT: usize = 0;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
match identifier
.strip_prefix(era_compiler_llvm_context::eravm_const::GLOBAL_VERBATIM_GETTER_PREFIX)
{
Some(identifier)
if identifier
== era_compiler_llvm_context::eravm_const::GLOBAL_CALLDATA_POINTER =>
{
context.get_global_value(identifier).map(Some)
}
Some(identifier)
if identifier == era_compiler_llvm_context::eravm_const::GLOBAL_CALL_FLAGS =>
{
context.get_global_value(identifier).map(Some)
}
Some(identifier)
if identifier
== era_compiler_llvm_context::eravm_const::GLOBAL_RETURN_DATA_POINTER =>
{
context.get_global_value(identifier).map(Some)
}
Some(identifier)
if identifier.starts_with(
era_compiler_llvm_context::eravm_const::GLOBAL_EXTRA_ABI_DATA,
) =>
{
let stripped = identifier
.strip_prefix(era_compiler_llvm_context::eravm_const::GLOBAL_EXTRA_ABI_DATA)
.expect("Always exists");
let stripped = stripped.strip_prefix('_').ok_or_else(|| {
anyhow::anyhow!(
"{} Invalid global variable identifier `{:?}`",
call.location,
identifier
)
})?;
let index = stripped.parse::<u64>().map_err(|error| {
anyhow::anyhow!(
"{} Invalid global variable identifier `{:?}`: {}",
call.location,
identifier,
error,
)
})?;
if index >= (era_compiler_llvm_context::eravm_const::EXTRA_ABI_DATA_SIZE as u64)
{
anyhow::bail!(
"{} Extra ABI data overflow. Only indexes `0..=9` are allowed",
call.location,
);
}
era_compiler_llvm_context::eravm_abi::get_extra_abi_data(
context,
context.field_const(index),
)
.map(Some)
}
identifier => Err(anyhow::anyhow!(
"{} Invalid global variable identifier `{:?}`",
call.location,
identifier
)),
}
}
identifier @ "active_ptr_data_load" => {
const ARGUMENTS_COUNT: usize = 1;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_abi::active_ptr_data_load(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
identifier @ "active_ptr_data_size" => {
const ARGUMENTS_COUNT: usize = 0;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
era_compiler_llvm_context::eravm_abi::active_ptr_data_size(context).map(Some)
}
identifier @ "active_ptr_data_copy" => {
const ARGUMENTS_COUNT: usize = 3;
if input_size != ARGUMENTS_COUNT {
anyhow::bail!(
"{} Internal function `{}` expected {} arguments, found {}",
call.location,
identifier,
ARGUMENTS_COUNT,
input_size
);
}
let arguments = call.pop_arguments_llvm::<D, ARGUMENTS_COUNT>(context)?;
era_compiler_llvm_context::eravm_abi::active_ptr_data_copy(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
arguments[2].into_int_value(),
)
.map(|_| None)
}
identifier => anyhow::bail!(
"{} Found unknown internal function `{}`",
call.location,
identifier
),
}
}
@@ -0,0 +1,239 @@
//!
//! The YUL source code literal.
//!
use inkwell::values::BasicValue;
use num::Num;
use num::One;
use num::Zero;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::literal::boolean::Boolean as BooleanLiteral;
use crate::yul::lexer::token::lexeme::literal::integer::Integer as IntegerLiteral;
use crate::yul::lexer::token::lexeme::literal::Literal as LexicalLiteral;
use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::r#type::Type;
///
/// Represents a literal in YUL without differentiating its type.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Literal {
/// The location.
pub location: Location,
/// The lexical literal.
pub inner: LexicalLiteral,
/// The type, if it has been explicitly specified.
pub yul_type: Option<Type>,
}
impl Literal {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let (location, literal) = match token {
Token {
lexeme: Lexeme::Literal(literal),
location,
..
} => (location, literal),
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{literal}"],
found: token.lexeme.to_string(),
}
.into());
}
};
let yul_type = match lexer.peek()? {
Token {
lexeme: Lexeme::Symbol(Symbol::Colon),
..
} => {
lexer.next()?;
Some(Type::parse(lexer, None)?)
}
_ => None,
};
Ok(Self {
location,
inner: literal,
yul_type,
})
}
///
/// Converts the literal into its LLVM.
///
pub fn into_llvm<'ctx, D>(
self,
context: &era_compiler_llvm_context::EraVMContext<'ctx, D>,
) -> anyhow::Result<era_compiler_llvm_context::EraVMArgument<'ctx>>
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
match self.inner {
LexicalLiteral::Boolean(inner) => {
let value = self
.yul_type
.unwrap_or_default()
.into_llvm(context)
.const_int(
match inner {
BooleanLiteral::False => 0,
BooleanLiteral::True => 1,
},
false,
)
.as_basic_value_enum();
let constant = match inner {
BooleanLiteral::False => num::BigUint::zero(),
BooleanLiteral::True => num::BigUint::one(),
};
Ok(era_compiler_llvm_context::EraVMArgument::new_with_constant(
value, constant,
))
}
LexicalLiteral::Integer(inner) => {
let r#type = self.yul_type.unwrap_or_default().into_llvm(context);
let value = match inner {
IntegerLiteral::Decimal { ref inner } => r#type.const_int_from_string(
inner.as_str(),
inkwell::types::StringRadix::Decimal,
),
IntegerLiteral::Hexadecimal { ref inner } => r#type.const_int_from_string(
&inner["0x".len()..],
inkwell::types::StringRadix::Hexadecimal,
),
}
.expect("The value is valid")
.as_basic_value_enum();
let constant = match inner {
IntegerLiteral::Decimal { ref inner } => num::BigUint::from_str_radix(
inner.as_str(),
era_compiler_common::BASE_DECIMAL,
),
IntegerLiteral::Hexadecimal { ref inner } => num::BigUint::from_str_radix(
&inner["0x".len()..],
era_compiler_common::BASE_HEXADECIMAL,
),
}
.expect("Always valid");
Ok(era_compiler_llvm_context::EraVMArgument::new_with_constant(
value, constant,
))
}
LexicalLiteral::String(inner) => {
let string = inner.inner;
let r#type = self.yul_type.unwrap_or_default().into_llvm(context);
let mut hex_string = if inner.is_hexadecimal {
string.clone()
} else {
let mut hex_string =
String::with_capacity(era_compiler_common::BYTE_LENGTH_FIELD * 2);
let mut index = 0;
loop {
if index >= string.len() {
break;
}
if string[index..].starts_with('\\') {
index += 1;
if string[index..].starts_with('x') {
hex_string.push_str(&string[index + 1..index + 3]);
index += 3;
} else if string[index..].starts_with('u') {
let codepoint_str = &string[index + 1..index + 5];
let codepoint = u32::from_str_radix(
codepoint_str,
era_compiler_common::BASE_HEXADECIMAL,
)
.map_err(|error| {
anyhow::anyhow!(
"Invalid codepoint `{}`: {}",
codepoint_str,
error
)
})?;
let unicode_char = char::from_u32(codepoint).ok_or_else(|| {
anyhow::anyhow!("Invalid codepoint {}", codepoint)
})?;
let mut unicode_bytes = vec![0u8; 3];
unicode_char.encode_utf8(&mut unicode_bytes);
for byte in unicode_bytes.into_iter() {
hex_string.push_str(format!("{:02x}", byte).as_str());
}
index += 5;
} else if string[index..].starts_with('t') {
hex_string.push_str("09");
index += 1;
} else if string[index..].starts_with('n') {
hex_string.push_str("0a");
index += 1;
} else if string[index..].starts_with('r') {
hex_string.push_str("0d");
index += 1;
} else if string[index..].starts_with('\n') {
index += 1;
} else {
hex_string
.push_str(format!("{:02x}", string.as_bytes()[index]).as_str());
index += 1;
}
} else {
hex_string
.push_str(format!("{:02x}", string.as_bytes()[index]).as_str());
index += 1;
}
}
hex_string
};
if hex_string.len() > era_compiler_common::BYTE_LENGTH_FIELD * 2 {
return Ok(era_compiler_llvm_context::EraVMArgument::new_with_original(
r#type.const_zero().as_basic_value_enum(),
string,
));
}
if hex_string.len() < era_compiler_common::BYTE_LENGTH_FIELD * 2 {
hex_string.push_str(
"0".repeat((era_compiler_common::BYTE_LENGTH_FIELD * 2) - hex_string.len())
.as_str(),
);
}
let value = r#type
.const_int_from_string(
hex_string.as_str(),
inkwell::types::StringRadix::Hexadecimal,
)
.expect("The value is valid")
.as_basic_value_enum();
Ok(era_compiler_llvm_context::EraVMArgument::new_with_original(
value, string,
))
}
}
}
}
@@ -0,0 +1,164 @@
//!
//! The expression statement.
//!
pub mod function_call;
pub mod literal;
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::identifier::Identifier;
use self::function_call::FunctionCall;
use self::literal::Literal;
///
/// The Yul expression statement.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Expression {
/// The function call subexpression.
FunctionCall(FunctionCall),
/// The identifier operand.
Identifier(Identifier),
/// The literal operand.
Literal(Literal),
}
impl Expression {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let (location, identifier) = match token {
Token {
lexeme: Lexeme::Literal(_),
..
} => return Ok(Self::Literal(Literal::parse(lexer, Some(token))?)),
Token {
location,
lexeme: Lexeme::Identifier(identifier),
..
} => (location, identifier),
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{literal}", "{identifier}"],
found: token.lexeme.to_string(),
}
.into());
}
};
let length = identifier.inner.len();
match lexer.peek()? {
Token {
lexeme: Lexeme::Symbol(Symbol::ParenthesisLeft),
..
} => {
lexer.next()?;
Ok(Self::FunctionCall(FunctionCall::parse(
lexer,
Some(Token::new(location, Lexeme::Identifier(identifier), length)),
)?))
}
_ => Ok(Self::Identifier(Identifier::new(
location,
identifier.inner,
))),
}
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
match self {
Self::FunctionCall(inner) => inner.get_missing_libraries(),
Self::Identifier(_) => HashSet::new(),
Self::Literal(_) => HashSet::new(),
}
}
///
/// Returns the statement location.
///
pub fn location(&self) -> Location {
match self {
Self::FunctionCall(inner) => inner.location,
Self::Identifier(inner) => inner.location,
Self::Literal(inner) => inner.location,
}
}
///
/// Converts the expression into an LLVM value.
///
pub fn into_llvm<'ctx, D>(
self,
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
) -> anyhow::Result<Option<era_compiler_llvm_context::EraVMArgument<'ctx>>>
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
match self {
Self::Literal(literal) => literal
.clone()
.into_llvm(context)
.map_err(|error| {
anyhow::anyhow!(
"{} Invalid literal `{}`: {}",
literal.location,
literal.inner.to_string(),
error
)
})
.map(Some),
Self::Identifier(identifier) => {
let pointer = context
.current_function()
.borrow()
.get_stack_pointer(identifier.inner.as_str())
.ok_or_else(|| {
anyhow::anyhow!(
"{} Undeclared variable `{}`",
identifier.location,
identifier.inner,
)
})?;
let constant = context
.current_function()
.borrow()
.yul()
.get_constant(identifier.inner.as_str());
let value = context.build_load(pointer, identifier.inner.as_str())?;
match constant {
Some(constant) => Ok(Some(
era_compiler_llvm_context::EraVMArgument::new_with_constant(
value, constant,
),
)),
None => Ok(Some(value.into())),
}
}
Self::FunctionCall(call) => Ok(call
.into_llvm(context)?
.map(era_compiler_llvm_context::EraVMArgument::new)),
}
}
}
@@ -0,0 +1,122 @@
//!
//! The for-loop statement.
//!
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::statement::block::Block;
use crate::yul::parser::statement::expression::Expression;
///
/// The Yul for-loop statement.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct ForLoop {
/// The location.
pub location: Location,
/// The index variables initialization block.
pub initializer: Block,
/// The continue condition block.
pub condition: Expression,
/// The index variables mutating block.
pub finalizer: Block,
/// The loop body.
pub body: Block,
}
impl ForLoop {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let location = token.location;
let initializer = Block::parse(lexer, Some(token))?;
let condition = Expression::parse(lexer, None)?;
let finalizer = Block::parse(lexer, None)?;
let body = Block::parse(lexer, None)?;
Ok(Self {
location,
initializer,
condition,
finalizer,
body,
})
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
let mut libraries = self.initializer.get_missing_libraries();
libraries.extend(self.condition.get_missing_libraries());
libraries.extend(self.finalizer.get_missing_libraries());
libraries.extend(self.body.get_missing_libraries());
libraries
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for ForLoop
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn into_llvm(
self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
self.initializer.into_llvm(context)?;
let condition_block = context.append_basic_block("for_condition");
let body_block = context.append_basic_block("for_body");
let increment_block = context.append_basic_block("for_increment");
let join_block = context.append_basic_block("for_join");
context.build_unconditional_branch(condition_block);
context.set_basic_block(condition_block);
let condition = self
.condition
.into_llvm(context)?
.expect("Always exists")
.to_llvm()
.into_int_value();
let condition = context.builder().build_int_z_extend_or_bit_cast(
condition,
context.field_type(),
"for_condition_extended",
)?;
let condition = context.builder().build_int_compare(
inkwell::IntPredicate::NE,
condition,
context.field_const(0),
"for_condition_compared",
)?;
context.build_conditional_branch(condition, body_block, join_block)?;
context.push_loop(body_block, increment_block, join_block);
context.set_basic_block(body_block);
self.body.into_llvm(context)?;
context.build_unconditional_branch(increment_block);
context.set_basic_block(increment_block);
self.finalizer.into_llvm(context)?;
context.build_unconditional_branch(condition_block);
context.pop_loop();
context.set_basic_block(join_block);
Ok(())
}
}
@@ -0,0 +1,720 @@
//!
//! The function definition statement.
//!
use std::collections::BTreeSet;
use std::collections::HashSet;
use inkwell::types::BasicType;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::identifier::Identifier;
use crate::yul::parser::statement::block::Block;
use crate::yul::parser::statement::expression::function_call::name::Name as FunctionName;
///
/// The function definition statement.
///
/// All functions are translated in two steps:
/// 1. The hoisted declaration
/// 2. The definition, which now has the access to all function signatures
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct FunctionDefinition {
/// The location.
pub location: Location,
/// The function identifier.
pub identifier: String,
/// The function formal arguments.
pub arguments: Vec<Identifier>,
/// The function return variables.
pub result: Vec<Identifier>,
/// The function body block.
pub body: Block,
/// The function LLVM attributes encoded in the identifier.
pub attributes: BTreeSet<era_compiler_llvm_context::EraVMAttribute>,
}
impl FunctionDefinition {
/// The LLVM attribute section prefix.
pub const LLVM_ATTRIBUTE_PREFIX: &'static str = "$llvm_";
/// The LLVM attribute section suffix.
pub const LLVM_ATTRIBUTE_SUFFIX: &'static str = "_llvm$";
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let (location, identifier) = match token {
Token {
lexeme: Lexeme::Identifier(identifier),
location,
..
} => (location, identifier),
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{identifier}"],
found: token.lexeme.to_string(),
}
.into());
}
};
let identifier = Identifier::new(location, identifier.inner);
match FunctionName::from(identifier.inner.as_str()) {
FunctionName::UserDefined(_) => {}
_function_name => {
return Err(ParserError::ReservedIdentifier {
location,
identifier: identifier.inner,
}
.into())
}
}
match lexer.next()? {
Token {
lexeme: Lexeme::Symbol(Symbol::ParenthesisLeft),
..
} => {}
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["("],
found: token.lexeme.to_string(),
}
.into());
}
}
let (mut arguments, next) = Identifier::parse_typed_list(lexer, None)?;
if identifier
.inner
.contains(era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX)
{
if arguments.is_empty() {
return Err(ParserError::InvalidNumberOfArguments {
location,
identifier: identifier.inner,
expected: 1,
found: arguments.len(),
}
.into());
}
arguments.remove(0);
}
if identifier.inner.contains(
era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER,
) && !arguments.is_empty()
{
return Err(ParserError::InvalidNumberOfArguments {
location,
identifier: identifier.inner,
expected: 0,
found: arguments.len(),
}
.into());
}
match crate::yul::parser::take_or_next(next, lexer)? {
Token {
lexeme: Lexeme::Symbol(Symbol::ParenthesisRight),
..
} => {}
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec![")"],
found: token.lexeme.to_string(),
}
.into());
}
}
let (result, next) = match lexer.peek()? {
Token {
lexeme: Lexeme::Symbol(Symbol::Arrow),
..
} => {
lexer.next()?;
Identifier::parse_typed_list(lexer, None)?
}
Token {
lexeme: Lexeme::Symbol(Symbol::BracketCurlyLeft),
..
} => (vec![], None),
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["->", "{"],
found: token.lexeme.to_string(),
}
.into());
}
};
let body = Block::parse(lexer, next)?;
let attributes = Self::get_llvm_attributes(&identifier)?;
Ok(Self {
location,
identifier: identifier.inner,
arguments,
result,
body,
attributes,
})
}
///
/// Gets the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
self.body.get_missing_libraries()
}
///
/// Gets the list of LLVM attributes provided in the function name.
///
pub fn get_llvm_attributes(
identifier: &Identifier,
) -> Result<BTreeSet<era_compiler_llvm_context::EraVMAttribute>, Error> {
let mut valid_attributes = BTreeSet::new();
let llvm_begin = identifier.inner.find(Self::LLVM_ATTRIBUTE_PREFIX);
let llvm_end = identifier.inner.find(Self::LLVM_ATTRIBUTE_SUFFIX);
let attribute_string = if let (Some(llvm_begin), Some(llvm_end)) = (llvm_begin, llvm_end) {
if llvm_begin < llvm_end {
&identifier.inner[llvm_begin + Self::LLVM_ATTRIBUTE_PREFIX.len()..llvm_end]
} else {
return Ok(valid_attributes);
}
} else {
return Ok(valid_attributes);
};
let mut invalid_attributes = BTreeSet::new();
for value in attribute_string.split('_') {
match era_compiler_llvm_context::EraVMAttribute::try_from(value) {
Ok(attribute) => valid_attributes.insert(attribute),
Err(value) => invalid_attributes.insert(value),
};
}
if !invalid_attributes.is_empty() {
return Err(ParserError::InvalidAttributes {
location: identifier.location,
values: invalid_attributes,
}
.into());
}
Ok(valid_attributes)
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for FunctionDefinition
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn declare(
&mut self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
let argument_types: Vec<_> = self
.arguments
.iter()
.map(|argument| {
let yul_type = argument.r#type.to_owned().unwrap_or_default();
yul_type.into_llvm(context).as_basic_type_enum()
})
.collect();
let function_type = context.function_type(
argument_types,
self.result.len(),
self.identifier
.starts_with(era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX),
);
let function = context.add_function(
self.identifier.as_str(),
function_type,
self.result.len(),
Some(inkwell::module::Linkage::Private),
)?;
era_compiler_llvm_context::EraVMFunction::set_attributes(
context.llvm(),
function.borrow().declaration(),
self.attributes.clone().into_iter().collect(),
true,
);
function
.borrow_mut()
.set_yul_data(era_compiler_llvm_context::EraVMFunctionYulData::default());
Ok(())
}
fn into_llvm(
mut self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
context.set_current_function(self.identifier.as_str())?;
let r#return = context.current_function().borrow().r#return();
context.set_basic_block(context.current_function().borrow().entry_block());
match r#return {
era_compiler_llvm_context::EraVMFunctionReturn::None => {}
era_compiler_llvm_context::EraVMFunctionReturn::Primitive { pointer } => {
let identifier = self.result.pop().expect("Always exists");
let r#type = identifier.r#type.unwrap_or_default();
context.build_store(pointer, r#type.into_llvm(context).const_zero())?;
context
.current_function()
.borrow_mut()
.insert_stack_pointer(identifier.inner, pointer);
}
era_compiler_llvm_context::EraVMFunctionReturn::Compound { pointer, .. } => {
for (index, identifier) in self.result.into_iter().enumerate() {
let r#type = identifier.r#type.unwrap_or_default().into_llvm(context);
let pointer = context.build_gep(
pointer,
&[
context.field_const(0),
context
.integer_type(era_compiler_common::BIT_LENGTH_X32)
.const_int(index as u64, false),
],
context.field_type(),
format!("return_{index}_gep_pointer").as_str(),
);
context.build_store(pointer, r#type.const_zero())?;
context
.current_function()
.borrow_mut()
.insert_stack_pointer(identifier.inner.clone(), pointer);
}
}
};
let argument_types: Vec<_> = self
.arguments
.iter()
.map(|argument| {
let yul_type = argument.r#type.to_owned().unwrap_or_default();
yul_type.into_llvm(context)
})
.collect();
for (mut index, argument) in self.arguments.iter().enumerate() {
let pointer = context.build_alloca(argument_types[index], argument.inner.as_str());
context
.current_function()
.borrow_mut()
.insert_stack_pointer(argument.inner.clone(), pointer);
if self
.identifier
.starts_with(era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX)
&& matches!(
context.current_function().borrow().r#return(),
era_compiler_llvm_context::EraVMFunctionReturn::Compound { .. }
)
&& context.is_system_mode()
{
index += 1;
}
context.build_store(
pointer,
context.current_function().borrow().get_nth_param(index),
)?;
}
self.body.into_llvm(context)?;
match context
.basic_block()
.get_last_instruction()
.map(|instruction| instruction.get_opcode())
{
Some(inkwell::values::InstructionOpcode::Br) => {}
Some(inkwell::values::InstructionOpcode::Switch) => {}
_ => context
.build_unconditional_branch(context.current_function().borrow().return_block()),
}
context.set_basic_block(context.current_function().borrow().return_block());
match context.current_function().borrow().r#return() {
era_compiler_llvm_context::EraVMFunctionReturn::None => {
context.build_return(None);
}
era_compiler_llvm_context::EraVMFunctionReturn::Primitive { pointer } => {
let return_value = context.build_load(pointer, "return_value")?;
context.build_return(Some(&return_value));
}
era_compiler_llvm_context::EraVMFunctionReturn::Compound { pointer, .. }
if context.current_function().borrow().name().starts_with(
era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX,
) =>
{
context.build_return(Some(&pointer.value));
}
era_compiler_llvm_context::EraVMFunctionReturn::Compound { pointer, .. } => {
let return_value = context.build_load(pointer, "return_value")?;
context.build_return(Some(&return_value));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error;
use crate::yul::parser::statement::object::Object;
#[test]
fn error_invalid_token_identifier() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function 256() -> result {
result := 42
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(14, 22),
expected: vec!["{identifier}"],
found: "256".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_token_parenthesis_left() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function test{) -> result {
result := 42
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(14, 26),
expected: vec!["("],
found: "{".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_token_parenthesis_right() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function test(} -> result {
result := 42
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(14, 27),
expected: vec![")"],
found: "}".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_token_arrow_or_bracket_curly_left() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function test() := result {
result := 42
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(14, 29),
expected: vec!["->", "{"],
found: ":=".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_number_of_arguments_near_call_abi() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function ZKSYNC_NEAR_CALL_test() -> result {
result := 42
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidNumberOfArguments {
location: Location::new(14, 22),
identifier: "ZKSYNC_NEAR_CALL_test".to_owned(),
expected: 1,
found: 0,
}
.into())
);
}
#[test]
fn error_invalid_number_of_arguments_near_call_abi_catch() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function ZKSYNC_CATCH_NEAR_CALL(length) {
revert(0, length)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidNumberOfArguments {
location: Location::new(14, 22),
identifier: "ZKSYNC_CATCH_NEAR_CALL".to_owned(),
expected: 0,
found: 1,
}
.into())
);
}
#[test]
fn error_reserved_identifier() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function basefee() -> result {
result := 42
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::ReservedIdentifier {
location: Location::new(14, 22),
identifier: "basefee".to_owned()
}
.into())
);
}
#[test]
fn error_invalid_attributes_single() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function test_$llvm_UnknownAttribute_llvm$_test() -> result {
result := 42
}
}
}
}
"#;
let mut invalid_attributes = BTreeSet::new();
invalid_attributes.insert("UnknownAttribute".to_owned());
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidAttributes {
location: Location::new(14, 22),
values: invalid_attributes,
}
.into())
);
}
#[test]
fn error_invalid_attributes_multiple_repeated() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
function test_$llvm_UnknownAttribute1_UnknownAttribute1_UnknownAttribute2_llvm$_test() -> result {
result := 42
}
}
}
}
"#;
let mut invalid_attributes = BTreeSet::new();
invalid_attributes.insert("UnknownAttribute1".to_owned());
invalid_attributes.insert("UnknownAttribute2".to_owned());
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidAttributes {
location: Location::new(14, 22),
values: invalid_attributes,
}
.into())
);
}
}
@@ -0,0 +1,94 @@
//!
//! The if-conditional statement.
//!
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::statement::block::Block;
use crate::yul::parser::statement::expression::Expression;
///
/// The Yul if-conditional statement.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct IfConditional {
/// The location.
pub location: Location,
/// The condition expression.
pub condition: Expression,
/// The conditional block.
pub block: Block,
}
impl IfConditional {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let location = token.location;
let condition = Expression::parse(lexer, Some(token))?;
let block = Block::parse(lexer, None)?;
Ok(Self {
location,
condition,
block,
})
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
let mut libraries = self.condition.get_missing_libraries();
libraries.extend(self.block.get_missing_libraries());
libraries
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for IfConditional
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn into_llvm(
self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
let condition = self
.condition
.into_llvm(context)?
.expect("Always exists")
.to_llvm()
.into_int_value();
let condition = context.builder().build_int_z_extend_or_bit_cast(
condition,
context.field_type(),
"if_condition_extended",
)?;
let condition = context.builder().build_int_compare(
inkwell::IntPredicate::NE,
condition,
context.field_const(0),
"if_condition_compared",
)?;
let main_block = context.append_basic_block("if_main");
let join_block = context.append_basic_block("if_join");
context.build_conditional_branch(condition, main_block, join_block)?;
context.set_basic_block(main_block);
self.block.into_llvm(context)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
Ok(())
}
}
@@ -0,0 +1,189 @@
//!
//! The block statement.
//!
pub mod assignment;
pub mod block;
pub mod code;
pub mod expression;
pub mod for_loop;
pub mod function_definition;
pub mod if_conditional;
pub mod object;
pub mod switch;
pub mod variable_declaration;
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::keyword::Keyword;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use self::assignment::Assignment;
use self::block::Block;
use self::code::Code;
use self::expression::Expression;
use self::for_loop::ForLoop;
use self::function_definition::FunctionDefinition;
use self::if_conditional::IfConditional;
use self::object::Object;
use self::switch::Switch;
use self::variable_declaration::VariableDeclaration;
///
/// The Yul block statement.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Statement {
/// The object element.
Object(Object),
/// The code element.
Code(Code),
/// The code block.
Block(Block),
/// The expression.
Expression(Expression),
/// The `function` statement.
FunctionDefinition(FunctionDefinition),
/// The `let` statement.
VariableDeclaration(VariableDeclaration),
/// The `:=` existing variables reassignment statement.
Assignment(Assignment),
/// The `if` statement.
IfConditional(IfConditional),
/// The `switch` statement.
Switch(Switch),
/// The `for` statement.
ForLoop(ForLoop),
/// The `continue` statement.
Continue(Location),
/// The `break` statement.
Break(Location),
/// The `leave` statement.
Leave(Location),
}
impl Statement {
///
/// The element parser.
///
pub fn parse(
lexer: &mut Lexer,
initial: Option<Token>,
) -> Result<(Self, Option<Token>), Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
match token {
token @ Token {
lexeme: Lexeme::Keyword(Keyword::Object),
..
} => Ok((Statement::Object(Object::parse(lexer, Some(token))?), None)),
Token {
lexeme: Lexeme::Keyword(Keyword::Code),
..
} => Ok((Statement::Code(Code::parse(lexer, None)?), None)),
Token {
lexeme: Lexeme::Keyword(Keyword::Function),
..
} => Ok((
Statement::FunctionDefinition(FunctionDefinition::parse(lexer, None)?),
None,
)),
Token {
lexeme: Lexeme::Keyword(Keyword::Let),
..
} => {
let (statement, next) = VariableDeclaration::parse(lexer, None)?;
Ok((Statement::VariableDeclaration(statement), next))
}
Token {
lexeme: Lexeme::Keyword(Keyword::If),
..
} => Ok((
Statement::IfConditional(IfConditional::parse(lexer, None)?),
None,
)),
Token {
lexeme: Lexeme::Keyword(Keyword::Switch),
..
} => Ok((Statement::Switch(Switch::parse(lexer, None)?), None)),
Token {
lexeme: Lexeme::Keyword(Keyword::For),
..
} => Ok((Statement::ForLoop(ForLoop::parse(lexer, None)?), None)),
Token {
lexeme: Lexeme::Keyword(Keyword::Continue),
location,
..
} => Ok((Statement::Continue(location), None)),
Token {
lexeme: Lexeme::Keyword(Keyword::Break),
location,
..
} => Ok((Statement::Break(location), None)),
Token {
lexeme: Lexeme::Keyword(Keyword::Leave),
location,
..
} => Ok((Statement::Leave(location), None)),
token => Err(ParserError::InvalidToken {
location: token.location,
expected: vec![
"object", "code", "function", "let", "if", "switch", "for", "continue",
"break", "leave",
],
found: token.lexeme.to_string(),
}
.into()),
}
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
match self {
Self::Object(inner) => inner.get_missing_libraries(),
Self::Code(inner) => inner.get_missing_libraries(),
Self::Block(inner) => inner.get_missing_libraries(),
Self::Expression(inner) => inner.get_missing_libraries(),
Self::FunctionDefinition(inner) => inner.get_missing_libraries(),
Self::VariableDeclaration(inner) => inner.get_missing_libraries(),
Self::Assignment(inner) => inner.get_missing_libraries(),
Self::IfConditional(inner) => inner.get_missing_libraries(),
Self::Switch(inner) => inner.get_missing_libraries(),
Self::ForLoop(inner) => inner.get_missing_libraries(),
Self::Continue(_) => HashSet::new(),
Self::Break(_) => HashSet::new(),
Self::Leave(_) => HashSet::new(),
}
}
///
/// Returns the statement location.
///
pub fn location(&self) -> Location {
match self {
Self::Object(inner) => inner.location,
Self::Code(inner) => inner.location,
Self::Block(inner) => inner.location,
Self::Expression(inner) => inner.location(),
Self::FunctionDefinition(inner) => inner.location,
Self::VariableDeclaration(inner) => inner.location,
Self::Assignment(inner) => inner.location,
Self::IfConditional(inner) => inner.location,
Self::Switch(inner) => inner.location,
Self::ForLoop(inner) => inner.location,
Self::Continue(location) => *location,
Self::Break(location) => *location,
Self::Leave(location) => *location,
}
}
}
@@ -0,0 +1,424 @@
//!
//! The YUL object.
//!
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::keyword::Keyword;
use crate::yul::lexer::token::lexeme::literal::Literal;
use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::statement::code::Code;
///
/// The upper-level YUL object, representing the deploy code.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Object {
/// The location.
pub location: Location,
/// The identifier.
pub identifier: String,
/// The code.
pub code: Code,
/// The optional inner object, representing the runtime code.
pub inner_object: Option<Box<Self>>,
/// The factory dependency objects, which are represented by nested Yul object. The nested
/// objects are duplicates of the upper-level objects describing the dependencies, so only
/// their identifiers are preserved. The identifiers are used to address upper-level objects.
pub factory_dependencies: HashSet<String>,
}
impl Object {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let location = match token {
Token {
lexeme: Lexeme::Keyword(Keyword::Object),
location,
..
} => location,
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["object"],
found: token.lexeme.to_string(),
}
.into());
}
};
let identifier = match lexer.next()? {
Token {
lexeme: Lexeme::Literal(Literal::String(literal)),
..
} => literal.inner,
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{string}"],
found: token.lexeme.to_string(),
}
.into());
}
};
let is_runtime_code = identifier.ends_with("_deployed");
match lexer.next()? {
Token {
lexeme: Lexeme::Symbol(Symbol::BracketCurlyLeft),
..
} => {}
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{"],
found: token.lexeme.to_string(),
}
.into());
}
}
let code = Code::parse(lexer, None)?;
let mut inner_object = None;
let mut factory_dependencies = HashSet::new();
if !is_runtime_code {
inner_object = match lexer.peek()? {
Token {
lexeme: Lexeme::Keyword(Keyword::Object),
..
} => {
let mut object = Self::parse(lexer, None)?;
if format!("{identifier}_deployed") != object.identifier {
return Err(ParserError::InvalidObjectName {
location: object.location,
expected: format!("{identifier}_deployed"),
found: object.identifier,
}
.into());
}
factory_dependencies.extend(object.factory_dependencies.drain());
Some(Box::new(object))
}
_ => None,
};
if let Token {
lexeme: Lexeme::Identifier(identifier),
..
} = lexer.peek()?
{
if identifier.inner.as_str() == "data" {
let _data = lexer.next()?;
let _identifier = lexer.next()?;
let _metadata = lexer.next()?;
}
};
}
loop {
match lexer.next()? {
Token {
lexeme: Lexeme::Symbol(Symbol::BracketCurlyRight),
..
} => break,
token @ Token {
lexeme: Lexeme::Keyword(Keyword::Object),
..
} => {
let dependency = Self::parse(lexer, Some(token))?;
factory_dependencies.insert(dependency.identifier);
}
Token {
lexeme: Lexeme::Identifier(identifier),
..
} if identifier.inner.as_str() == "data" => {
let _identifier = lexer.next()?;
let _metadata = lexer.next()?;
}
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["object", "}"],
found: token.lexeme.to_string(),
}
.into());
}
}
}
Ok(Self {
location,
identifier,
code,
inner_object,
factory_dependencies,
})
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
let mut missing_libraries = self.code.get_missing_libraries();
if let Some(inner_object) = &self.inner_object {
missing_libraries.extend(inner_object.get_missing_libraries());
}
missing_libraries
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Object
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn declare(
&mut self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
let mut entry = era_compiler_llvm_context::EraVMEntryFunction::default();
entry.declare(context)?;
let mut runtime = era_compiler_llvm_context::EraVMRuntime::new(
era_compiler_llvm_context::EraVMAddressSpace::Heap,
);
runtime.declare(context)?;
era_compiler_llvm_context::EraVMDeployCodeFunction::new(
era_compiler_llvm_context::EraVMDummyLLVMWritable::default(),
)
.declare(context)?;
era_compiler_llvm_context::EraVMRuntimeCodeFunction::new(
era_compiler_llvm_context::EraVMDummyLLVMWritable::default(),
)
.declare(context)?;
for name in [
era_compiler_llvm_context::EraVMRuntime::FUNCTION_DEPLOY_CODE,
era_compiler_llvm_context::EraVMRuntime::FUNCTION_RUNTIME_CODE,
era_compiler_llvm_context::EraVMRuntime::FUNCTION_ENTRY,
]
.into_iter()
{
context
.get_function(name)
.expect("Always exists")
.borrow_mut()
.set_yul_data(era_compiler_llvm_context::EraVMFunctionYulData::default());
}
entry.into_llvm(context)?;
Ok(())
}
fn into_llvm(
self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
if self.identifier.ends_with("_deployed") {
era_compiler_llvm_context::EraVMRuntimeCodeFunction::new(self.code)
.into_llvm(context)?;
} else {
era_compiler_llvm_context::EraVMDeployCodeFunction::new(self.code)
.into_llvm(context)?;
}
match self.inner_object {
Some(object) => {
object.into_llvm(context)?;
}
None => {
let runtime = era_compiler_llvm_context::EraVMRuntime::new(
era_compiler_llvm_context::EraVMAddressSpace::Heap,
);
runtime.into_llvm(context)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error;
use crate::yul::parser::statement::object::Object;
#[test]
fn error_invalid_token_object() {
let input = r#"
class "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(2, 1),
expected: vec!["object"],
found: "class".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_token_identifier() {
let input = r#"
object 256 {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(2, 8),
expected: vec!["{string}"],
found: "256".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_token_bracket_curly_left() {
let input = r#"
object "Test" (
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
return(0, 0)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(2, 15),
expected: vec!["{"],
found: "(".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_token_object_inner() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
class "Test_deployed" {
code {
{
return(0, 0)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(8, 5),
expected: vec!["object", "}"],
found: "class".to_owned(),
}
.into())
);
}
#[test]
fn error_invalid_object_name() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Invalid" {
code {
{
return(0, 0)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidObjectName {
location: Location::new(8, 5),
expected: "Test_deployed".to_owned(),
found: "Invalid".to_owned(),
}
.into())
);
}
}
@@ -0,0 +1,113 @@
//!
//! The switch statement case.
//!
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::statement::block::Block;
use crate::yul::parser::statement::expression::literal::Literal;
///
/// The Yul switch statement case.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Case {
/// The location.
pub location: Location,
/// The matched constant.
pub literal: Literal,
/// The case block.
pub block: Block,
}
impl Case {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let (location, literal) = match token {
token @ Token {
lexeme: Lexeme::Literal(_),
location,
..
} => (location, Literal::parse(lexer, Some(token))?),
token => {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{literal}"],
found: token.lexeme.to_string(),
}
.into());
}
};
let block = Block::parse(lexer, None)?;
Ok(Self {
location,
literal,
block,
})
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
self.block.get_missing_libraries()
}
}
#[cfg(test)]
mod tests {
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error;
use crate::yul::parser::statement::object::Object;
#[test]
fn error_invalid_token_literal() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
switch 42
case x {}
default {}
}
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(12, 26),
expected: vec!["{literal}"],
found: "x".to_owned(),
}
.into())
);
}
}
@@ -0,0 +1,229 @@
//!
//! The switch statement.
//!
pub mod case;
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::keyword::Keyword;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::statement::block::Block;
use crate::yul::parser::statement::expression::Expression;
use self::case::Case;
///
/// The Yul switch statement.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Switch {
/// The location.
pub location: Location,
/// The expression being matched.
pub expression: Expression,
/// The non-default cases.
pub cases: Vec<Case>,
/// The optional default case, if `cases` do not cover all possible values.
pub default: Option<Block>,
}
///
/// The parsing state.
///
pub enum State {
/// After match expression.
CaseOrDefaultKeyword,
/// After `case`.
CaseBlock,
/// After `default`.
DefaultBlock,
}
impl Switch {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let mut token = crate::yul::parser::take_or_next(initial, lexer)?;
let location = token.location;
let mut state = State::CaseOrDefaultKeyword;
let expression = Expression::parse(lexer, Some(token.clone()))?;
let mut cases = Vec::new();
let mut default = None;
loop {
match state {
State::CaseOrDefaultKeyword => match lexer.peek()? {
_token @ Token {
lexeme: Lexeme::Keyword(Keyword::Case),
..
} => {
token = _token;
state = State::CaseBlock;
}
_token @ Token {
lexeme: Lexeme::Keyword(Keyword::Default),
..
} => {
token = _token;
state = State::DefaultBlock;
}
_token => {
token = _token;
break;
}
},
State::CaseBlock => {
lexer.next()?;
cases.push(Case::parse(lexer, None)?);
state = State::CaseOrDefaultKeyword;
}
State::DefaultBlock => {
lexer.next()?;
default = Some(Block::parse(lexer, None)?);
break;
}
}
}
if cases.is_empty() && default.is_none() {
return Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["case", "default"],
found: token.lexeme.to_string(),
}
.into());
}
Ok(Self {
location,
expression,
cases,
default,
})
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
let mut libraries = HashSet::new();
for case in self.cases.iter() {
libraries.extend(case.get_missing_libraries());
}
if let Some(default) = &self.default {
libraries.extend(default.get_missing_libraries());
}
libraries
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Switch
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn into_llvm(
self,
context: &mut era_compiler_llvm_context::EraVMContext<D>,
) -> anyhow::Result<()> {
let scrutinee = self.expression.into_llvm(context)?;
if self.cases.is_empty() {
if let Some(block) = self.default {
block.into_llvm(context)?;
}
return Ok(());
}
let current_block = context.basic_block();
let join_block = context.append_basic_block("switch_join_block");
let mut branches = Vec::with_capacity(self.cases.len());
for (index, case) in self.cases.into_iter().enumerate() {
let constant = case.literal.into_llvm(context)?.to_llvm();
let expression_block = context
.append_basic_block(format!("switch_case_branch_{}_block", index + 1).as_str());
context.set_basic_block(expression_block);
case.block.into_llvm(context)?;
context.build_unconditional_branch(join_block);
branches.push((constant.into_int_value(), expression_block));
}
let default_block = match self.default {
Some(default) => {
let default_block = context.append_basic_block("switch_default_block");
context.set_basic_block(default_block);
default.into_llvm(context)?;
context.build_unconditional_branch(join_block);
default_block
}
None => join_block,
};
context.set_basic_block(current_block);
context.builder().build_switch(
scrutinee.expect("Always exists").to_llvm().into_int_value(),
default_block,
branches.as_slice(),
)?;
context.set_basic_block(join_block);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error;
use crate::yul::parser::statement::object::Object;
#[test]
fn error_invalid_token_case() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
switch 42
branch x {}
default {}
}
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::InvalidToken {
location: Location::new(12, 21),
expected: vec!["case", "default"],
found: "branch".to_owned(),
}
.into())
);
}
}
@@ -0,0 +1,262 @@
//!
//! The variable declaration statement.
//!
use std::collections::HashSet;
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::symbol::Symbol;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
use crate::yul::parser::identifier::Identifier;
use crate::yul::parser::statement::expression::function_call::name::Name as FunctionName;
use crate::yul::parser::statement::expression::Expression;
///
/// The Yul variable declaration statement.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct VariableDeclaration {
/// The location.
pub location: Location,
/// The variable bindings list.
pub bindings: Vec<Identifier>,
/// The variable initializing expression.
pub expression: Option<Expression>,
}
impl VariableDeclaration {
///
/// The element parser.
///
pub fn parse(
lexer: &mut Lexer,
initial: Option<Token>,
) -> Result<(Self, Option<Token>), Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
let location = token.location;
let (bindings, next) = Identifier::parse_typed_list(lexer, Some(token))?;
for binding in bindings.iter() {
match FunctionName::from(binding.inner.as_str()) {
FunctionName::UserDefined(_) => continue,
_function_name => {
return Err(ParserError::ReservedIdentifier {
location: binding.location,
identifier: binding.inner.to_owned(),
}
.into())
}
}
}
match crate::yul::parser::take_or_next(next, lexer)? {
Token {
lexeme: Lexeme::Symbol(Symbol::Assignment),
..
} => {}
token => {
return Ok((
Self {
location,
bindings,
expression: None,
},
Some(token),
))
}
}
let expression = Expression::parse(lexer, None)?;
Ok((
Self {
location,
bindings,
expression: Some(expression),
},
None,
))
}
///
/// Get the list of missing deployable libraries.
///
pub fn get_missing_libraries(&self) -> HashSet<String> {
self.expression
.as_ref()
.map_or_else(HashSet::new, |expression| {
expression.get_missing_libraries()
})
}
}
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for VariableDeclaration
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
fn into_llvm<'ctx>(
mut self,
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
) -> anyhow::Result<()> {
if self.bindings.len() == 1 {
let identifier = self.bindings.remove(0);
let r#type = identifier.r#type.unwrap_or_default().into_llvm(context);
let pointer = context.build_alloca(r#type, identifier.inner.as_str());
context
.current_function()
.borrow_mut()
.insert_stack_pointer(identifier.inner.clone(), pointer);
let value = if let Some(expression) = self.expression {
match expression.into_llvm(context)? {
Some(mut value) => {
if let Some(constant) = value.constant.take() {
context
.current_function()
.borrow_mut()
.yul_mut()
.insert_constant(identifier.inner, constant);
}
value.to_llvm()
}
None => r#type.const_zero().as_basic_value_enum(),
}
} else {
r#type.const_zero().as_basic_value_enum()
};
context.build_store(pointer, value)?;
return Ok(());
}
for (index, binding) in self.bindings.iter().enumerate() {
let yul_type = binding
.r#type
.to_owned()
.unwrap_or_default()
.into_llvm(context);
let pointer = context.build_alloca(
yul_type.as_basic_type_enum(),
format!("binding_{index}_pointer").as_str(),
);
context.build_store(pointer, yul_type.const_zero())?;
context
.current_function()
.borrow_mut()
.insert_stack_pointer(binding.inner.to_owned(), pointer);
}
let expression = match self.expression.take() {
Some(expression) => expression,
None => return Ok(()),
};
let location = expression.location();
let expression = match expression.into_llvm(context)? {
Some(expression) => expression,
None => return Ok(()),
};
let llvm_type = context.structure_type(
self.bindings
.iter()
.map(|binding| {
binding
.r#type
.to_owned()
.unwrap_or_default()
.into_llvm(context)
.as_basic_type_enum()
})
.collect::<Vec<inkwell::types::BasicTypeEnum<'ctx>>>()
.as_slice(),
);
if expression.value.get_type() != llvm_type.as_basic_type_enum() {
anyhow::bail!(
"{} Assignment to {:?} received an invalid number of arguments",
location,
self.bindings
);
}
let pointer = context.build_alloca(llvm_type, "bindings_pointer");
context.build_store(pointer, expression.to_llvm())?;
for (index, binding) in self.bindings.into_iter().enumerate() {
let pointer = context.build_gep(
pointer,
&[
context.field_const(0),
context
.integer_type(era_compiler_common::BIT_LENGTH_X32)
.const_int(index as u64, false),
],
binding.r#type.unwrap_or_default().into_llvm(context),
format!("binding_{index}_gep_pointer").as_str(),
);
let value = context.build_load(pointer, format!("binding_{index}_value").as_str())?;
let pointer = context
.current_function()
.borrow_mut()
.get_stack_pointer(binding.inner.as_str())
.ok_or_else(|| {
anyhow::anyhow!(
"{} Assignment to an undeclared variable `{}`",
binding.location,
binding.inner
)
})?;
context.build_store(pointer, value)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::yul::lexer::token::location::Location;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error;
use crate::yul::parser::statement::object::Object;
#[test]
fn error_reserved_identifier() {
let input = r#"
object "Test" {
code {
{
return(0, 0)
}
}
object "Test_deployed" {
code {
{
let basefee := 42
return(0, 0)
}
}
}
}
"#;
let mut lexer = Lexer::new(input.to_owned());
let result = Object::parse(&mut lexer, None);
assert_eq!(
result,
Err(Error::ReservedIdentifier {
location: Location::new(11, 21),
identifier: "basefee".to_owned()
}
.into())
);
}
}
+88
View File
@@ -0,0 +1,88 @@
//!
//! The YUL source code type.
//!
use serde::Deserialize;
use serde::Serialize;
use crate::yul::error::Error;
use crate::yul::lexer::token::lexeme::keyword::Keyword;
use crate::yul::lexer::token::lexeme::Lexeme;
use crate::yul::lexer::token::Token;
use crate::yul::lexer::Lexer;
use crate::yul::parser::error::Error as ParserError;
///
/// The YUL source code type.
///
/// The type is not currently in use, so all values have the `uint256` type by default.
///
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Type {
/// The `bool` type.
Bool,
/// The `int{N}` type.
Int(usize),
/// The `uint{N}` type.
UInt(usize),
/// The custom user-defined type.
Custom(String),
}
impl Default for Type {
fn default() -> Self {
Self::UInt(era_compiler_common::BIT_LENGTH_FIELD)
}
}
impl Type {
///
/// The element parser.
///
pub fn parse(lexer: &mut Lexer, initial: Option<Token>) -> Result<Self, Error> {
let token = crate::yul::parser::take_or_next(initial, lexer)?;
match token {
Token {
lexeme: Lexeme::Keyword(Keyword::Bool),
..
} => Ok(Self::Bool),
Token {
lexeme: Lexeme::Keyword(Keyword::Int(bitlength)),
..
} => Ok(Self::Int(bitlength)),
Token {
lexeme: Lexeme::Keyword(Keyword::Uint(bitlength)),
..
} => Ok(Self::UInt(bitlength)),
Token {
lexeme: Lexeme::Identifier(identifier),
..
} => Ok(Self::Custom(identifier.inner)),
token => Err(ParserError::InvalidToken {
location: token.location,
expected: vec!["{type}"],
found: token.lexeme.to_string(),
}
.into()),
}
}
///
/// Converts the type into its LLVM.
///
pub fn into_llvm<'ctx, D>(
self,
context: &era_compiler_llvm_context::EraVMContext<'ctx, D>,
) -> inkwell::types::IntType<'ctx>
where
D: era_compiler_llvm_context::EraVMDependency + Clone,
{
match self {
Self::Bool => context.integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN),
Self::Int(bitlength) => context.integer_type(bitlength),
Self::UInt(bitlength) => context.integer_type(bitlength),
Self::Custom(_) => context.field_type(),
}
}
}