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
+3
View File
@@ -1,3 +1,6 @@
/target /target
*.dot *.dot
.vscode/ .vscode/
.DS_Store
/*.sol
/*.yul
Generated
+877 -478
View File
File diff suppressed because it is too large Load Diff
+25 -6
View File
@@ -3,14 +3,33 @@ resolver = "2"
members = ["crates/*"] members = ["crates/*"]
[workspace.dependencies] [workspace.dependencies]
evmil = "0.4"
hex = "0.4" hex = "0.4"
petgraph = "0.6" petgraph = "0.6"
primitive-types = "0.12"
indexmap = "2.1.0"
inkwell = { version = "0.2.0", features = ["target-riscv", "no-libffi-linking", "llvm16-0"] }
cc = "1.0" cc = "1.0"
libc = "0.2" libc = "0.2"
tempfile = "3.8" tempfile = "3.8"
polkavm-common = { git = "https://github.com/koute/polkavm.git", rev = "3552524a248a025de8e608394fcf9eb7c528eb11" } #inkwell = { version = "0.4.0", default-features = false, features = ["llvm16-0", "no-libffi-linking", "target-riscv"] }
polkavm-linker = { git = "https://github.com/koute/polkavm.git", rev = "3552524a248a025de8e608394fcf9eb7c528eb11" } inkwell = { path = "../inkwell", default-features = false, features = ["serde", "llvm16-0", "no-libffi-linking", "target-riscv"] }
anyhow = "1.0"
semver = { version = "1.0", features = [ "serde" ] }
itertools = "0.12"
serde = { version = "1.0", features = [ "derive" ] }
serde_json = { version = "1.0", features = [ "arbitrary_precision" ] }
regex = "1.10"
once_cell = "1.19"
num = "0.4"
sha2 = "0.10"
sha3 = "0.10"
md5 = "0.7"
colored = "2.1"
thiserror = "1.0"
which = "5.0"
path-slash = "0.2"
rayon = "1.8"
structopt = { version = "0.3", default-features = false }
rand = "0.8"
polkavm-common = { git = "https://github.com/koute/polkavm.git" }
polkavm-linker = { git = "https://github.com/koute/polkavm.git" }
polkavm = { git = "https://github.com/koute/polkavm.git" }
parity-scale-codec = "3.6"
alloy-primitives = "0.6"
+191
View File
@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright (c) 2019 Matter Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Matter Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+32
View File
@@ -0,0 +1,32 @@
# revive
YUL and EVM bytecode recompiler to LLVM, targetting RISC-V on PolkaVM.
Code bases of [frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are forked adapted from ZKSync `zksolc`.
Primary goal of this codebase currently is to allow for benchmarks comparing runtime performance against ink!, solang and EVM interpreters.
# TODO
The project is in a very early PoC phase; at this stage don't expect the produced code to be working nor to be correct for anything more than a basic flipper contract yet.
- [ ] Efficient implementations of byte swaps, memset, memmove and the like
- [ ] Use drink! for integration tests once we have 64bit support in PolkaVM
- [ ] Exercice `schlau` and possibly `smart-bench` benchmark cases
- [ ] Tests currently rely on the binary being in $PATH, which is very annoying and requires `cargo install` all the times
- [ ] Define how to do deployments
- [ ] Calling conventions for calling other contracts
- [ ] Runtime environment isn't fully figured out; implement all EVM builtins
- [ ] Iron out many leftovers from the ZKVM target
- [ ] Use of exceptions
- [ ] Change long calls (contract calls)
- [ ] Check all alignments, attributes etc. if they still make sense with our target
- [ ] Add a lot more test cases
- [ ] Debug information
- [ ] Look for and implement further optimizations
- [ ] Differential testing against EVM
- [ ] Switch to LLVM 18 which has RV{32,64}E upstream
- [ ] Minimize scope of "stdlib", favorably implement it in high level language instead of LLVM IR.
- [ ] Document differences from EVM
- [ ] Audit for bugs and correctness
- [ ] Rebranding
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "revive-cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex = { workspace = true }
evmil = { workspace = true }
revive-ir = { path = "../ir" }
revive-codegen = { path = "../codegen" }
revive-target-polkavm = { path = "../target-polkavm" }
-21
View File
@@ -1,21 +0,0 @@
use evmil::bytecode::Disassemble;
use revive_ir::cfg::BasicBlockFormatOption;
use revive_target_polkavm::PolkaVm;
fn main() {
let hexcode = std::fs::read_to_string(std::env::args().nth(1).unwrap()).unwrap();
let bytecode = hex::decode(hexcode.trim()).unwrap();
let instructions = bytecode.disassemble();
let mut ir = revive_ir::cfg::Program::new(&instructions);
ir.optimize();
ir.dot(BasicBlockFormatOption::Ir);
let target = PolkaVm::default();
let program = revive_codegen::program::Program::new(&target).unwrap();
program.emit(ir);
let artifact = program.compile_and_link();
std::fs::write("/tmp/out.pvm", artifact).unwrap();
}
-12
View File
@@ -1,12 +0,0 @@
[package]
name = "revive-codegen"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
inkwell = { workspace = true }
revive-compilation-target = { path = "../compilation-target" }
revive-ir = { path = "../ir" }
-2
View File
@@ -1,2 +0,0 @@
mod module;
pub mod program;
-37
View File
@@ -1,37 +0,0 @@
use inkwell::{
module::Module,
support::LLVMString,
targets::{RelocMode, TargetTriple},
};
use revive_compilation_target::target::Target;
pub(crate) fn create<'ctx, T>(target: &'ctx T) -> Result<Module<'ctx>, LLVMString>
where
T: Target<'ctx>,
{
let module = target.context().create_module("contract");
module.set_triple(&TargetTriple::create(<T as Target>::TARGET_TRIPLE));
module.set_source_file_name("contract.bin");
set_flags(target, &module);
for lib in target.libraries() {
module.link_in_module(lib)?;
}
Ok(module)
}
fn set_flags<'ctx, T>(target: &'ctx T, module: &Module<'ctx>)
where
T: Target<'ctx>,
{
if let RelocMode::PIC = <T as Target>::RELOC_MODE {
module.add_basic_value_flag(
"PIE Level",
inkwell::module::FlagBehavior::Override,
target.context().i32_type().const_int(2, false),
);
}
}
-93
View File
@@ -1,93 +0,0 @@
use inkwell::{
builder::Builder,
module::{Linkage, Module},
support::LLVMString,
targets::{FileType, TargetTriple},
values::{FunctionValue, GlobalValue},
AddressSpace,
};
use revive_compilation_target::environment::Environment;
use revive_compilation_target::target::Target;
use crate::module;
pub struct Program<'ctx, T> {
pub module: Module<'ctx>,
pub builder: Builder<'ctx>,
pub calldata: GlobalValue<'ctx>,
pub returndata: GlobalValue<'ctx>,
pub target: &'ctx T,
pub start: FunctionValue<'ctx>,
}
impl<'ctx, T> Program<'ctx, T>
where
T: Target<'ctx> + Environment<'ctx>,
{
pub fn new(target: &'ctx T) -> Result<Self, LLVMString> {
T::initialize_llvm();
let context = target.context();
let module = module::create(target)?;
let builder = context.create_builder();
let address_space = Some(AddressSpace::default());
let calldata_type = context.i8_type().array_type(T::CALLDATA_SIZE);
let calldata = module.add_global(calldata_type, address_space, "calldata");
let returndata_type = context.i8_type().array_type(T::RETURNDATA_SIZE);
let returndata = module.add_global(returndata_type, address_space, "returndata");
let start_fn_type = target.context().void_type().fn_type(&[], false);
let start = module.add_function("start", start_fn_type, Some(Linkage::Internal));
Ok(Self {
module,
builder,
calldata,
returndata,
target,
start,
})
}
pub fn emit(&self, program: revive_ir::cfg::Program) {
self.emit_start();
}
pub fn compile_and_link(&self) -> Vec<u8> {
inkwell::targets::Target::from_name(T::TARGET_NAME)
.expect("target name should be valid")
.create_target_machine(
&TargetTriple::create(T::TARGET_TRIPLE),
T::CPU,
T::TARGET_FEATURES,
self.target.optimization_level(),
T::RELOC_MODE,
T::CODE_MODEL,
)
.expect("target configuration should be valid")
.write_to_memory_buffer(&self.module, FileType::Object)
.map(|out| self.target.link(out.as_slice()))
.expect("linker should succeed")
.to_vec()
}
fn emit_start(&self) {
let start = self.start;
let block = self
.start
.get_last_basic_block()
.unwrap_or_else(|| self.target.context().append_basic_block(start, "entry"));
self.builder.position_at_end(block);
self.builder.build_return(None);
let env_start = self.target.call_start(&self.builder, self.start);
self.module
.link_in_module(env_start)
.expect("entrypoint module should be linkable");
}
}
@@ -1,15 +0,0 @@
use inkwell::{builder::Builder, module::Module, values::FunctionValue};
/// [Environment] describes EVM runtime functionality.
pub trait Environment<'ctx> {
const STACK_SIZE: u32 = 1024 * 32;
const CALLDATA_SIZE: u32 = 0x10000;
const RETURNDATA_SIZE: u32 = 0x10000;
const MEMORY_SIZE: u32 = 0x100000;
/// Build a module containing all required runtime exports and imports.
///
/// The `start` function is the entrypoint to the contract logic.
/// The returned `Module` is expected to call `start` somewhere.
fn call_start(&'ctx self, builder: &Builder<'ctx>, start: FunctionValue<'ctx>) -> Module<'ctx>;
}
-2
View File
@@ -1,2 +0,0 @@
pub mod environment;
pub mod target;
-27
View File
@@ -1,27 +0,0 @@
use inkwell::{
context::Context,
module::Module,
targets::{CodeModel, RelocMode},
OptimizationLevel,
};
pub trait Target<'ctx> {
const TARGET_NAME: &'ctx str;
const TARGET_TRIPLE: &'ctx str;
const TARGET_FEATURES: &'ctx str;
const CPU: &'ctx str;
const RELOC_MODE: RelocMode = RelocMode::Default;
const CODE_MODEL: CodeModel = CodeModel::Default;
fn initialize_llvm() {
inkwell::targets::Target::initialize_riscv(&Default::default());
}
fn context(&self) -> &Context;
fn libraries(&'ctx self) -> Vec<Module<'ctx>>;
fn link(&self, blob: &[u8]) -> Vec<u8>;
fn optimization_level(&self) -> OptimizationLevel;
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "revive-integration"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex = { workspace = true }
polkavm = { workspace = true }
parity-scale-codec = { workspace = true }
revive-solidity = { path = "../solidity" }
era-compiler-llvm-context = { path = "../llvm-context" }
alloy-primitives = { workspace = true }
@@ -0,0 +1,19 @@
contract Computation {
function triangle_number(int64 n) public pure returns (int64 sum) {
unchecked {
for (int64 x = 1; x <= n; x++) {
sum += x;
}
}
}
function odd_product(int32 n) public pure returns (int64) {
unchecked {
int64 prod = 1;
for (int32 x = 1; x <= n; x++) {
prod *= 2 * int64(x) - 1;
}
return prod;
}
}
}
+73
View File
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
contract ERC20 is IERC20 {
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
string public name = "Solidity by Example";
string public symbol = "SOLBYEX";
uint8 public decimals = 18;
function transfer(address recipient, uint amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function approve(address spender, uint amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
function mint(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
function burn(uint amount) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
+7
View File
@@ -0,0 +1,7 @@
contract Flipper {
bool coin;
function flip() public payable {
coin = !coin;
}
}
+95
View File
@@ -0,0 +1,95 @@
pub mod mock_runtime;
pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
let file_name = "contract.sol";
let contracts = revive_solidity::test_utils::build_solidity(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
revive_solidity::SolcPipeline::Yul,
era_compiler_llvm_context::OptimizerSettings::cycles(),
)
.expect("source should compile")
.contracts
.expect("source should contain at least one contract");
let bytecode = contracts[file_name][contract_name]
.evm
.as_ref()
.expect("source should produce EVM output")
.assembly_text
.as_ref()
.expect("source should produce assembly text");
hex::decode(bytecode).expect("hex encoding should always be valid")
}
#[cfg(test)]
mod tests {
use alloy_primitives::U256;
use crate::mock_runtime::{self, State};
#[test]
fn flipper() {
let code = crate::compile_blob("Flipper", include_str!("../contracts/flipper.sol"));
let state = State::new(0xcde4efa9u32.to_be_bytes().to_vec());
let (instance, export) = mock_runtime::prepare(&code);
let state = crate::mock_runtime::call(state, &instance, export);
assert_eq!(state.output.flags, 0);
assert_eq!(state.storage[&U256::ZERO], U256::try_from(1).unwrap());
let state = crate::mock_runtime::call(state, &instance, export);
assert_eq!(state.output.flags, 0);
assert_eq!(state.storage[&U256::ZERO], U256::ZERO);
}
#[test]
fn erc20() {
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
}
#[test]
fn triangle_number() {
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
let param = alloy_primitives::U256::try_from(13).unwrap();
let expected = alloy_primitives::U256::try_from(91).unwrap();
// function triangle_number(int64)
let mut input = 0x0f760610u32.to_be_bytes().to_vec();
input.extend_from_slice(&param.to_be_bytes::<32>());
let state = State::new(input);
let (instance, export) = mock_runtime::prepare(&code);
let state = crate::mock_runtime::call(state, &instance, export);
assert_eq!(state.output.flags, 0);
let received =
alloy_primitives::U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
assert_eq!(received, expected);
}
#[test]
fn odd_product() {
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
let param = alloy_primitives::I256::try_from(5i32).unwrap();
let expected = alloy_primitives::I256::try_from(945i64).unwrap();
// function odd_product(int32)
let mut input = 0x00261b66u32.to_be_bytes().to_vec();
input.extend_from_slice(&param.to_be_bytes::<32>());
let state = State::new(input);
let (instance, export) = mock_runtime::prepare(&code);
let state = crate::mock_runtime::call(state, &instance, export);
assert_eq!(state.output.flags, 0);
let received =
alloy_primitives::I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
assert_eq!(received, expected);
}
}
+204
View File
@@ -0,0 +1,204 @@
//! Mock environment used for integration tests.
//! TODO: Switch to drink! once RISCV is ready in polkadot-sdk
use std::collections::HashMap;
use alloy_primitives::U256;
use parity_scale_codec::Encode;
use polkavm::{
Caller, Config, Engine, ExportIndex, GasMeteringKind, InstancePre, Linker, Module,
ModuleConfig, ProgramBlob, Trap,
};
#[derive(Default, Clone, Debug)]
pub struct State {
pub input: Vec<u8>,
pub output: CallOutput,
pub value: u128,
pub storage: HashMap<U256, U256>,
}
#[derive(Clone, Debug)]
pub struct CallOutput {
pub flags: u32,
pub data: Vec<u8>,
}
impl Default for CallOutput {
fn default() -> Self {
Self {
flags: u32::MAX,
data: Vec::new(),
}
}
}
impl State {
pub fn new(input: Vec<u8>) -> Self {
Self {
input,
..Default::default()
}
}
pub fn reset_output(&mut self) {
self.output = Default::default();
}
pub fn assert_storage_key(&self, at: U256, expect: U256) {
assert_eq!(self.storage[&at], expect);
}
}
fn link_host_functions(engine: &Engine) -> Linker<State> {
let mut linker = Linker::new(engine);
linker
.func_wrap(
"input",
|caller: Caller<State>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
let (mut caller, state) = caller.split();
assert_ne!(0, caller.read_u32(out_len_ptr)?);
caller.write_memory(out_ptr, &state.input)?;
caller.write_memory(out_len_ptr, &(state.input.len() as u32).encode())?;
Ok(())
},
)
.unwrap();
linker
.func_wrap(
"seal_return",
|caller: Caller<State>, flags: u32, data_ptr: u32, data_len: u32| -> Result<(), Trap> {
let (caller, state) = caller.split();
state.output.flags = flags;
state.output.data = caller.read_memory_into_vec(data_ptr, data_len)?;
Err(Default::default())
},
)
.unwrap();
linker
.func_wrap(
"value_transferred",
|caller: Caller<State>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
let (mut caller, state) = caller.split();
let value = state.value.encode();
caller.write_memory(out_ptr, &value)?;
caller.write_memory(out_len_ptr, &(value.len() as u32).encode())?;
Ok(())
},
)
.unwrap();
linker
.func_wrap(
"debug_message",
|caller: Caller<State>, str_ptr: u32, str_len: u32| -> Result<u32, Trap> {
let (caller, _) = caller.split();
let data = caller.read_memory_into_vec(str_ptr, str_len)?;
print!("debug_message: {}", String::from_utf8(data).unwrap());
Ok(0)
},
)
.unwrap();
linker
.func_wrap(
"set_storage",
|caller: Caller<State>,
key_ptr: u32,
key_len: u32,
value_ptr: u32,
value_len: u32|
-> Result<u32, Trap> {
let (caller, state) = caller.split();
assert_eq!(key_len, 32, "storage key must be 32 bytes");
assert_eq!(value_len, 32, "storage value must be 32 bytes");
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
let value = caller.read_memory_into_vec(value_ptr, value_len)?;
state.storage.insert(
U256::from_be_bytes::<32>(key.try_into().unwrap()),
U256::from_be_bytes::<32>(value.try_into().unwrap()),
);
Ok(0)
},
)
.unwrap();
linker
.func_wrap(
"get_storage",
|caller: Caller<State>,
key_ptr: u32,
key_len: u32,
out_ptr: u32,
out_len_ptr: u32|
-> Result<u32, Trap> {
let (mut caller, state) = caller.split();
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
let out_len = caller.read_u32(out_len_ptr)?;
assert!(out_len >= 32);
let value = state
.storage
.get(&U256::from_be_bytes::<32>(key.try_into().unwrap()))
.map(U256::to_be_bytes::<32>)
.unwrap_or_default();
caller.write_memory(out_ptr, &value[..])?;
caller.write_memory(out_len_ptr, &32u32.to_le_bytes())?;
Ok(0)
},
)
.unwrap();
linker
}
pub fn prepare(code: &[u8]) -> (InstancePre<State>, ExportIndex) {
let blob = ProgramBlob::parse(code).unwrap();
let engine = Engine::new(&Config::new()).unwrap();
let mut module_config = ModuleConfig::new();
module_config.set_gas_metering(Some(GasMeteringKind::Sync));
let module = Module::from_blob(&engine, &module_config, &blob).unwrap();
let export = module.lookup_export("call").unwrap();
let func = link_host_functions(&engine)
.instantiate_pre(&module)
.unwrap();
(func, export)
}
pub fn call(mut state: State, on: &InstancePre<State>, export: ExportIndex) -> State {
state.reset_output();
let mut state_args = polkavm::StateArgs::default();
state_args.set_gas(polkavm::Gas::MAX);
let call_args = polkavm::CallArgs::new(&mut state, export);
match on.instantiate().unwrap().call(state_args, call_args) {
Err(polkavm::ExecutionError::Trap(_)) => state,
Err(other) => panic!("unexpected error: {other}"),
Ok(_) => panic!("unexpected return"),
}
}
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "revive-ir"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
evmil = { workspace = true }
petgraph = { workspace = true }
primitive-types = { workspace = true }
indexmap = { workspace = true }
revive-compilation-target = { path = "../compilation-target" }
-58
View File
@@ -1,58 +0,0 @@
use primitive_types::U256;
#[derive(Clone, Copy)]
pub enum Kind {
Constant(U256),
Temporary(usize),
Stack,
}
#[derive(Clone, Copy)]
pub struct Address {
pub kind: Kind,
pub type_hint: Option<Type>,
}
impl From<(Kind, Option<Type>)> for Address {
fn from(value: (Kind, Option<Type>)) -> Self {
Self {
kind: value.0,
type_hint: value.1,
}
}
}
impl Address {
pub fn new(kind: Kind, type_hint: Option<Type>) -> Self {
Self { kind, type_hint }
}
}
#[derive(Clone, Copy)]
pub enum Type {
Int { size: u16 },
Bytes { size: u8 },
Bool,
}
impl Type {
pub fn int(size: u16) -> Self {
Self::Int { size }
}
fn bytes(size: u8) -> Self {
Self::Bytes { size }
}
}
impl Default for Type {
fn default() -> Self {
Type::Bytes { size: 32 }
}
}
pub enum LinearMemory {
CallData,
Memory,
ReturnData,
}
-60
View File
@@ -1,60 +0,0 @@
use indexmap::{IndexMap, IndexSet};
use petgraph::prelude::*;
use crate::{
analysis::BlockAnalysis,
cfg::{Branch, Program},
instruction::Instruction,
symbol::Kind,
};
/// Remove basic blocks not reachable from the start node.
#[derive(Default)]
pub struct ReachableCode(pub IndexSet<NodeIndex>);
impl BlockAnalysis for ReachableCode {
fn analyze_block(&mut self, node: NodeIndex, _program: &mut Program) {
self.0.insert(node);
}
fn apply_results(&mut self, program: &mut Program) {
program.cfg.graph.retain_nodes(|_, i| self.0.contains(&i));
}
}
/// Remove edges to the jump table if the jump target is statically known.
#[derive(Default)]
pub struct StaticJumps(IndexMap<EdgeIndex, (NodeIndex, NodeIndex)>);
impl BlockAnalysis for StaticJumps {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) {
for edge in program.cfg.graph.edges(node) {
if *edge.weight() == Branch::Static {
continue;
}
if let Some(Instruction::ConditionalBranch { target, .. })
| Some(Instruction::UncoditionalBranch { target }) =
program.cfg.graph[node].instructions.last()
{
let Kind::Constant(bytecode_offset) = target.symbol().kind else {
continue;
};
let destination = program
.jump_targets
.get(&bytecode_offset.as_usize())
.unwrap_or(&program.cfg.invalid_jump);
self.0.insert(edge.id(), (node, *destination));
}
}
}
fn apply_results(&mut self, program: &mut Program) {
for (edge, (a, b)) in &self.0 {
program.cfg.graph.remove_edge(*edge);
program.cfg.graph.add_edge(*a, *b, Branch::Static);
}
}
}
-19
View File
@@ -1,19 +0,0 @@
use crate::{cfg::Program, instruction::Instruction, symbol::Type, POINTER_SIZE};
use petgraph::prelude::*;
use super::BlockAnalysis;
#[derive(Default)]
pub struct Unstack;
impl BlockAnalysis for Unstack {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) {
for instruction in &program.cfg.graph[node].instructions {
match instruction {
_ => {}
}
}
}
fn apply_results(&mut self, _program: &mut Program) {}
}
-373
View File
@@ -1,373 +0,0 @@
use indexmap::{IndexMap, IndexSet};
use petgraph::prelude::*;
use crate::{
analysis::BlockAnalysis,
cfg::{Program, StackInfo},
instruction::{Instruction, Operator},
symbol::{Global, Kind, Symbol, SymbolBuilder, SymbolRef, SymbolTable},
};
#[derive(Default)]
pub struct IrBuilder;
impl BlockAnalysis for IrBuilder {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) {
let mut builder = BlockBuilder::new(node, &mut program.symbol_table);
for opcode in &program.evm_instructions[program.cfg.graph[node].opcodes.to_owned()] {
builder.translate(&opcode.instruction);
}
let (instructions, stack_info) = builder.done();
program.cfg.graph[node].instructions = instructions;
program.cfg.graph[node].stack_info = stack_info;
}
fn apply_results(&mut self, _program: &mut Program) {}
}
pub struct BlockBuilder<'tbl> {
state: State<'tbl>,
instructions: Vec<Instruction>,
}
impl<'tbl> BlockBuilder<'tbl> {
fn new(node: NodeIndex, symbol_table: &'tbl mut SymbolTable) -> Self {
Self {
state: State::new(node, symbol_table),
instructions: Default::default(),
}
}
fn done(self) -> (Vec<Instruction>, StackInfo) {
let stack_info = StackInfo {
arguments: self.state.borrows,
generates: self.state.stack,
height: self.state.height,
};
assert_eq!(
stack_info.arguments as i32 + stack_info.height,
stack_info.generates.len() as i32,
"local stack elements must equal stack arguments taken + local height"
);
(self.instructions, stack_info)
}
fn translate(&mut self, opcode: &evmil::bytecode::Instruction) {
use evmil::bytecode::Instruction::*;
self.instructions.extend(match opcode {
JUMPDEST => Vec::new(),
PUSH(bytes) => {
self.state.push(Symbol::builder().constant(bytes));
Vec::new()
}
POP => {
self.state.pop();
Vec::new()
}
SWAP(n) => self.state.swap(*n as usize),
DUP(n) => vec![Instruction::Copy {
y: self.state.nth(*n as usize),
x: self.state.push(Symbol::builder().variable()),
}],
ADD => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::Add,
}],
SUB => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::Sub,
}],
MSTORE => vec![Instruction::IndexedAssign {
x: self.state.symbol_table.global(Global::Memory),
index: self.state.pop(),
y: self.state.pop(),
}],
MLOAD => vec![Instruction::IndexedCopy {
index: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
y: self.state.symbol_table.global(Global::Memory),
}],
JUMP => vec![Instruction::UncoditionalBranch {
target: self.state.pop(),
}],
JUMPI => vec![Instruction::ConditionalBranch {
target: self.state.pop(),
condition: self.state.pop(),
}],
CALLDATACOPY => vec![Instruction::Procedure {
symbol: Global::CallDataCopy,
parameters: vec![self.state.pop(), self.state.pop(), self.state.pop()],
}],
CALLDATALOAD => vec![Instruction::IndexedCopy {
index: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
y: self.state.symbol_table.global(Global::CallData),
}],
RETURN => vec![Instruction::Procedure {
symbol: Global::Return,
parameters: vec![self.state.pop(), self.state.pop()],
}],
GT => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::GreaterThan,
}],
LT => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::LessThan,
}],
EQ => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::Equal,
}],
ISZERO => vec![Instruction::UnaryAssign {
y: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::IsZero,
}],
_ => {
eprintln!("unimplement instruction: {opcode}");
Vec::new()
}
})
}
}
struct State<'tbl> {
node: NodeIndex,
symbol_table: &'tbl mut SymbolTable,
stack: Vec<SymbolRef>,
/// Every pop on an empty stack was counts as an additional argument.
borrows: usize,
/// Caches the arguments the block borrows from the stack.
arguments: IndexMap<usize, SymbolRef>,
/// Tracks the relative stack height:
/// - Pushes increase the height by one
/// - Pops decrease the height by one
height: i32,
}
impl<'tbl> State<'tbl> {
fn new(node: NodeIndex, symbol_table: &'tbl mut SymbolTable) -> Self {
Self {
node,
symbol_table,
stack: Default::default(),
borrows: Default::default(),
arguments: Default::default(),
height: Default::default(),
}
}
fn pop(&mut self) -> SymbolRef {
self.height -= 1;
self.stack.pop().unwrap_or_else(|| {
self.borrows += 1;
self.nth(0)
})
}
fn push(&mut self, builder: SymbolBuilder<(), Kind>) -> SymbolRef {
let symbol = builder.temporary().done();
let symbol = self.symbol_table.insert(self.node, symbol);
self.stack.push(symbol.clone());
self.height += 1;
symbol
}
fn swap(&mut self, n: usize) -> Vec<Instruction> {
// For free if both elements are local to the basic block
let top = self.stack.len().saturating_sub(1);
if n <= top {
self.stack.swap(top - n, top);
return Vec::new();
}
let tmp = self.symbol_table.temporary(self.node);
let a = self.nth(0);
let b = self.nth(n);
vec![
Instruction::Copy {
x: tmp.clone(),
y: a.clone(),
},
Instruction::Copy { x: a, y: b.clone() },
Instruction::Copy { x: b, y: tmp },
]
}
fn nth(&mut self, n: usize) -> SymbolRef {
self.stack
.iter()
.rev()
.nth(n)
.or_else(|| self.arguments.get(&(self.slot(n) as usize)))
.cloned()
.unwrap_or_else(|| {
let builder = Symbol::builder().stack(self.slot(n)).variable();
let symbol = self.symbol_table.insert(self.node, builder.done());
self.arguments.insert(self.slot(n) as usize, symbol.clone());
symbol
})
}
fn slot(&self, n: usize) -> i32 {
n as i32 - (self.stack.len() as i32 - self.borrows as i32)
}
}
#[cfg(test)]
mod tests {
use super::{BlockBuilder, State};
use crate::{
cfg::StackInfo,
instruction::Instruction,
symbol::{Symbol, SymbolTable},
};
use evmil::bytecode::Instruction::*;
fn translate<'tbl>(code: &[evmil::bytecode::Instruction]) -> (Vec<Instruction>, StackInfo) {
code.iter()
.fold(
BlockBuilder::new(Default::default(), &mut SymbolTable::default()),
|mut builder, instruction| {
builder.translate(instruction);
builder
},
)
.done()
}
#[test]
fn stack_slot_works() {
let mut symbol_table = SymbolTable::default();
let mut state = State::new(Default::default(), &mut symbol_table);
state.push(Symbol::builder().variable());
assert_eq!(state.slot(0), -1);
assert_eq!(state.slot(1), 0);
assert_eq!(state.slot(2), 1);
state.pop();
state.pop();
assert_eq!(state.slot(0), 1);
assert_eq!(state.slot(1), 2);
assert_eq!(state.slot(2), 3);
state.push(Symbol::builder().variable());
state.push(Symbol::builder().variable());
assert_eq!(state.slot(0), -1);
assert_eq!(state.slot(1), 0);
assert_eq!(state.slot(2), 1);
}
#[test]
fn push_works() {
let state = translate(&[PUSH(vec![1])]).1;
assert_eq!(state.height, 1);
assert_eq!(state.arguments, 0);
assert_eq!(state.generates.len(), 1);
}
#[test]
fn add_works() {
let state = translate(&[ADD]).1;
assert_eq!(state.height, -1);
assert_eq!(state.arguments, 2);
assert_eq!(state.generates.len(), 1);
}
#[test]
fn dup_works() {
let state = translate(&[DUP(4)]).1;
assert_eq!(state.height, 1);
assert_eq!(state.arguments, 0);
assert_eq!(state.generates.len(), 1);
}
#[test]
fn swap_works() {
let state = translate(&[SWAP(4)]).1;
assert_eq!(state.height, 0);
assert_eq!(state.arguments, 0);
assert_eq!(state.generates.len(), 0);
}
#[test]
fn jump() {
let state = translate(&[JUMP]).1;
assert_eq!(state.height, -1);
assert_eq!(state.arguments, 1);
assert_eq!(state.generates.len(), 0);
}
#[test]
fn pop5_push2() {
let state = translate(&[POP, POP, POP, POP, POP, PUSH(vec![1]), PUSH(vec![1])]).1;
assert_eq!(state.height, -3);
assert_eq!(state.arguments, 5);
assert_eq!(state.generates.len(), 2);
}
#[test]
fn fibonacci_loop_body() {
let state = translate(&[
PUSH(vec![1]),
ADD,
SWAP(2),
DUP(1),
SWAP(4),
ADD,
SWAP(2),
PUSH(vec![10]),
JUMP,
])
.1;
assert_eq!(state.height, 0);
assert_eq!(state.arguments, 1);
assert_eq!(state.generates.len(), 1);
}
}
-31
View File
@@ -1,31 +0,0 @@
use petgraph::prelude::*;
use crate::cfg::Program;
pub mod control_flow;
pub mod dominance;
pub mod evm_bytecode;
pub mod types;
/// The analyzer visits each basic block using DFS.
pub trait BlockAnalysis: Default {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program);
fn apply_results(&mut self, program: &mut Program);
}
pub fn analyze<Pass>(program: &mut Program) -> Pass
where
Pass: BlockAnalysis,
{
let mut dfs = Dfs::new(&program.cfg.graph, program.cfg.start);
let mut pass = Pass::default();
while let Some(node) = dfs.next(&program.cfg.graph) {
pass.analyze_block(node, program);
}
pass.apply_results(program);
pass
}
-10
View File
@@ -1,10 +0,0 @@
use indexmap::IndexMap;
use petgraph::prelude::*;
use crate::{
cfg::{Branch, Program},
instruction::Instruction,
symbol::Kind,
};
use super::BlockAnalysis;
-42
View File
@@ -1,42 +0,0 @@
use crate::{cfg::Program, instruction::Instruction, symbol::Type, POINTER_SIZE};
use petgraph::prelude::*;
use super::BlockAnalysis;
#[derive(Default)]
pub struct TypePropagation;
impl BlockAnalysis for TypePropagation {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) {
for instruction in &program.cfg.graph[node].instructions {
match instruction {
Instruction::ConditionalBranch { condition, target } => {
condition.replace_type(Type::Bool);
target.replace_type(Type::Int(POINTER_SIZE));
}
Instruction::UncoditionalBranch { target } => {
target.replace_type(Type::Int(POINTER_SIZE));
}
Instruction::BinaryAssign { x, y, z, .. } => {
y.replace_type(x.symbol().type_hint);
z.replace_type(x.symbol().type_hint);
}
Instruction::Copy { x, y } | Instruction::UnaryAssign { x, y, .. } => {
x.replace_type(y.symbol().type_hint);
}
Instruction::IndexedCopy { index, .. }
| Instruction::IndexedAssign { index, .. } => {
index.replace_type(Type::Int(POINTER_SIZE))
}
_ => {}
}
}
}
fn apply_results(&mut self, _program: &mut Program) {}
}
-241
View File
@@ -1,241 +0,0 @@
use std::fmt::Write;
use std::ops::Range;
use indexmap::IndexMap;
use petgraph::dot::{Config, Dot};
use petgraph::prelude::*;
use crate::pass::dead_code::DeadCodeElimination;
use crate::pass::lift::BytecodeLifter;
use crate::pass::Pass;
use crate::symbol::SymbolRef;
use crate::{instruction::Instruction, symbol::SymbolTable};
pub struct Cfg {
pub graph: StableDiGraph<BasicBlock, Branch>,
pub start: NodeIndex,
pub jump_table: NodeIndex,
pub terminator: NodeIndex,
pub invalid_jump: NodeIndex,
}
#[derive(Debug, PartialEq)]
pub enum Branch {
Static,
Dynamic,
}
impl Default for Cfg {
fn default() -> Self {
let mut graph = StableDiGraph::new();
Self {
start: graph.add_node(Default::default()),
jump_table: graph.add_node(Default::default()),
terminator: graph.add_node(Default::default()),
invalid_jump: graph.add_node(Default::default()),
graph,
}
}
}
#[derive(Clone, Debug)]
pub struct EvmInstruction {
pub bytecode_offset: usize,
pub instruction: evmil::bytecode::Instruction,
}
#[derive(Debug, Default)]
pub struct BasicBlock {
pub opcodes: Range<usize>,
pub instructions: Vec<Instruction>,
pub stack_info: StackInfo,
}
#[derive(Debug, Default)]
pub struct StackInfo {
pub arguments: usize,
pub generates: Vec<SymbolRef>,
pub height: i32,
}
impl BasicBlock {
fn linear_at(start: usize) -> Self {
Self {
opcodes: start..start + 1,
..Default::default()
}
}
fn format(&self, evm_bytecode: &[EvmInstruction], options: BasicBlockFormatOption) -> String {
match options {
BasicBlockFormatOption::ByteCode => evm_bytecode[self.opcodes.start..self.opcodes.end]
.iter()
.fold(String::new(), |mut acc, opcode| {
writeln!(&mut acc, "{:?}", opcode.instruction).unwrap();
acc
}),
BasicBlockFormatOption::Ir => {
self.instructions
.iter()
.fold(String::new(), |mut acc, instruction| {
writeln!(&mut acc, "{instruction}").unwrap();
acc
})
}
_ => String::new(),
}
}
}
#[derive(Clone, Copy, Default)]
pub enum BasicBlockFormatOption {
ByteCode,
Ir,
#[default]
None,
}
pub struct Program {
pub evm_instructions: Vec<EvmInstruction>,
pub cfg: Cfg,
pub symbol_table: SymbolTable,
pub jump_targets: IndexMap<usize, NodeIndex>,
}
impl Program {
/// Create a new [Program] from EVM bytecode.
///
/// - Dynamic jumps reach the dynamic jump table
/// - `JUMPDEST` and `JUMPI` split up the node
/// - Instructions not returning reach the terminator node
pub fn new(bytecode: &[evmil::bytecode::Instruction]) -> Self {
let mut evm_instructions = Vec::with_capacity(bytecode.len());
let mut cfg = Cfg::default();
let mut jump_targets = IndexMap::default();
let mut bytecode_offset = 0;
let mut node = cfg.graph.add_node(Default::default());
cfg.graph.add_edge(cfg.start, node, Branch::Static);
cfg.graph
.add_edge(cfg.invalid_jump, cfg.terminator, Branch::Static);
cfg.graph
.add_edge(cfg.jump_table, cfg.invalid_jump, Branch::Dynamic);
for (index, opcode) in bytecode.iter().enumerate() {
evm_instructions.push(EvmInstruction {
bytecode_offset,
instruction: opcode.clone(),
});
cfg.graph[node].opcodes.end = index + 1;
use evmil::bytecode::Instruction::*;
match opcode {
// The preceding instruction did already split up control flow
JUMPDEST
if matches!(
evm_instructions[index.saturating_sub(1)].instruction,
JUMP | JUMPI | RETURN | REVERT | INVALID | STOP | SELFDESTRUCT
) =>
{
cfg.graph.add_edge(cfg.jump_table, node, Branch::Dynamic);
jump_targets.insert(bytecode_offset, node);
}
JUMPDEST => {
cfg.graph[node].opcodes.end = index;
let previous_node = node;
node = cfg.graph.add_node(BasicBlock::linear_at(index));
cfg.graph.add_edge(cfg.jump_table, node, Branch::Dynamic);
cfg.graph.add_edge(previous_node, node, Branch::Static);
jump_targets.insert(bytecode_offset, node);
}
JUMP => {
cfg.graph.add_edge(node, cfg.jump_table, Branch::Dynamic);
node = cfg.graph.add_node(BasicBlock::linear_at(index + 1));
}
JUMPI => {
cfg.graph.add_edge(node, cfg.jump_table, Branch::Dynamic);
let previous_node = node;
node = cfg.graph.add_node(BasicBlock::linear_at(index + 1));
cfg.graph.add_edge(previous_node, node, Branch::Static);
}
STOP | RETURN | REVERT | INVALID | SELFDESTRUCT => {
cfg.graph.add_edge(node, cfg.terminator, Branch::Static);
node = cfg.graph.add_node(BasicBlock::linear_at(index + 1));
}
_ => {}
}
bytecode_offset += opcode.length();
}
Self {
evm_instructions,
cfg,
symbol_table: Default::default(),
jump_targets,
}
}
pub fn optimize(&mut self) {
DeadCodeElimination::run(&mut Default::default(), self);
BytecodeLifter::run(&mut Default::default(), self);
DeadCodeElimination::run(&mut Default::default(), self)
}
pub fn dot(&self, format_options: BasicBlockFormatOption) {
let get_node_attrs = move |_, (index, node): (_, &BasicBlock)| {
let (color, shape, label) = if index == self.cfg.terminator {
("red", "oval", "Terminator".to_string())
} else if index == self.cfg.start {
("red", "oval", "Start".to_string())
} else if index == self.cfg.invalid_jump {
("blue", "hexagon", "Invalid jump target".to_string())
} else if index == self.cfg.jump_table {
("blue", "diamond", "Dynamic jump table".to_string())
} else {
let instructions = node.format(&self.evm_instructions, format_options);
let start = &self.evm_instructions[node.opcodes.start].bytecode_offset;
let end = &self
.evm_instructions
.get(node.opcodes.end)
.unwrap_or_else(|| &self.evm_instructions[node.opcodes.end - 1])
.bytecode_offset;
(
"black",
"rectangle",
format!("Bytecode (0x{start:02x}, 0x{end:02x}]\n---\n{instructions}",),
)
};
format!("color={color} shape={shape} label=\"{label}\"",)
};
let get_edge_attrs = |_, edge: petgraph::stable_graph::EdgeReference<'_, Branch>| {
let style = match edge.weight() {
Branch::Static => "solid",
Branch::Dynamic => "dashed",
};
format!("style={style}")
};
let dot = Dot::with_attr_getters(
&self.cfg.graph,
&[Config::EdgeNoLabel, Config::NodeNoLabel],
&get_edge_attrs,
&get_node_attrs,
);
println!("{dot:?}");
}
}
-143
View File
@@ -1,143 +0,0 @@
use crate::symbol::{Global, SymbolRef};
use std::fmt::Write;
#[derive(PartialEq, Debug)]
pub enum Instruction {
Nop,
/// `x = y op z`
BinaryAssign {
x: SymbolRef,
y: SymbolRef,
operator: Operator,
z: SymbolRef,
},
/// `x = op y`
UnaryAssign {
x: SymbolRef,
operator: Operator,
y: SymbolRef,
},
/// `branch target`
UncoditionalBranch {
target: SymbolRef,
},
/// `branch target if condition`
ConditionalBranch {
condition: SymbolRef,
target: SymbolRef,
},
/// `call(label, n)`
Procedure {
symbol: Global,
parameters: Vec<SymbolRef>,
},
/// `x = call(label, n)`
Function {
symbol: Global,
x: SymbolRef,
parameters: Vec<SymbolRef>,
},
/// `x = y`
Copy {
x: SymbolRef,
y: SymbolRef,
},
/// `x[index] = y`
IndexedAssign {
x: SymbolRef,
index: SymbolRef,
y: SymbolRef,
},
/// `x = y[index]`
IndexedCopy {
x: SymbolRef,
y: SymbolRef,
index: SymbolRef,
},
}
impl std::fmt::Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BinaryAssign { x, y, operator, z } => write!(f, "{x} = {y} {operator:?} {z}"),
Self::UnaryAssign { x, operator, y } => write!(f, "{x} = {operator:?} {y} "),
Self::UncoditionalBranch { target } => write!(f, "branch {target}"),
Self::ConditionalBranch { condition, target } => {
write!(f, "if {condition} branch {target}")
}
Self::Procedure { symbol, parameters } => write!(
f,
"{symbol:?}({})",
parameters.iter().fold(String::new(), |mut acc, p| {
write!(&mut acc, "{p}, ").unwrap();
acc
})
),
Self::Function {
symbol,
x,
parameters: args,
} => write!(
f,
"{x} = {symbol:?}({})",
args.iter().fold(String::new(), |mut acc, p| {
write!(&mut acc, "{p}, ").unwrap();
acc
})
),
Self::Copy { x, y } => write!(f, "{x} = {y}"),
Self::IndexedAssign { x, index, y } => write!(f, "{x}[{index}] = {y}"),
Self::IndexedCopy { x, y, index } => write!(f, "{x} = {y}[{index}]"),
Self::Nop => write!(f, "no-op"),
}
}
}
#[derive(PartialEq, Debug)]
pub enum Operator {
Add,
Mul,
Sub,
Div,
SDiv,
Mod,
SMod,
AddMod,
MulMod,
Exp,
SignExtend,
LessThan,
GreaterThan,
SignedLessThan,
SignedGreaterThan,
Equal,
IsZero,
And,
Or,
Xor,
Not,
Byte,
ShiftLeft,
ShiftRight,
ShiftArithmeticRight,
}
-14
View File
@@ -1,14 +0,0 @@
pub mod analysis;
pub mod cfg;
pub mod instruction;
pub mod pass;
pub mod symbol;
pub static POINTER_SIZE: usize = 32;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {}
}
-15
View File
@@ -1,15 +0,0 @@
use crate::{
analysis::{analyze, control_flow::ReachableCode},
cfg::Program,
};
use super::Pass;
#[derive(Default)]
pub struct DeadCodeElimination;
impl Pass for DeadCodeElimination {
fn run(&mut self, program: &mut Program) {
analyze::<ReachableCode>(program);
}
}
-19
View File
@@ -1,19 +0,0 @@
use crate::{
analysis::{
analyze, control_flow::StaticJumps, evm_bytecode::IrBuilder, types::TypePropagation,
},
cfg::Program,
};
use super::Pass;
#[derive(Default)]
pub struct BytecodeLifter;
impl Pass for BytecodeLifter {
fn run(&mut self, program: &mut Program) {
analyze::<IrBuilder>(program);
analyze::<StaticJumps>(program);
analyze::<TypePropagation>(program);
}
}
-8
View File
@@ -1,8 +0,0 @@
use crate::cfg::Program;
pub mod dead_code;
pub mod lift;
pub trait Pass: Default {
fn run(&mut self, program: &mut Program);
}
-317
View File
@@ -1,317 +0,0 @@
use indexmap::IndexMap;
use petgraph::prelude::NodeIndex;
use primitive_types::U256;
use std::{cell::RefCell, rc::Rc};
use crate::POINTER_SIZE;
#[derive(Debug, Default)]
pub struct SymbolTable {
table: IndexMap<NodeIndex, IndexMap<usize, Rc<RefCell<Symbol>>>>,
symbols: IndexMap<usize, Rc<RefCell<Symbol>>>,
global_scope: NodeIndex,
id_nonce: usize,
}
impl SymbolTable {
pub fn merge_scopes(&mut self, node: NodeIndex, target: NodeIndex) {
let sym = self.symbols.remove(&0).unwrap();
let new = self
.table
.get(&NodeIndex::default())
.unwrap()
.get(&0)
.unwrap();
//RefCell::replace(&sym, Rc::clone(new));
}
pub fn get_symbol(&self, id: usize) -> SymbolRef {
SymbolRef {
inner: self.symbols.get(&id).unwrap().clone(),
id,
}
}
pub fn insert(&mut self, scope: NodeIndex, symbol: Symbol) -> SymbolRef {
let id = self.next();
let inner = Rc::new(RefCell::new(symbol));
self.table
.entry(scope)
.or_default()
.insert(id, Rc::clone(&inner));
self.symbols.insert(id, inner.clone());
SymbolRef { inner, id }
}
pub fn global(&mut self, label: Global) -> SymbolRef {
self.table
.entry(self.global_scope)
.or_default()
.iter()
.find(|(_, symbol)| symbol.borrow().address == Address::Label(label))
.map(|(id, _)| *id)
.map(|id| self.get_symbol(id))
.unwrap_or_else(|| self.insert(self.global_scope, Symbol::builder().global(label)))
}
pub fn temporary(&mut self, node: NodeIndex) -> SymbolRef {
self.insert(node, Symbol::builder().temporary().variable().done())
}
fn next(&mut self) -> usize {
let current = self.id_nonce;
self.id_nonce += 1;
current
}
}
#[derive(Default)]
pub struct SymbolBuilder<A = (), K = ()> {
address: A,
type_hint: Type,
kind: K,
}
impl<K> SymbolBuilder<(), K> {
pub fn temporary(self) -> SymbolBuilder<Address, K> {
SymbolBuilder {
address: Address::Temporary,
type_hint: self.type_hint,
kind: self.kind,
}
}
pub fn stack(self, slot: i32) -> SymbolBuilder<Address, K> {
SymbolBuilder {
address: Address::Stack(slot),
type_hint: self.type_hint,
kind: self.kind,
}
}
pub fn global(self, label: Global) -> Symbol {
Symbol {
address: Address::Label(label),
type_hint: label.typ(),
kind: label.kind(),
}
}
}
impl<A> SymbolBuilder<A, ()> {
pub fn constant(self, bytes: &[u8]) -> SymbolBuilder<A, Kind> {
SymbolBuilder {
address: self.address,
type_hint: Type::Bytes(bytes.len()),
kind: Kind::Constant(U256::from_big_endian(bytes)),
}
}
pub fn variable(self) -> SymbolBuilder<A, Kind> {
SymbolBuilder {
address: self.address,
type_hint: self.type_hint,
kind: Kind::Variable,
}
}
}
impl<A, K> SymbolBuilder<A, K> {
pub fn of(self, type_hint: Type) -> Self {
Self { type_hint, ..self }
}
}
impl SymbolBuilder<Address, Kind> {
pub fn done(self) -> Symbol {
Symbol {
address: self.address,
type_hint: self.type_hint,
kind: self.kind,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Symbol {
pub address: Address,
pub type_hint: Type,
pub kind: Kind,
}
impl Symbol {
pub fn builder() -> SymbolBuilder {
Default::default()
}
}
#[derive(Clone, Debug)]
pub struct SymbolRef {
inner: Rc<RefCell<Symbol>>,
id: usize,
}
impl std::fmt::Display for SymbolRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let symbol = self.symbol();
let address = format!("${}_{}", self.id, symbol.address);
match symbol.kind {
Kind::Pointer => write!(f, "*{address}"),
Kind::Constant(value) => {
write!(f, "{} {address} := {value}", symbol.type_hint)
}
_ => write!(f, "{} {address} ", symbol.type_hint),
}
}
}
impl SymbolRef {
pub fn replace_type(&self, type_hint: Type) {
self.inner.replace_with(|old| Symbol {
address: old.address,
kind: old.kind,
type_hint,
});
}
pub fn symbol(&self) -> Symbol {
*self.inner.borrow()
}
pub fn id(&self) -> usize {
self.id
}
}
impl PartialEq for SymbolRef {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Address {
#[default]
Temporary,
Stack(i32),
Label(Global),
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Stack(slot) => write!(f, "stack[{slot}]"),
Self::Temporary => write!(f, "tmp"),
Self::Label(label) => write!(f, "{label:?}"),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy)]
pub enum Type {
#[default]
Word,
UInt(usize),
Int(usize),
Bytes(usize),
Bool,
}
impl std::fmt::Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Word => write!(f, "word"),
Self::UInt(size) => write!(f, "u{}", size),
Self::Int(size) => write!(f, "i{}", size),
Self::Bytes(size) => write!(f, "bytes{size}"),
Self::Bool => write!(f, "bool"),
}
}
}
impl Type {
pub fn pointer() -> Self {
Self::UInt(POINTER_SIZE)
}
}
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Kind {
Pointer,
#[default]
Variable,
Constant(U256),
Function,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Global {
Stack,
StackHeight,
CallData,
Memory,
ReturnData,
MemoryCopy,
// EVM runtime environment
Sha3,
Address,
CallDataLoad,
CallDataSize,
CallDataCopy,
CodeSize,
CodeCopy,
GasPrice,
ExtCodeSize,
ExtCodeCopy,
ReturnDataSize,
ReturnDataCopy,
ExtCodeHash,
BlockHash,
Coinbase,
Timestamp,
BlockNumber,
PrevRanDao,
GasLimit,
ChainId,
SelfBalance,
BaseFee,
SLoad,
SStore,
Gas,
Create,
Create2,
Call,
StaticCall,
DelegateCall,
CallCode,
Return,
Stop,
Revert,
SelfDestruct,
Event,
}
impl Global {
pub fn typ(&self) -> Type {
match self {
Self::Stack | Self::CallData | Self::Memory | Self::ReturnData => Type::pointer(),
Self::StackHeight => Type::UInt(POINTER_SIZE),
_ => Type::Word,
}
}
pub fn kind(&self) -> Kind {
match self {
Self::Stack | Self::CallData | Self::Memory | Self::ReturnData => Kind::Pointer,
Self::StackHeight => Kind::Variable,
_ => Kind::Function,
}
}
}
@@ -1,5 +1,5 @@
[package] [package]
name = "revive-target-polkavm" name = "revive-linker"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
@@ -11,8 +11,7 @@ tempfile = { workspace = true }
polkavm-linker = { workspace = true } polkavm-linker = { workspace = true }
polkavm-common = { workspace = true } polkavm-common = { workspace = true }
libc = { workspace = true } libc = { workspace = true }
anyhow = { workspace = true }
revive-codegen = { path = "../codegen" }
revive-compilation-target = { path = "../compilation-target" }
revive-builtins = { path = "../builtins" } revive-builtins = { path = "../builtins" }
lld-sys = { path = "../lld-sys" } lld-sys = { path = "../lld-sys" }
@@ -1,25 +1,11 @@
use std::{ffi::CString, fs}; use std::{env, ffi::CString, fs};
use lld_sys::LLDELFLink; use lld_sys::LLDELFLink;
use revive_builtins::COMPILER_RT; use revive_builtins::COMPILER_RT;
const LINKER_SCRIPT: &str = r#" const LINKER_SCRIPT: &str = r#"
SECTIONS { SECTIONS {
. = 0x10000;
.rodata : { *(.rodata) *(.rodata.*) }
.data.rel.ro : { *(.data.rel.ro) *(.data.rel.ro.*) }
.got : { *(.got) *(.got.*) }
. = ALIGN(0x4000);
.data : { *(.sdata) *(.data) }
.bss : { *(.sbss) *(.bss) *(.bss.*) }
. = 0xf0000000;
.text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) } .text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) }
/DISCARD/ : { *(.eh_frame) }
. = ALIGN(4);
}"#; }"#;
fn invoke_lld(cmd_args: &[&str]) -> bool { fn invoke_lld(cmd_args: &[&str]) -> bool {
@@ -33,30 +19,33 @@ fn invoke_lld(cmd_args: &[&str]) -> bool {
unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 } unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 }
} }
fn polkavm_linker(code: &[u8]) -> Vec<u8> { fn polkavm_linker<T: AsRef<[u8]>>(code: T) -> anyhow::Result<Vec<u8>> {
let mut config = polkavm_linker::Config::default(); let mut config = polkavm_linker::Config::default();
config.set_strip(true); config.set_strip(true);
match polkavm_linker::program_from_elf(config, code) { polkavm_linker::program_from_elf(config, code.as_ref())
Ok(blob) => blob.as_bytes().to_vec(), .map(|blob| blob.as_bytes().to_vec())
Err(reason) => panic!("polkavm linker failed: {}", reason), .map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
}
} }
pub(crate) fn link(input: &[u8]) -> Vec<u8> { pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
let dir = tempfile::tempdir().expect("failed to create temp directory for linking"); let dir = tempfile::tempdir().expect("failed to create temp directory for linking");
let output_path = dir.path().join("out.so"); let output_path = dir.path().join("out.so");
let object_path = dir.path().join("out.o"); let object_path = dir.path().join("out.o");
let linker_script_path = dir.path().join("linker.ld"); let linker_script_path = dir.path().join("linker.ld");
let compiler_rt_path = dir.path().join("libclang_rt.builtins-riscv32.a"); let compiler_rt_path = dir.path().join("libclang_rt.builtins-riscv32.a");
fs::write(&object_path, input).unwrap_or_else(|msg| panic!("{msg} {object_path:?}")); fs::write(&object_path, input).map_err(|msg| anyhow::anyhow!("{msg} {object_path:?}"))?;
if env::var("PVM_LINKER_DUMP_OBJ").is_ok() {
fs::copy(&object_path, "/tmp/out.o")?;
}
fs::write(&linker_script_path, LINKER_SCRIPT) fs::write(&linker_script_path, LINKER_SCRIPT)
.unwrap_or_else(|msg| panic!("{msg} {linker_script_path:?}")); .map_err(|msg| anyhow::anyhow!("{msg} {linker_script_path:?}"))?;
fs::write(&compiler_rt_path, COMPILER_RT) fs::write(&compiler_rt_path, COMPILER_RT)
.unwrap_or_else(|msg| panic!("{msg} {compiler_rt_path:?}")); .map_err(|msg| anyhow::anyhow!("{msg} {compiler_rt_path:?}"))?;
let ld_args = [ let ld_args = [
"ld.lld", "ld.lld",
@@ -75,12 +64,14 @@ pub(crate) fn link(input: &[u8]) -> Vec<u8> {
output_path.to_str().expect("should be utf8"), output_path.to_str().expect("should be utf8"),
]; ];
assert!(!invoke_lld(&ld_args), "ld.lld failed"); if invoke_lld(&ld_args) {
return Err(anyhow::anyhow!("ld.lld failed"));
}
fs::copy(&object_path, "/tmp/out.o").unwrap(); if env::var("PVM_LINKER_DUMP_SO").is_ok() {
fs::copy(&output_path, "/tmp/out.so").unwrap(); fs::copy(&output_path, "/tmp/out.so")?;
fs::copy(&linker_script_path, "/tmp/linkder.ld").unwrap(); };
let blob = fs::read(&output_path).expect("ld.lld should produce output"); let blob = fs::read(&output_path)?;
polkavm_linker(&blob) polkavm_linker(blob)
} }
+34
View File
@@ -0,0 +1,34 @@
[package]
name = "era-compiler-llvm-context"
version = "1.4.1"
authors = [
"Oleksandr Zarudnyi <a.zarudnyy@matterlabs.dev>",
]
license = "MIT OR Apache-2.0"
edition = "2021"
description = "Shared front end code of the EraVM compilers"
[lib]
doctest = false
[dependencies]
anyhow = { workspace = true }
semver = { workspace = true }
itertools = { workspace = true }
serde = { workspace = true, features = ["derive"] }
regex = { workspace = true }
once_cell = { workspace = true }
num = { workspace = true }
hex = { workspace = true }
sha2 = { workspace = true }
sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true }
zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs", branch = "v1.4.1" }
era-compiler-common = { git = "https://github.com/matter-labs/era-compiler-common", branch = "main" }
pallet-contracts-pvm-llapi = { path = "../pallet-contracts-pvm-llapi" }
revive-linker = { path = "../linker" }
revive-builtins = { path = "../builtins" }
revive-stdlib = { path = "../stdlib" }
@@ -0,0 +1,40 @@
//!
//! The debug IR type.
//!
///
/// The debug IR type.
///
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IRType {
/// Whether to dump the Yul code.
Yul,
/// Whether to dump the EVM legacy assembly code.
EVMLA,
/// Whether to dump the Ethereal IR code.
EthIR,
/// Whether to dump the Vyper LLL IR code.
LLL,
/// Whether to dump the LLVM IR code.
LLVM,
/// Whether to dump the assembly code.
Assembly,
}
impl IRType {
///
/// Returns the file extension for the specified IR.
///
pub fn file_extension(&self) -> &'static str {
match self {
Self::Yul => era_compiler_common::EXTENSION_YUL,
Self::EthIR => era_compiler_common::EXTENSION_ETHIR,
Self::EVMLA => era_compiler_common::EXTENSION_EVMLA,
Self::LLL => era_compiler_common::EXTENSION_LLL,
Self::LLVM => era_compiler_common::EXTENSION_LLVM_SOURCE,
Self::Assembly => era_compiler_common::EXTENSION_ERAVM_ASSEMBLY,
}
}
}
+140
View File
@@ -0,0 +1,140 @@
//!
//! The debug configuration.
//!
pub mod ir_type;
use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
use self::ir_type::IRType;
///
/// The debug configuration.
///
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct DebugConfig {
/// The directory to dump the IRs to.
pub output_directory: PathBuf,
}
impl DebugConfig {
///
/// A shortcut constructor.
///
pub fn new(output_directory: PathBuf) -> Self {
Self { output_directory }
}
///
/// Dumps the Yul IR.
///
pub fn dump_yul(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the EVM legacy assembly IR.
///
pub fn dump_evmla(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the Ethereal IR.
///
pub fn dump_ethir(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the LLL IR.
///
pub fn dump_lll(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::LLL);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the unoptimized LLVM IR.
///
pub fn dump_llvm_ir_unoptimized(
&self,
contract_path: &str,
module: &inkwell::module::Module,
) -> anyhow::Result<()> {
let llvm_code = module.print_to_string().to_string();
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, Some("unoptimized"), IRType::LLVM);
file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
Ok(())
}
///
/// Dumps the optimized LLVM IR.
///
pub fn dump_llvm_ir_optimized(
&self,
contract_path: &str,
module: &inkwell::module::Module,
) -> anyhow::Result<()> {
let llvm_code = module.print_to_string().to_string();
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, Some("optimized"), IRType::LLVM);
file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
Ok(())
}
///
/// Dumps the assembly.
///
pub fn dump_assembly(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Assembly);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Creates a full file name, given the contract full path, suffix, and extension.
///
fn full_file_name(contract_path: &str, suffix: Option<&str>, ir_type: IRType) -> String {
let mut full_file_name = contract_path.replace('/', "_").replace(':', ".");
if let Some(suffix) = suffix {
full_file_name.push('.');
full_file_name.push_str(suffix);
}
full_file_name.push('.');
full_file_name.push_str(ir_type.file_extension());
full_file_name
}
}
+72
View File
@@ -0,0 +1,72 @@
//!
//! The LLVM context constants.
//!
/// The LLVM framework version.
pub const LLVM_VERSION: semver::Version = semver::Version::new(15, 0, 4);
/// The EraVM version.
pub const ZKEVM_VERSION: semver::Version = semver::Version::new(1, 3, 2);
/// The heap memory pointer pointer global variable name.
pub static GLOBAL_HEAP_MEMORY_POINTER: &str = "memory_pointer";
/// The calldata pointer global variable name.
pub static GLOBAL_CALLDATA_POINTER: &str = "ptr_calldata";
/// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
/// The return data pointer global variable name.
pub static GLOBAL_RETURN_DATA_POINTER: &str = "ptr_return_data";
/// The return data size pointer global variable name.
pub static GLOBAL_RETURN_DATA_SIZE: &str = "returndatasize";
/// The call flags global variable name.
pub static GLOBAL_CALL_FLAGS: &str = "call_flags";
/// The extra ABI data global variable name.
pub static GLOBAL_EXTRA_ABI_DATA: &str = "extra_abi_data";
/// The active pointer global variable name.
pub static GLOBAL_ACTIVE_POINTER: &str = "ptr_active";
/// The constant array global variable name prefix.
pub static GLOBAL_CONST_ARRAY_PREFIX: &str = "const_array_";
/// The global verbatim getter identifier prefix.
pub static GLOBAL_VERBATIM_GETTER_PREFIX: &str = "get_global::";
/// The external call data offset in the auxiliary heap.
pub const HEAP_AUX_OFFSET_EXTERNAL_CALL: u64 = 0;
/// The constructor return data offset in the auxiliary heap.
pub const HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA: u64 =
8 * (era_compiler_common::BYTE_LENGTH_FIELD as u64);
/// The number of the extra ABI data arguments.
pub const EXTRA_ABI_DATA_SIZE: usize = 0;
/// The `create` method deployer signature.
pub static DEPLOYER_SIGNATURE_CREATE: &str = "create(bytes32,bytes32,bytes)";
/// The `create2` method deployer signature.
pub static DEPLOYER_SIGNATURE_CREATE2: &str = "create2(bytes32,bytes32,bytes)";
/// The absence of system call bit.
pub const NO_SYSTEM_CALL_BIT: bool = false;
/// The system call bit.
pub const SYSTEM_CALL_BIT: bool = true;
///
/// The deployer call header size that consists of:
/// - selector (4 bytes)
/// - salt (32 bytes)
/// - bytecode hash (32 bytes)
/// - constructor arguments offset (32 bytes)
/// - constructor arguments length (32 bytes)
///
pub const DEPLOYER_CALL_HEADER_SIZE: usize =
era_compiler_common::BYTE_LENGTH_X32 + (era_compiler_common::BYTE_LENGTH_FIELD * 4);
@@ -0,0 +1,38 @@
//!
//! The address space aliases.
//!
///
/// The address space aliases.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressSpace {
/// The stack memory.
Stack,
/// The heap memory.
Heap,
/// The auxiliary heap memory.
HeapAuxiliary,
/// The generic memory page.
Generic,
/// The code area.
Code,
/// The storage.
Storage,
/// The transient storage.
TransientStorage,
}
impl From<AddressSpace> for inkwell::AddressSpace {
fn from(value: AddressSpace) -> Self {
match value {
AddressSpace::Stack => Self::from(0),
AddressSpace::Heap => Self::from(1),
AddressSpace::HeapAuxiliary => Self::from(2),
AddressSpace::Generic => Self::from(3),
AddressSpace::Code => Self::from(4),
AddressSpace::Storage => Self::from(5),
AddressSpace::TransientStorage => Self::from(6),
}
}
}
@@ -0,0 +1,76 @@
//!
//! The LLVM argument with metadata.
//!
///
/// The LLVM argument with metadata.
///
#[derive(Debug, Clone)]
pub struct Argument<'ctx> {
/// The actual LLVM operand.
pub value: inkwell::values::BasicValueEnum<'ctx>,
/// The original AST value. Used mostly for string literals.
pub original: Option<String>,
/// The preserved constant value, if available.
pub constant: Option<num::BigUint>,
}
impl<'ctx> Argument<'ctx> {
/// The calldata offset argument index.
pub const ARGUMENT_INDEX_CALLDATA_OFFSET: usize = 0;
/// The calldata length argument index.
pub const ARGUMENT_INDEX_CALLDATA_LENGTH: usize = 1;
///
/// A shortcut constructor.
///
pub fn new(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self {
value,
original: None,
constant: None,
}
}
///
/// A shortcut constructor.
///
pub fn new_with_original(
value: inkwell::values::BasicValueEnum<'ctx>,
original: String,
) -> Self {
Self {
value,
original: Some(original),
constant: None,
}
}
///
/// A shortcut constructor.
///
pub fn new_with_constant(
value: inkwell::values::BasicValueEnum<'ctx>,
constant: num::BigUint,
) -> Self {
Self {
value,
original: None,
constant: Some(constant),
}
}
///
/// Returns the inner LLVM value.
///
pub fn to_llvm(&self) -> inkwell::values::BasicValueEnum<'ctx> {
self.value
}
}
impl<'ctx> From<inkwell::values::BasicValueEnum<'ctx>> for Argument<'ctx> {
fn from(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self::new(value)
}
}
@@ -0,0 +1,127 @@
//! The LLVM attribute.
use serde::Deserialize;
use serde::Serialize;
/// The LLVM attribute.
///
/// In order to check the real order in a new major version of LLVM, find the `Attribute.inc` file
/// inside of the LLVM build directory. This order is actually generated during the building.
///
/// FIXME: Generate this in build.rs?
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Attribute {
/// Unused (attributes start at 1).
Unused = 0,
AllocAlign = 1,
AllocatedPointer = 2,
AlwaysInline = 3,
Builtin = 4,
Cold = 5,
Convergent = 6,
DisableSanitizerInstrumentation = 7,
FnRetThunkExtern = 8,
Hot = 9,
ImmArg = 10,
InReg = 11,
InlineHint = 12,
JumpTable = 13,
MinSize = 14,
MustProgress = 15,
Naked = 16,
Nest = 17,
NoAlias = 18,
NoBuiltin = 19,
NoCallback = 20,
NoCapture = 21,
NoCfCheck = 22,
NoDuplicate = 23,
NoFree = 24,
NoImplicitFloat = 25,
NoInline = 26,
NoMerge = 27,
NoProfile = 28,
NoRecurse = 29,
NoRedZone = 30,
NoReturn = 31,
NoSanitizeBounds = 32,
NoSanitizeCoverage = 33,
NoSync = 34,
NoUndef = 35,
NoUnwind = 36,
NonLazyBind = 37,
NonNull = 38,
NullPointerIsValid = 39,
OptForFuzzing = 40,
OptimizeForSize = 41,
OptimizeNone = 42,
PresplitCoroutine = 43,
ReadNone = 44,
ReadOnly = 45,
Returned = 46,
ReturnsTwice = 47,
SExt = 48,
SafeStack = 49,
SanitizeAddress = 50,
SanitizeHWAddress = 51,
SanitizeMemTag = 52,
SanitizeMemory = 53,
SanitizeThread = 54,
ShadowCallStack = 55,
SkipProfile = 56,
Speculatable = 57,
SpeculativeLoadHardening = 58,
StackProtect = 59,
StackProtectReq = 60,
StackProtectStrong = 61,
StrictFP = 62,
SwiftAsync = 63,
SwiftError = 64,
SwiftSelf = 65,
WillReturn = 66,
WriteOnly = 67,
ZExt = 68,
// FirstTypeAttr = 69,
ByRef = 69,
ByVal = 70,
ElementType = 71,
InAlloca = 72,
Preallocated = 73,
StructRet = 74,
// LastTypeAttr = 74,
// FirstIntAttr = 75,
Alignment = 75,
AllocKind = 76,
AllocSize = 77,
Dereferenceable = 78,
DereferenceableOrNull = 79,
Memory = 80,
StackAlignment = 81,
UWTable = 82,
VScaleRange = 83,
// LastIntAttr = 83,
}
impl TryFrom<&str> for Attribute {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"AlwaysInline" => Ok(Attribute::AlwaysInline),
"Cold" => Ok(Attribute::Cold),
"Hot" => Ok(Attribute::Hot),
"MinSize" => Ok(Attribute::MinSize),
"OptimizeForSize" => Ok(Attribute::OptimizeForSize),
"NoInline" => Ok(Attribute::NoInline),
"WillReturn" => Ok(Attribute::WillReturn),
"WriteOnly" => Ok(Attribute::WriteOnly),
"ReadNone" => Ok(Attribute::ReadNone),
"ReadOnly" => Ok(Attribute::ReadOnly),
"NoReturn" => Ok(Attribute::NoReturn),
// FIXME: Not in Attributes.inc
//"InaccessibleMemOnly" => Ok(Attribute::InaccessibleMemOnly),
"MustProgress" => Ok(Attribute::MustProgress),
_ => Err(value.to_owned()),
}
}
}
@@ -0,0 +1,45 @@
//!
//! The LLVM module build.
//!
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::Serialize;
///
/// The LLVM module build.
///
#[derive(Debug, Serialize, Deserialize)]
pub struct Build {
/// The EraVM text assembly.
pub assembly_text: String,
/// The metadata hash.
pub metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>,
/// The EraVM binary bytecode.
pub bytecode: Vec<u8>,
/// The EraVM bytecode hash.
pub bytecode_hash: String,
/// The hash-to-full-path mapping of the contract factory dependencies.
pub factory_dependencies: BTreeMap<String, String>,
}
impl Build {
///
/// A shortcut constructor.
///
pub fn new(
assembly_text: String,
metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>,
bytecode: Vec<u8>,
bytecode_hash: String,
) -> Self {
Self {
assembly_text,
metadata_hash,
bytecode,
bytecode_hash,
factory_dependencies: BTreeMap::new(),
}
}
}
@@ -0,0 +1,26 @@
//!
//! The contract code types.
//!
///
/// The contract code types (deploy and runtime).
///
/// They do not represent any entities in the final bytecode, but this separation is always present
/// in the IRs used for translation to the EVM bytecode.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CodeType {
/// The deploy code.
Deploy,
/// The runtime code.
Runtime,
}
impl std::fmt::Display for CodeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Deploy => write!(f, "deploy"),
Self::Runtime => write!(f, "runtime"),
}
}
}
@@ -0,0 +1,109 @@
//!
//! The LLVM debug information.
//!
use inkwell::debug_info::AsDIScope;
use num::Zero;
///
/// The LLVM debug information.
///
pub struct DebugInfo<'ctx> {
/// The compile unit.
compile_unit: inkwell::debug_info::DICompileUnit<'ctx>,
/// The debug info builder.
builder: inkwell::debug_info::DebugInfoBuilder<'ctx>,
}
impl<'ctx> DebugInfo<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(module: &inkwell::module::Module<'ctx>) -> Self {
let (builder, compile_unit) = module.create_debug_info_builder(
true,
inkwell::debug_info::DWARFSourceLanguage::C,
module.get_name().to_string_lossy().as_ref(),
"",
"",
false,
"",
0,
"",
inkwell::debug_info::DWARFEmissionKind::Full,
0,
false,
false,
"",
"",
);
Self {
compile_unit,
builder,
}
}
///
/// Creates a function info.
///
pub fn create_function(
&self,
name: &str,
) -> anyhow::Result<inkwell::debug_info::DISubprogram<'ctx>> {
let subroutine_type = self.builder.create_subroutine_type(
self.compile_unit.get_file(),
Some(self.create_type(era_compiler_common::BIT_LENGTH_FIELD)?),
&[],
inkwell::debug_info::DIFlags::zero(),
);
let function = self.builder.create_function(
self.compile_unit.get_file().as_debug_info_scope(),
name,
None,
self.compile_unit.get_file(),
42,
subroutine_type,
true,
false,
1,
inkwell::debug_info::DIFlags::zero(),
false,
);
self.builder.create_lexical_block(
function.as_debug_info_scope(),
self.compile_unit.get_file(),
1,
1,
);
Ok(function)
}
///
/// Creates a primitive type info.
///
pub fn create_type(
&self,
bit_length: usize,
) -> anyhow::Result<inkwell::debug_info::DIType<'ctx>> {
self.builder
.create_basic_type(
"U256",
bit_length as u64,
0,
inkwell::debug_info::DIFlags::zero(),
)
.map(|basic_type| basic_type.as_type())
.map_err(|error| anyhow::anyhow!("Debug info error: {}", error))
}
///
/// Finalizes the builder.
///
pub fn finalize(&self) {
self.builder.finalize();
}
}
@@ -0,0 +1,34 @@
//!
//! The LLVM IR generator EVM legacy assembly data.
//!
use crate::eravm::context::argument::Argument;
///
/// The LLVM IR generator EVM legacy assembly data.
///
/// Describes some data that is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone)]
pub struct EVMLAData<'ctx> {
/// The Solidity compiler version.
/// Some instruction behave differenly depending on the version.
pub version: semver::Version,
/// The static stack allocated for the current function.
pub stack: Vec<Argument<'ctx>>,
}
impl<'ctx> EVMLAData<'ctx> {
/// The default stack size.
pub const DEFAULT_STACK_SIZE: usize = 64;
///
/// A shortcut constructor.
///
pub fn new(version: semver::Version) -> Self {
Self {
version,
stack: Vec::with_capacity(Self::DEFAULT_STACK_SIZE),
}
}
}
@@ -0,0 +1,41 @@
//!
//! The LLVM IR generator function block key.
//!
use crate::eravm::context::code_type::CodeType;
///
/// The LLVM IR generator function block key.
///
/// Is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key {
/// The block code type.
pub code_type: CodeType,
/// The block tag.
pub tag: num::BigUint,
}
impl Key {
///
/// A shortcut constructor.
///
pub fn new(code_type: CodeType, tag: num::BigUint) -> Self {
Self { code_type, tag }
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}_{}",
match self.code_type {
CodeType::Deploy => "dt",
CodeType::Runtime => "rt",
},
self.tag
)
}
}
@@ -0,0 +1,25 @@
//!
//! The LLVM function block EVM legacy assembly data.
//!
pub mod key;
///
/// The LLVM function block EVM legacy assembly data.
///
/// Describes some data that is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone)]
pub struct EVMLAData {
/// The initial hashes of the allowed stack states.
pub stack_hashes: Vec<md5::Digest>,
}
impl EVMLAData {
///
/// A shortcut constructor.
///
pub fn new(stack_hashes: Vec<md5::Digest>) -> Self {
Self { stack_hashes }
}
}
@@ -0,0 +1,68 @@
//!
//! The LLVM IR generator function block.
//!
pub mod evmla_data;
use self::evmla_data::EVMLAData;
///
/// The LLVM IR generator function block.
///
#[derive(Debug, Clone)]
pub struct Block<'ctx> {
/// The inner block.
inner: inkwell::basic_block::BasicBlock<'ctx>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData>,
}
impl<'ctx> Block<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(inner: inkwell::basic_block::BasicBlock<'ctx>) -> Self {
Self {
inner,
evmla_data: None,
}
}
///
/// Sets the EVM legacy assembly data.
///
pub fn set_evmla_data(&mut self, data: EVMLAData) {
self.evmla_data = Some(data);
}
///
/// The LLVM object reference.
///
pub fn inner(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.inner
}
///
/// Returns the EVM data reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evm(&self) -> &EVMLAData {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
///
/// Returns the EVM data mutable reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evm_mut(&mut self) -> &mut EVMLAData {
self.evmla_data
.as_mut()
.expect("The EVM data must have been initialized")
}
}
@@ -0,0 +1,26 @@
//!
//! The LLVM function declaration.
//!
///
/// The LLVM function declaration.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Declaration<'ctx> {
/// The function type.
pub r#type: inkwell::types::FunctionType<'ctx>,
/// The function value.
pub value: inkwell::values::FunctionValue<'ctx>,
}
impl<'ctx> Declaration<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(
r#type: inkwell::types::FunctionType<'ctx>,
value: inkwell::values::FunctionValue<'ctx>,
) -> Self {
Self { r#type, value }
}
}
@@ -0,0 +1,86 @@
//!
//! The LLVM function EVM legacy assembly data.
//!
use std::collections::BTreeMap;
use crate::eravm::context::function::block::evmla_data::key::Key as BlockKey;
use crate::eravm::context::function::block::Block;
///
/// The LLVM function EVM legacy assembly data.
///
/// Describes some data that is only relevant to the EVM legacy assembly.
///
#[derive(Debug)]
pub struct EVMLAData<'ctx> {
/// The ordinary blocks with numeric tags.
/// Is only used by the Solidity EVM compiler.
pub blocks: BTreeMap<BlockKey, Vec<Block<'ctx>>>,
/// The function stack size.
pub stack_size: usize,
}
impl<'ctx> EVMLAData<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(stack_size: usize) -> Self {
Self {
blocks: BTreeMap::new(),
stack_size,
}
}
///
/// Inserts a function block.
///
pub fn insert_block(&mut self, key: BlockKey, block: Block<'ctx>) {
if let Some(blocks) = self.blocks.get_mut(&key) {
blocks.push(block);
} else {
self.blocks.insert(key, vec![block]);
}
}
///
/// Returns the block with the specified tag and initial stack pattern.
///
/// If there is only one block, it is returned unconditionally.
///
pub fn find_block(
&self,
key: &BlockKey,
stack_hash: &md5::Digest,
) -> anyhow::Result<Block<'ctx>> {
if self
.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.len()
== 1
{
return self
.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.first()
.cloned()
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key));
}
self.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.iter()
.find(|block| {
block
.evm()
.stack_hashes
.iter()
.any(|hash| hash == stack_hash)
})
.cloned()
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))
}
}
@@ -0,0 +1,154 @@
//!
//! The LLVM intrinsic functions.
//!
use inkwell::types::BasicType;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
///
/// The LLVM intrinsic functions, implemented in the LLVM back-end.
///
/// Most of them are translated directly into bytecode instructions.
///
#[derive(Debug)]
pub struct Intrinsics<'ctx> {
/// The trap.
pub trap: FunctionDeclaration<'ctx>,
/// The memory copy within the heap.
pub memory_copy: FunctionDeclaration<'ctx>,
/// The memory copy from a generic page.
pub memory_copy_from_generic: FunctionDeclaration<'ctx>,
/// Performs endianness swaps on i256 values
pub byte_swap: FunctionDeclaration<'ctx>,
}
impl<'ctx> Intrinsics<'ctx> {
/// The corresponding intrinsic function name.
pub const FUNCTION_TRAP: &'static str = "llvm.trap";
/// The corresponding intrinsic function name.
pub const FUNCTION_MEMORY_COPY: &'static str = "llvm.memcpy.p1.p1.i256";
/// The corresponding intrinsic function name.
pub const FUNCTION_MEMORY_COPY_FROM_GENERIC: &'static str = "llvm.memcpy.p3.p1.i256";
/// The corresponding intrinsic function name.
pub const FUNCTION_BYTE_SWAP: &'static str = "llvm.bswap.i256";
///
/// A shortcut constructor.
///
pub fn new(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
) -> Self {
let void_type = llvm.void_type();
let bool_type = llvm.bool_type();
let byte_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32);
let field_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32);
let _stack_field_pointer_type = field_type.ptr_type(AddressSpace::Stack.into());
let heap_field_pointer_type = byte_type.ptr_type(AddressSpace::Heap.into());
let generic_byte_pointer_type = byte_type.ptr_type(AddressSpace::Generic.into());
let trap = Self::declare(
llvm,
module,
Self::FUNCTION_TRAP,
void_type.fn_type(&[], false),
);
let memory_copy = Self::declare(
llvm,
module,
Self::FUNCTION_MEMORY_COPY,
void_type.fn_type(
&[
heap_field_pointer_type.as_basic_type_enum().into(),
heap_field_pointer_type.as_basic_type_enum().into(),
field_type.as_basic_type_enum().into(),
bool_type.as_basic_type_enum().into(),
],
false,
),
);
let memory_copy_from_generic = Self::declare(
llvm,
module,
Self::FUNCTION_MEMORY_COPY_FROM_GENERIC,
void_type.fn_type(
&[
heap_field_pointer_type.as_basic_type_enum().into(),
generic_byte_pointer_type.as_basic_type_enum().into(),
field_type.as_basic_type_enum().into(),
bool_type.as_basic_type_enum().into(),
],
false,
),
);
let byte_swap = Self::declare(
llvm,
module,
Self::FUNCTION_BYTE_SWAP,
field_type.fn_type(&[field_type.as_basic_type_enum().into()], false),
);
Self {
trap,
memory_copy,
memory_copy_from_generic,
byte_swap,
}
}
///
/// Finds the specified LLVM intrinsic function in the target and returns its declaration.
///
pub fn declare(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
name: &str,
r#type: inkwell::types::FunctionType<'ctx>,
) -> FunctionDeclaration<'ctx> {
let intrinsic = inkwell::intrinsics::Intrinsic::find(name)
.unwrap_or_else(|| panic!("Intrinsic function `{name}` does not exist"));
let argument_types = Self::argument_types(llvm, name);
let value = intrinsic
.get_declaration(module, argument_types.as_slice())
.unwrap_or_else(|| panic!("Intrinsic function `{name}` declaration error"));
FunctionDeclaration::new(r#type, value)
}
///
/// Returns the LLVM types for selecting via the signature.
///
pub fn argument_types(
llvm: &'ctx inkwell::context::Context,
name: &str,
) -> Vec<inkwell::types::BasicTypeEnum<'ctx>> {
let field_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32);
match name {
name if name == Self::FUNCTION_MEMORY_COPY => vec![
field_type
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
field_type
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
field_type.as_basic_type_enum(),
],
name if name == Self::FUNCTION_MEMORY_COPY_FROM_GENERIC => vec![
field_type
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
field_type
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
field_type.as_basic_type_enum(),
],
name if name == Self::FUNCTION_BYTE_SWAP => vec![field_type.as_basic_type_enum()],
_ => vec![],
}
}
}
@@ -0,0 +1,758 @@
//!
//! The LLVM runtime functions.
//!
use inkwell::types::BasicType;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::attribute::Attribute;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::function::Function;
use crate::optimizer::Optimizer;
///
/// The runtime functions, implemented on the LLVM side.
///
/// The functions are automatically linked to the LLVM implementations if the signatures match.
///
#[derive(Debug)]
pub struct LLVMRuntime<'ctx> {
/// The LLVM personality function, used for exception handling.
pub personality: FunctionDeclaration<'ctx>,
/// The LLVM exception throwing function.
pub cxa_throw: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub div: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sdiv: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub r#mod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub smod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub shl: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub shr: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sar: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub byte: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub add_mod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mul_mod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub exp: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sign_extend: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mstore8: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sha3: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub system_request: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
//pub far_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub far_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub static_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub static_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub delegate_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub delegate_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mimic_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mimic_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub r#return: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub revert: FunctionDeclaration<'ctx>,
}
impl<'ctx> LLVMRuntime<'ctx> {
/// The LLVM personality function name.
pub const FUNCTION_PERSONALITY: &'static str = "__personality";
/// The LLVM exception throwing function name.
pub const FUNCTION_CXA_THROW: &'static str = "__cxa_throw";
/// The corresponding runtime function name.
pub const FUNCTION_DIV: &'static str = "__div";
/// The corresponding runtime function name.
pub const FUNCTION_SDIV: &'static str = "__sdiv";
/// The corresponding runtime function name.
pub const FUNCTION_MOD: &'static str = "__mod";
/// The corresponding runtime function name.
pub const FUNCTION_SMOD: &'static str = "__smod";
/// The corresponding runtime function name.
pub const FUNCTION_SHL: &'static str = "__shl";
/// The corresponding runtime function name.
pub const FUNCTION_SHR: &'static str = "__shr";
/// The corresponding runtime function name.
pub const FUNCTION_SAR: &'static str = "__sar";
/// The corresponding runtime function name.
pub const FUNCTION_BYTE: &'static str = "__byte";
/// The corresponding runtime function name.
pub const FUNCTION_ADDMOD: &'static str = "__addmod";
/// The corresponding runtime function name.
pub const FUNCTION_MULMOD: &'static str = "__mulmod";
/// The corresponding runtime function name.
pub const FUNCTION_EXP: &'static str = "__exp";
/// The corresponding runtime function name.
pub const FUNCTION_SIGNEXTEND: &'static str = "__signextend";
/// The corresponding runtime function name.
pub const FUNCTION_MSTORE8: &'static str = "__mstore8";
/// The corresponding runtime function name.
pub const FUNCTION_SHA3: &'static str = "__sha3";
/// The corresponding runtime function name.
pub const FUNCTION_SYSTEM_REQUEST: &'static str = "__system_request";
/// The corresponding runtime function name.
pub const FUNCTION_FARCALL: &'static str = "__farcall";
/// The corresponding runtime function name.
pub const FUNCTION_STATICCALL: &'static str = "__staticcall";
/// The corresponding runtime function name.
pub const FUNCTION_DELEGATECALL: &'static str = "__delegatecall";
/// The corresponding runtime function name.
pub const FUNCTION_MIMICCALL: &'static str = "__mimiccall";
/// The corresponding runtime function name.
pub const FUNCTION_FARCALL_BYREF: &'static str = "__farcall_byref";
/// The corresponding runtime function name.
pub const FUNCTION_STATICCALL_BYREF: &'static str = "__staticcall_byref";
/// The corresponding runtime function name.
pub const FUNCTION_DELEGATECALL_BYREF: &'static str = "__delegatecall_byref";
/// The corresponding runtime function name.
pub const FUNCTION_MIMICCALL_BYREF: &'static str = "__mimiccall_byref";
/// The corresponding runtime function name.
pub const FUNCTION_RETURN: &'static str = "__return";
/// The corresponding runtime function name.
pub const FUNCTION_REVERT: &'static str = "__revert";
///
/// A shortcut constructor.
///
pub fn new(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
optimizer: &Optimizer,
) -> Self {
let personality = Self::declare(
module,
Self::FUNCTION_PERSONALITY,
llvm.i32_type().fn_type(&[], false),
None,
);
let cxa_throw = Self::declare(
module,
Self::FUNCTION_CXA_THROW,
llvm.void_type().fn_type(
vec![
llvm.i8_type()
.ptr_type(AddressSpace::Stack.into())
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_cxa_throw_attributes(llvm, cxa_throw);
let div = Self::declare(
module,
Self::FUNCTION_DIV,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, div, optimizer);
Function::set_pure_function_attributes(llvm, div);
let r#mod = Self::declare(
module,
Self::FUNCTION_MOD,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, r#mod, optimizer);
Function::set_pure_function_attributes(llvm, r#mod);
let sdiv = Self::declare(
module,
Self::FUNCTION_SDIV,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, sdiv, optimizer);
Function::set_pure_function_attributes(llvm, sdiv);
let smod = Self::declare(
module,
Self::FUNCTION_SMOD,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, smod, optimizer);
Function::set_pure_function_attributes(llvm, smod);
let shl = Self::declare(
module,
Self::FUNCTION_SHL,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, shl, optimizer);
Function::set_pure_function_attributes(llvm, shl);
let shr = Self::declare(
module,
Self::FUNCTION_SHR,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, shr, optimizer);
Function::set_pure_function_attributes(llvm, shr);
let sar = Self::declare(
module,
Self::FUNCTION_SAR,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, sar, optimizer);
Function::set_pure_function_attributes(llvm, sar);
let byte = Self::declare(
module,
Self::FUNCTION_BYTE,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, byte, optimizer);
Function::set_pure_function_attributes(llvm, byte);
let add_mod = Self::declare(
module,
Self::FUNCTION_ADDMOD,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, add_mod, optimizer);
Function::set_pure_function_attributes(llvm, add_mod);
let mul_mod = Self::declare(
module,
Self::FUNCTION_MULMOD,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mul_mod, optimizer);
Function::set_pure_function_attributes(llvm, mul_mod);
let exp = Self::declare(
module,
Self::FUNCTION_EXP,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, exp, optimizer);
Function::set_pure_function_attributes(llvm, exp);
let sign_extend = FunctionDeclaration::new(
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
module
.get_function(Self::FUNCTION_SIGNEXTEND)
.expect("should be declared in stdlib"),
);
Function::set_default_attributes(llvm, sign_extend, optimizer);
Function::set_pure_function_attributes(llvm, sign_extend);
let mstore8 = Self::declare(
module,
Self::FUNCTION_MSTORE8,
llvm.void_type().fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32)
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mstore8, optimizer);
Function::set_attributes(
llvm,
mstore8,
vec![
Attribute::MustProgress,
Attribute::NoUnwind,
Attribute::WillReturn,
],
false,
);
let sha3 = Self::declare(
module,
Self::FUNCTION_SHA3,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32)
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BOOLEAN as u32)
.as_basic_type_enum()
.into(),
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, sha3, optimizer);
Function::set_attributes(
llvm,
sha3,
//vec![Attribute::ArgMemOnly, Attribute::ReadOnly],
vec![],
false,
);
let system_request = Self::declare(
module,
Self::FUNCTION_SYSTEM_REQUEST,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.ptr_type(AddressSpace::Stack.into())
.as_basic_type_enum()
.into(),
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, system_request, optimizer);
Function::set_attributes(
llvm,
system_request,
//vec![Attribute::ArgMemOnly, Attribute::ReadOnly],
vec![],
false,
);
let external_call_arguments: Vec<inkwell::types::BasicMetadataTypeEnum> = vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
crate::eravm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT
+ crate::eravm::EXTRA_ABI_DATA_SIZE
];
let mut mimic_call_arguments = external_call_arguments.clone();
mimic_call_arguments.push(
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
);
let mut external_call_arguments_by_ref: Vec<inkwell::types::BasicMetadataTypeEnum> = vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32)
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
];
external_call_arguments_by_ref.extend::<Vec<inkwell::types::BasicMetadataTypeEnum>>(vec![
llvm.custom_width_int_type(
era_compiler_common::BIT_LENGTH_FIELD as u32
)
.as_basic_type_enum()
.into();
crate::eravm::EXTRA_ABI_DATA_SIZE
]);
let mut mimic_call_arguments_by_ref = external_call_arguments_by_ref.clone();
mimic_call_arguments_by_ref.push(
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
);
let external_call_result_type = llvm
.struct_type(
&[
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32)
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
llvm.bool_type().as_basic_type_enum(),
],
false,
)
.as_basic_type_enum();
//let far_call = Self::declare(
// module,
// Self::FUNCTION_FARCALL,
// external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
// Some(inkwell::module::Linkage::External),
//);
//Function::set_default_attributes(llvm, far_call, optimizer);
let static_call = Self::declare(
module,
Self::FUNCTION_STATICCALL,
external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, static_call, optimizer);
let delegate_call = Self::declare(
module,
Self::FUNCTION_DELEGATECALL,
external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, delegate_call, optimizer);
let mimic_call = Self::declare(
module,
Self::FUNCTION_MIMICCALL,
external_call_result_type.fn_type(mimic_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mimic_call, optimizer);
let far_call_byref = Self::declare(
module,
Self::FUNCTION_FARCALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, far_call_byref, optimizer);
let static_call_byref = Self::declare(
module,
Self::FUNCTION_STATICCALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, static_call_byref, optimizer);
let delegate_call_byref = Self::declare(
module,
Self::FUNCTION_DELEGATECALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, delegate_call_byref, optimizer);
let mimic_call_byref = Self::declare(
module,
Self::FUNCTION_MIMICCALL_BYREF,
external_call_result_type.fn_type(mimic_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mimic_call_byref, optimizer);
let r#return = Self::declare(
module,
Self::FUNCTION_RETURN,
llvm.void_type().fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, r#return, optimizer);
let revert = Self::declare(
module,
Self::FUNCTION_REVERT,
llvm.void_type().fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, revert, optimizer);
Self {
personality,
cxa_throw,
div,
sdiv,
r#mod,
smod,
shl,
shr,
sar,
byte,
add_mod,
mul_mod,
exp,
sign_extend,
mstore8,
sha3,
system_request,
//far_call,
static_call,
delegate_call,
mimic_call,
far_call_byref,
static_call_byref,
delegate_call_byref,
mimic_call_byref,
r#return,
revert,
}
}
///
/// Declares an LLVM runtime function in the `module`,
///
pub fn declare(
module: &inkwell::module::Module<'ctx>,
name: &str,
r#type: inkwell::types::FunctionType<'ctx>,
linkage: Option<inkwell::module::Linkage>,
) -> FunctionDeclaration<'ctx> {
let value = module.add_function(name, r#type, linkage);
FunctionDeclaration::new(r#type, value)
}
///
/// Modifies the external call function with `is_byref` and `is_system` modifiers.
///
pub fn modify(
&self,
function: FunctionDeclaration<'ctx>,
is_byref: bool,
) -> anyhow::Result<FunctionDeclaration<'ctx>> {
let modified = if
/*function == self.far_call {
match is_byref {
false => self.far_call,
true => self.far_call_byref,
}
} else if */
function == self.static_call {
match is_byref {
false => self.static_call,
true => self.static_call_byref,
}
} else if function == self.delegate_call {
match is_byref {
false => self.delegate_call,
true => self.delegate_call_byref,
}
} else if function == self.mimic_call {
match is_byref {
false => self.mimic_call,
true => self.mimic_call_byref,
}
} else {
anyhow::bail!(
"Cannot modify an external call function `{}`",
function.value.get_name().to_string_lossy()
);
};
Ok(modified)
}
}
@@ -0,0 +1,428 @@
//!
//! The LLVM IR generator function.
//!
pub mod block;
pub mod declaration;
pub mod evmla_data;
pub mod intrinsics;
pub mod llvm_runtime;
pub mod r#return;
pub mod runtime;
pub mod vyper_data;
pub mod yul_data;
use std::collections::HashMap;
use crate::eravm::context::attribute::Attribute;
use crate::eravm::context::pointer::Pointer;
use crate::optimizer::settings::size_level::SizeLevel;
use crate::optimizer::Optimizer;
use self::declaration::Declaration;
use self::evmla_data::EVMLAData;
use self::r#return::Return;
use self::runtime::Runtime;
use self::vyper_data::VyperData;
use self::yul_data::YulData;
///
/// The LLVM IR generator function.
///
#[derive(Debug)]
pub struct Function<'ctx> {
/// The high-level source code name.
name: String,
/// The LLVM function declaration.
declaration: Declaration<'ctx>,
/// The stack representation.
stack: HashMap<String, Pointer<'ctx>>,
/// The return value entity.
r#return: Return<'ctx>,
/// The entry block. Each LLVM IR functions must have an entry block.
entry_block: inkwell::basic_block::BasicBlock<'ctx>,
/// The return/leave block. LLVM IR functions may have multiple returning blocks, but it is
/// more reasonable to have a single returning block and other high-level language returns
/// jumping to it. This way it is easier to implement some additional checks and clean-ups
/// before the returning.
return_block: inkwell::basic_block::BasicBlock<'ctx>,
/// The Yul compiler data.
yul_data: Option<YulData>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData<'ctx>>,
/// The Vyper data.
vyper_data: Option<VyperData>,
}
impl<'ctx> Function<'ctx> {
/// The near call ABI function prefix.
pub const ZKSYNC_NEAR_CALL_ABI_PREFIX: &'static str = "ZKSYNC_NEAR_CALL";
/// The near call ABI exception handler name.
pub const ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER: &'static str = "ZKSYNC_CATCH_NEAR_CALL";
/// The stack hashmap default capacity.
const STACK_HASHMAP_INITIAL_CAPACITY: usize = 64;
///
/// A shortcut constructor.
///
pub fn new(
name: String,
declaration: Declaration<'ctx>,
r#return: Return<'ctx>,
entry_block: inkwell::basic_block::BasicBlock<'ctx>,
return_block: inkwell::basic_block::BasicBlock<'ctx>,
) -> Self {
Self {
name,
declaration,
stack: HashMap::with_capacity(Self::STACK_HASHMAP_INITIAL_CAPACITY),
r#return,
entry_block,
return_block,
yul_data: None,
evmla_data: None,
vyper_data: None,
}
}
///
/// Returns the function name reference.
///
pub fn name(&self) -> &str {
self.name.as_str()
}
///
/// Checks whether the function is defined outside of the front-end.
///
pub fn is_name_external(name: &str) -> bool {
name.starts_with("llvm.")
|| (name.starts_with("__")
&& name != Runtime::FUNCTION_ENTRY
&& name != Runtime::FUNCTION_DEPLOY_CODE
&& name != Runtime::FUNCTION_RUNTIME_CODE)
}
///
/// Checks whether the function is related to the near call ABI.
///
pub fn is_near_call_abi(name: &str) -> bool {
name.starts_with(Self::ZKSYNC_NEAR_CALL_ABI_PREFIX)
|| name == Self::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER
}
///
/// Returns the LLVM function declaration.
///
pub fn declaration(&self) -> Declaration<'ctx> {
self.declaration
}
///
/// Returns the N-th parameter of the function.
///
pub fn get_nth_param(&self, index: usize) -> inkwell::values::BasicValueEnum<'ctx> {
self.declaration()
.value
.get_nth_param(index as u32)
.expect("Always exists")
}
///
/// Sets the memory writer function attributes.
///
pub fn set_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
attributes: Vec<Attribute>,
force: bool,
) {
for attribute_kind in attributes.into_iter() {
match attribute_kind {
attribute_kind @ Attribute::AlwaysInline if force => {
let is_optimize_none_set = declaration
.value
.get_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
Attribute::OptimizeNone as u32,
)
.is_some();
if !is_optimize_none_set {
declaration.value.remove_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
Attribute::NoInline as u32,
);
declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0),
);
}
}
attribute_kind @ Attribute::NoInline if force => {
declaration.value.remove_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
Attribute::AlwaysInline as u32,
);
declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0),
);
}
attribute_kind => declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0),
),
}
}
}
///
/// Sets the default attributes.
///
/// The attributes only affect the LLVM optimizations.
///
pub fn set_default_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
optimizer: &Optimizer,
) {
if optimizer.settings().level_middle_end == inkwell::OptimizationLevel::None {
Self::set_attributes(
llvm,
declaration,
vec![Attribute::OptimizeNone, Attribute::NoInline],
false,
);
} else if optimizer.settings().level_middle_end_size == SizeLevel::Z {
Self::set_attributes(
llvm,
declaration,
vec![Attribute::OptimizeForSize, Attribute::MinSize],
false,
);
}
Self::set_attributes(llvm, declaration, vec![Attribute::NoFree], false);
}
///
/// Sets the front-end runtime attributes.
///
pub fn set_frontend_runtime_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
optimizer: &Optimizer,
) {
if optimizer.settings().level_middle_end_size == SizeLevel::Z {
Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false);
}
}
///
/// Sets the exception handler attributes.
///
pub fn set_exception_handler_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
) {
Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false);
}
///
/// Sets the CXA-throw attributes.
///
pub fn set_cxa_throw_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
) {
Self::set_attributes(llvm, declaration, vec![Attribute::NoProfile], false);
}
///
/// Sets the pure function attributes.
///
pub fn set_pure_function_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
) {
Self::set_attributes(
llvm,
declaration,
vec![
Attribute::MustProgress,
Attribute::NoUnwind,
// FIXME: LLVM complains about ReadNone being not valid for fns
// Attribute::ReadNone,
Attribute::WillReturn,
],
false,
);
}
///
/// Saves the pointer to a stack variable, returning the pointer to the shadowed variable,
/// if it exists.
///
pub fn insert_stack_pointer(
&mut self,
name: String,
pointer: Pointer<'ctx>,
) -> Option<Pointer<'ctx>> {
self.stack.insert(name, pointer)
}
///
/// Gets the pointer to a stack variable.
///
pub fn get_stack_pointer(&self, name: &str) -> Option<Pointer<'ctx>> {
self.stack.get(name).copied()
}
///
/// Removes the pointer to a stack variable.
///
pub fn remove_stack_pointer(&mut self, name: &str) {
self.stack.remove(name);
}
///
/// Returns the return entity representation.
///
pub fn r#return(&self) -> Return<'ctx> {
self.r#return
}
///
/// Returns the pointer to the function return value.
///
/// # Panics
/// If the pointer has not been set yet.
///
pub fn return_pointer(&self) -> Option<Pointer<'ctx>> {
self.r#return.return_pointer()
}
///
/// Returns the return data size in bytes, based on the default stack alignment.
///
/// # Panics
/// If the pointer has not been set yet.
///
pub fn return_data_size(&self) -> usize {
self.r#return.return_data_size()
}
///
/// Returns the function entry block.
///
pub fn entry_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.entry_block
}
///
/// Returns the function return block.
///
pub fn return_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.return_block
}
///
/// Sets the EVM legacy assembly data.
///
pub fn set_evmla_data(&mut self, data: EVMLAData<'ctx>) {
self.evmla_data = Some(data);
}
///
/// Returns the EVM legacy assembly data reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evmla(&self) -> &EVMLAData<'ctx> {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
///
/// Returns the EVM legacy assembly data mutable reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evmla_mut(&mut self) -> &mut EVMLAData<'ctx> {
self.evmla_data
.as_mut()
.expect("The EVM data must have been initialized")
}
///
/// Sets the Vyper data.
///
pub fn set_vyper_data(&mut self, data: VyperData) {
self.vyper_data = Some(data);
}
///
/// Returns the Vyper data reference.
///
/// # Panics
/// If the Vyper data has not been initialized.
///
pub fn vyper(&self) -> &VyperData {
self.vyper_data
.as_ref()
.expect("The Vyper data must have been initialized")
}
///
/// Returns the Vyper data mutable reference.
///
/// # Panics
/// If the Vyper data has not been initialized.
///
pub fn vyper_mut(&mut self) -> &mut VyperData {
self.vyper_data
.as_mut()
.expect("The Vyper data must have been initialized")
}
///
/// Sets the Yul data.
///
pub fn set_yul_data(&mut self, data: YulData) {
self.yul_data = Some(data);
}
///
/// Returns the Yul data reference.
///
/// # Panics
/// If the Yul data has not been initialized.
///
pub fn yul(&self) -> &YulData {
self.yul_data
.as_ref()
.expect("The Yul data must have been initialized")
}
///
/// Returns the Yul data mutable reference.
///
/// # Panics
/// If the Yul data has not been initialized.
///
pub fn yul_mut(&mut self) -> &mut YulData {
self.yul_data
.as_mut()
.expect("The Yul data must have been initialized")
}
}
@@ -0,0 +1,73 @@
//!
//! The LLVM IR generator function return entity.
//!
use crate::eravm::context::pointer::Pointer;
///
/// The LLVM IR generator function return entity.
///
#[derive(Debug, Clone, Copy)]
pub enum Return<'ctx> {
/// The function does not return a value.
None,
/// The function returns a primitive value.
Primitive {
/// The primitive value pointer allocated at the function entry.
pointer: Pointer<'ctx>,
},
/// The function returns a compound value.
/// In this case, the return pointer is allocated on the stack by the callee.
Compound {
/// The structure pointer allocated at the function entry.
pointer: Pointer<'ctx>,
/// The function return type size.
size: usize,
},
}
impl<'ctx> Return<'ctx> {
///
/// A shortcut constructor.
///
pub fn none() -> Self {
Self::None
}
///
/// A shortcut constructor.
///
pub fn primitive(pointer: Pointer<'ctx>) -> Self {
Self::Primitive { pointer }
}
///
/// A shortcut constructor.
///
pub fn compound(pointer: Pointer<'ctx>, size: usize) -> Self {
Self::Compound { pointer, size }
}
///
/// Returns the pointer to the function return value.
///
pub fn return_pointer(&self) -> Option<Pointer<'ctx>> {
match self {
Return::None => None,
Return::Primitive { pointer } => Some(pointer.to_owned()),
Return::Compound { pointer, .. } => Some(pointer.to_owned()),
}
}
///
/// Returns the return data size in bytes, based on the default stack alignment.
///
pub fn return_data_size(&self) -> usize {
era_compiler_common::BYTE_LENGTH_FIELD
* match self {
Self::None => 0,
Self::Primitive { .. } => 1,
Self::Compound { size, .. } => *size,
}
}
}
@@ -0,0 +1,257 @@
//!
//! The `default_call` function.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::function::llvm_runtime::LLVMRuntime;
use crate::eravm::context::function::Function;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
///
/// The `default_call` function.
///
/// Generates a default contract call, if the `msg.value` is zero.
///
#[derive(Debug)]
pub struct DefaultCall {
/// The name of the inner function used for the low-level call.
inner_name: String,
/// The function name with the low-level function name as an element.
name: String,
}
impl DefaultCall {
/// The gas argument index.
pub const ARGUMENT_INDEX_GAS: usize = 0;
/// The address argument index.
pub const ARGUMENT_INDEX_ADDRESS: usize = 1;
/// The input offset argument index.
pub const ARGUMENT_INDEX_INPUT_OFFSET: usize = 2;
/// The input length argument index.
pub const ARGUMENT_INDEX_INPUT_LENGTH: usize = 3;
/// The output offset argument index.
pub const ARGUMENT_INDEX_OUTPUT_OFFSET: usize = 4;
/// The output length argument index.
pub const ARGUMENT_INDEX_OUTPUT_LENGTH: usize = 5;
///
/// A shortcut constructor.
///
pub fn new(call_function: FunctionDeclaration) -> Self {
let inner_name = call_function.value.get_name().to_string_lossy().to_string();
let name = Self::name(call_function);
Self { inner_name, name }
}
///
/// Returns the function name.
///
pub fn name(call_function: FunctionDeclaration) -> String {
let suffix = match call_function.value.get_name().to_string_lossy() {
name if name == LLVMRuntime::FUNCTION_FARCALL => "far",
name if name == LLVMRuntime::FUNCTION_STATICCALL => "static",
name if name == LLVMRuntime::FUNCTION_DELEGATECALL => "delegate",
name => panic!("Invalid low-level call inner function `{name}`"),
};
format!("__default_{suffix}_call")
}
///
/// Returns the low-level call function.
///
fn inner_function<'ctx, D>(&self, context: &Context<'ctx, D>) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
match self.inner_name.as_str() {
//name if name == LLVMRuntime::FUNCTION_FARCALL => context.llvm_runtime().far_call,
name if name == LLVMRuntime::FUNCTION_STATICCALL => context.llvm_runtime().static_call,
name if name == LLVMRuntime::FUNCTION_DELEGATECALL => {
context.llvm_runtime().delegate_call
}
name => panic!("Invalid low-level call inner function `{name}`"),
}
}
}
impl<D> WriteLLVM<D> for DefaultCall
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type = context.function_type(
vec![
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
],
1,
false,
);
let function = context.add_function(
self.name.as_str(),
function_type,
1,
Some(inkwell::module::Linkage::Private),
)?;
Function::set_frontend_runtime_attributes(
context.llvm,
function.borrow().declaration(),
&context.optimizer,
);
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(self.name.as_str())?;
/*
let gas = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_GAS)
.into_int_value();
let address = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_ADDRESS)
.into_int_value();
let input_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_OFFSET)
.into_int_value();
let input_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_LENGTH)
.into_int_value();
let output_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_OUTPUT_OFFSET)
.into_int_value();
let output_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_OUTPUT_LENGTH)
.into_int_value();
*/
context.set_basic_block(context.current_function().borrow().entry_block());
let status_code_result_pointer = context.build_alloca(
context.field_type(),
"contract_call_result_status_code_pointer",
);
/*
context.build_store(status_code_result_pointer, context.field_const(0));
let abi_data = crate::eravm::utils::abi_data(
context,
input_offset,
input_length,
Some(gas),
AddressSpace::Heap,
false,
)?
.into_int_value();
let result = context
.build_call(
self.inner_function(context),
crate::eravm::utils::external_call_arguments(
context,
abi_data.as_basic_value_enum(),
address,
vec![],
None,
)
.as_slice(),
"contract_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
result.into_struct_value(),
0,
"contract_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_abi_data_casted = result_abi_data_pointer.cast(context.field_type());
let result_status_code_boolean = context
.builder()
.build_extract_value(
result.into_struct_value(),
1,
"contract_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"contract_call_external_result_status_code",
)?;
context.build_store(status_code_result_pointer, result_status_code);
let source = result_abi_data_casted;
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
output_offset,
"contract_call_destination",
);
context.build_memcpy_return_data(
context.intrinsics().memory_copy_from_generic,
destination,
source,
output_length,
"contract_call_memcpy_from_child",
);
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
*/
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(context.current_function().borrow().return_block());
let status_code_result =
context.build_load(status_code_result_pointer, "contract_call_status_code")?;
context.build_return(Some(&status_code_result));
Ok(())
}
}
@@ -0,0 +1,105 @@
//!
//! The deploy code function.
//!
use std::marker::PhantomData;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
///
/// The deploy code function.
///
/// Is a special function that is only used by the front-end generated code.
///
#[derive(Debug)]
pub struct DeployCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
/// The deploy code AST representation.
inner: B,
/// The `D` phantom data.
_pd: PhantomData<D>,
}
impl<B, D> DeployCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
///
/// A shortcut constructor.
///
pub fn new(inner: B) -> Self {
Self {
inner,
_pd: PhantomData,
}
}
}
impl<B, D> WriteLLVM<D> for DeployCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type =
context.function_type::<inkwell::types::BasicTypeEnum>(vec![], 0, false);
context.add_function(
Runtime::FUNCTION_DEPLOY_CODE,
function_type,
0,
Some(inkwell::module::Linkage::External),
)?;
self.inner.declare(context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Runtime::FUNCTION_DEPLOY_CODE)?;
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Deploy);
if let Some(vyper) = context.vyper_data.as_ref() {
for index in 0..vyper.immutables_size() / era_compiler_common::BYTE_LENGTH_FIELD {
let offset = (crate::eravm::r#const::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
as usize)
+ (1 + index) * 2 * era_compiler_common::BYTE_LENGTH_FIELD;
let value = index * era_compiler_common::BYTE_LENGTH_FIELD;
let pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
context.field_const(offset as u64),
"immutable_index_initializer",
);
context.build_store(pointer, context.field_const(value as u64))?;
}
}
self.inner.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());
context.build_return(None);
Ok(())
}
}
@@ -0,0 +1,333 @@
//!
//! The `deployer_call` function.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::Function;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
///
/// The `deployer_call` function.
///
/// Calls the deployer system contract, which returns the newly deployed contract address or 0.
///
/// The address is returned in the first 32-byte word of the return data. If it is 0, the 0 is
/// returned. If the entire call has failed, there is also a 0 returned.
///
#[derive(Debug)]
pub struct DeployerCall {
/// The address space where the calldata is allocated.
/// Solidity uses the ordinary heap. Vyper uses the auxiliary heap.
address_space: AddressSpace,
}
impl DeployerCall {
/// The default function name.
pub const FUNCTION_NAME: &'static str = "__deployer_call";
/// The value argument index.
pub const ARGUMENT_INDEX_VALUE: usize = 0;
/// The input offset argument index.
pub const ARGUMENT_INDEX_INPUT_OFFSET: usize = 1;
/// The input length argument index.
pub const ARGUMENT_INDEX_INPUT_LENGTH: usize = 2;
/// The signature hash argument index.
pub const ARGUMENT_INDEX_SIGNATURE_HASH: usize = 3;
/// The salt argument index.
pub const ARGUMENT_INDEX_SALT: usize = 4;
///
/// A shortcut constructor.
///
pub fn new(address_space: AddressSpace) -> Self {
Self { address_space }
}
}
impl<D> WriteLLVM<D> for DeployerCall
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type = context.function_type(
vec![
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
],
1,
false,
);
let function = context.add_function(
Self::FUNCTION_NAME,
function_type,
1,
Some(inkwell::module::Linkage::External),
)?;
Function::set_frontend_runtime_attributes(
context.llvm,
function.borrow().declaration(),
&context.optimizer,
);
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Self::FUNCTION_NAME)?;
let value = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_VALUE)
.into_int_value();
let input_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_OFFSET)
.into_int_value();
let input_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_LENGTH)
.into_int_value();
let signature_hash = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_SIGNATURE_HASH)
.into_int_value();
let salt = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_SALT)
.into_int_value();
let error_block = context.append_basic_block("deployer_call_error_block");
let success_block = context.append_basic_block("deployer_call_success_block");
let value_zero_block = context.append_basic_block("deployer_call_value_zero_block");
let value_non_zero_block = context.append_basic_block("deployer_call_value_non_zero_block");
let value_join_block = context.append_basic_block("deployer_call_value_join_block");
context.set_basic_block(context.current_function().borrow().entry_block());
let abi_data = crate::eravm::utils::abi_data(
context,
input_offset,
input_length,
None,
self.address_space,
true,
)?;
let signature_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.field_type(),
input_offset,
"deployer_call_signature_pointer",
);
context.build_store(signature_pointer, signature_hash)?;
let salt_offset = context.builder().build_int_add(
input_offset,
context.field_const(era_compiler_common::BYTE_LENGTH_X32 as u64),
"deployer_call_salt_offset",
)?;
let salt_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.field_type(),
salt_offset,
"deployer_call_salt_pointer",
);
context.build_store(salt_pointer, salt)?;
let arguments_offset_offset = context.builder().build_int_add(
salt_offset,
context.field_const((era_compiler_common::BYTE_LENGTH_FIELD * 2) as u64),
"deployer_call_arguments_offset_offset",
)?;
let arguments_offset_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.field_type(),
arguments_offset_offset,
"deployer_call_arguments_offset_pointer",
);
context.build_store(
arguments_offset_pointer,
context.field_const(
(crate::eravm::DEPLOYER_CALL_HEADER_SIZE
- (era_compiler_common::BYTE_LENGTH_X32
+ era_compiler_common::BYTE_LENGTH_FIELD)) as u64,
),
)?;
let arguments_length_offset = context.builder().build_int_add(
arguments_offset_offset,
context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64),
"deployer_call_arguments_length_offset",
)?;
let arguments_length_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.field_type(),
arguments_length_offset,
"deployer_call_arguments_length_pointer",
);
let arguments_length_value = context.builder().build_int_sub(
input_length,
context.field_const(crate::eravm::DEPLOYER_CALL_HEADER_SIZE as u64),
"deployer_call_arguments_length",
)?;
context.build_store(arguments_length_pointer, arguments_length_value)?;
let result_pointer =
context.build_alloca(context.field_type(), "deployer_call_result_pointer");
context.build_store(result_pointer, context.field_const(0))?;
let deployer_call_result_type = context.structure_type(&[
context
.byte_type()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
context.bool_type().as_basic_type_enum(),
]);
let deployer_call_result_pointer =
context.build_alloca(deployer_call_result_type, "deployer_call_result_pointer");
context.build_store(
deployer_call_result_pointer,
deployer_call_result_type.const_zero(),
)?;
let is_value_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
value,
context.field_const(0),
"deployer_call_is_value_zero",
)?;
context.build_conditional_branch(is_value_zero, value_zero_block, value_non_zero_block)?;
context.set_basic_block(value_zero_block);
//let deployer_call_result = context
// .build_call(
// context.llvm_runtime().far_call,
// crate::eravm::utils::external_call_arguments(
// context,
// abi_data,
// context.field_const(zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into()),
// vec![],
// None,
// )
// .as_slice(),
// "deployer_call_ordinary",
// )
// .expect("Always returns a value");
//context.build_store(deployer_call_result_pointer, deployer_call_result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_non_zero_block);
//let deployer_call_result = context
// .build_call(
// context.llvm_runtime().far_call,
// crate::eravm::utils::external_call_arguments(
// context,
// abi_data.as_basic_value_enum(),
// context.field_const(zkevm_opcode_defs::ADDRESS_MSG_VALUE.into()),
// vec![
// value,
// context.field_const(zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into()),
// context.field_const(u64::from(crate::eravm::r#const::SYSTEM_CALL_BIT)),
// ],
// None,
// )
// .as_slice(),
// "deployer_call_system",
// )
// .expect("Always returns a value");
//context.build_store(deployer_call_result_pointer, deployer_call_result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_join_block);
let result_abi_data_pointer = context.build_gep(
deployer_call_result_pointer,
&[
context.field_const(0),
context
.integer_type(era_compiler_common::BIT_LENGTH_X32)
.const_zero(),
],
context
.byte_type()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
"deployer_call_result_abi_data_pointer",
);
let result_abi_data =
context.build_load(result_abi_data_pointer, "deployer_call_result_abi_data")?;
let result_status_code_pointer = context.build_gep(
deployer_call_result_pointer,
&[
context.field_const(0),
context
.integer_type(era_compiler_common::BIT_LENGTH_X32)
.const_int(1, false),
],
context.bool_type().as_basic_type_enum(),
"contract_call_external_result_status_code_pointer",
);
let result_status_code_boolean = context
.build_load(
result_status_code_pointer,
"contract_call_external_result_status_code_boolean",
)?
.into_int_value();
context.build_conditional_branch(result_status_code_boolean, success_block, error_block)?;
context.set_basic_block(success_block);
let result_abi_data_pointer = Pointer::new(
context.field_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let address_or_status_code = context.build_load(
result_abi_data_pointer,
"deployer_call_address_or_status_code",
)?;
context.build_store(result_pointer, address_or_status_code)?;
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(error_block);
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(context.current_function().borrow().return_block());
let result = context.build_load(result_pointer, "deployer_call_result")?;
context.build_return(Some(&result));
Ok(())
}
}
@@ -0,0 +1,269 @@
//!
//! The entry function.
//!
use inkwell::types::BasicType;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
use crate::EraVMPointer as Pointer;
///
/// The entry function.
///
/// The function is a wrapper managing the runtime and deploy code calling logic.
///
/// Is a special runtime function that is only used by the front-end generated code.
///
#[derive(Debug, Default)]
pub struct Entry {}
impl Entry {
/// The call flags argument index.
pub const ARGUMENT_INDEX_CALL_FLAGS: usize = 0;
/// The number of mandatory arguments.
pub const MANDATORY_ARGUMENTS_COUNT: usize = 2;
/// Initializes the global variables.
/// The pointers are not initialized, because it's not possible to create a null pointer.
pub fn initialize_globals<D>(context: &mut Context<D>)
where
D: Dependency + Clone,
{
let calldata_type = context.array_type(context.byte_type(), 1024);
context.set_global(
crate::eravm::GLOBAL_CALLDATA_POINTER,
calldata_type,
AddressSpace::Stack,
calldata_type.get_undef(),
);
let heap_memory_type = context.array_type(context.byte_type(), 1024 * 1024);
context.set_global(
crate::eravm::GLOBAL_HEAP_MEMORY_POINTER,
heap_memory_type,
AddressSpace::Stack,
heap_memory_type.get_undef(),
);
context.set_global(
crate::eravm::GLOBAL_CALLDATA_SIZE,
context.field_type(),
AddressSpace::Stack,
context.field_undef(),
);
context.set_global(
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
context.field_type(),
AddressSpace::Stack,
context.field_const(0),
);
context.set_global(
crate::eravm::GLOBAL_CALL_FLAGS,
context.field_type(),
AddressSpace::Stack,
context.field_const(0),
);
let extra_abi_data_type = context.array_type(
context.field_type().as_basic_type_enum(),
crate::eravm::EXTRA_ABI_DATA_SIZE,
);
context.set_global(
crate::eravm::GLOBAL_EXTRA_ABI_DATA,
extra_abi_data_type,
AddressSpace::Stack,
extra_abi_data_type.const_zero(),
);
}
/// Load the calldata via seal `input` and initialize the calldata end
/// and calldata size globals.
pub fn load_calldata<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let input_pointer = context
.get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)?
.value
.as_pointer_value();
let input_pointer_casted = context.builder.build_ptr_to_int(
input_pointer,
context.integer_type(32),
"input_pointer_casted",
)?;
let length_pointer = context.build_alloca(context.integer_type(32), "len_ptr");
let length_pointer_casted = context.builder.build_ptr_to_int(
length_pointer.value,
context.integer_type(32),
"length_pointer_casted",
)?;
context.build_store(length_pointer, context.integer_const(32, 1024))?;
context.builder().build_call(
context.module().get_function("input").expect("is declared"),
&[input_pointer_casted.into(), length_pointer_casted.into()],
"call_seal_input",
)?;
// Store the calldata size
let calldata_size = context
.build_load(length_pointer, "input_size")?
.into_int_value();
let calldata_size_casted = context.builder().build_int_z_extend(
calldata_size,
context.field_type(),
"zext_input_len",
)?;
context.set_global(
crate::eravm::GLOBAL_CALLDATA_SIZE,
context.field_type(),
AddressSpace::Stack,
calldata_size_casted,
);
// Store calldata end pointer
let input_pointer = Pointer::new(
input_pointer.get_type(),
AddressSpace::Generic,
input_pointer,
);
let calldata_end_pointer = context.build_gep(
input_pointer,
&[calldata_size_casted],
context
.byte_type()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
"return_data_abi_initializer",
);
context.write_abi_pointer(
calldata_end_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_pointer(calldata_end_pointer, crate::eravm::GLOBAL_ACTIVE_POINTER);
Ok(())
}
/// Calls the deploy code if the first function argument was `1`.
/// Calls the runtime code otherwise.
pub fn leave_entry<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let is_deploy = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_CALL_FLAGS);
context.set_global(
crate::eravm::GLOBAL_CALL_FLAGS,
is_deploy.get_type(),
AddressSpace::Stack,
is_deploy.into_int_value(),
);
let deploy_code_call_block = context.append_basic_block("deploy_code_call_block");
let runtime_code_call_block = context.append_basic_block("runtime_code_call_block");
context.build_conditional_branch(
is_deploy.into_int_value(),
deploy_code_call_block,
runtime_code_call_block,
)?;
let deploy_code = context
.functions
.get(Runtime::FUNCTION_DEPLOY_CODE)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Contract deploy code not found"))?;
let runtime_code = context
.functions
.get(Runtime::FUNCTION_RUNTIME_CODE)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Contract runtime code not found"))?;
context.set_basic_block(deploy_code_call_block);
context.build_invoke(deploy_code.borrow().declaration, &[], "deploy_code_call");
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(runtime_code_call_block);
context.build_invoke(runtime_code.borrow().declaration, &[], "runtime_code_call");
context.build_unconditional_branch(context.current_function().borrow().return_block());
Ok(())
}
}
impl<D> WriteLLVM<D> for Entry
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let entry_arguments = vec![context.bool_type().as_basic_type_enum()];
let entry_function_type = context.function_type(entry_arguments, 0, false);
context.add_function(Runtime::FUNCTION_ENTRY, entry_function_type, 0, None)?;
context.declare_extern_function("deploy")?;
context.declare_extern_function("call")?;
Ok(())
}
/// Instead of a single entrypoint, the runtime expects two exports: `call ` and `deploy`.
/// `call` and `deploy` directly call `entry`, signaling a deploy if the first arg is `1`.
/// The `entry` function loads calldata, sets globals and calls the runtime or deploy code.
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
let entry = context
.get_function(Runtime::FUNCTION_ENTRY)
.expect("the entry function should already be declared")
.borrow()
.declaration;
crate::EraVMFunction::set_attributes(
context.llvm(),
entry,
vec![crate::EraVMAttribute::NoReturn],
true,
);
context.set_current_function("deploy")?;
context.set_basic_block(context.current_function().borrow().entry_block());
assert!(context
.build_call(entry, &[context.bool_const(true).into()], "entry_deploy")
.is_none());
context.set_basic_block(context.current_function().borrow().return_block);
context.build_unreachable();
context.set_current_function("call")?;
context.set_basic_block(context.current_function().borrow().entry_block());
assert!(context
.build_call(entry, &[context.bool_const(false).into()], "entry_call")
.is_none());
context.set_basic_block(context.current_function().borrow().return_block);
context.build_unreachable();
context.set_current_function(Runtime::FUNCTION_ENTRY)?;
context.set_basic_block(context.current_function().borrow().entry_block());
Self::initialize_globals(context);
Self::load_calldata(context)?;
Self::leave_entry(context)?;
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(context.current_function().borrow().return_block());
context.build_unreachable();
Ok(())
}
}
@@ -0,0 +1,100 @@
//!
//! The front-end runtime functions.
//!
pub mod default_call;
pub mod deploy_code;
pub mod deployer_call;
pub mod entry;
pub mod runtime_code;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
use self::default_call::DefaultCall;
use self::deployer_call::DeployerCall;
///
/// The front-end runtime functions.
///
#[derive(Debug, Clone)]
pub struct Runtime {
/// The address space where the calldata is allocated.
/// Solidity uses the ordinary heap. Vyper uses the auxiliary heap.
address_space: AddressSpace,
}
impl Runtime {
/// The main entry function name.
pub const FUNCTION_ENTRY: &'static str = "__entry";
/// The deploy code function name.
pub const FUNCTION_DEPLOY_CODE: &'static str = "__deploy";
/// The runtime code function name.
pub const FUNCTION_RUNTIME_CODE: &'static str = "__runtime";
///
/// A shortcut constructor.
///
pub fn new(address_space: AddressSpace) -> Self {
Self { address_space }
}
///
/// Returns the corresponding runtime function.
///
pub fn default_call<'ctx, D>(
context: &Context<'ctx, D>,
call_function: FunctionDeclaration<'ctx>,
) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
context
.get_function(DefaultCall::name(call_function).as_str())
.expect("Always exists")
.borrow()
.declaration()
}
///
/// Returns the corresponding runtime function.
///
pub fn deployer_call<'ctx, D>(context: &Context<'ctx, D>) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
context
.get_function(DeployerCall::FUNCTION_NAME)
.expect("Always exists")
.borrow()
.declaration()
}
}
impl<D> WriteLLVM<D> for Runtime
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
//DefaultCall::new(context.llvm_runtime().far_call).declare(context)?;
DefaultCall::new(context.llvm_runtime().static_call).declare(context)?;
DefaultCall::new(context.llvm_runtime().delegate_call).declare(context)?;
DeployerCall::new(self.address_space).declare(context)?;
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
//DefaultCall::new(context.llvm_runtime().far_call).into_llvm(context)?;
DefaultCall::new(context.llvm_runtime().static_call).into_llvm(context)?;
DefaultCall::new(context.llvm_runtime().delegate_call).into_llvm(context)?;
DeployerCall::new(self.address_space).into_llvm(context)?;
Ok(())
}
}
@@ -0,0 +1,86 @@
//!
//! The runtime code function.
//!
use std::marker::PhantomData;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
///
/// The runtime code function.
///
/// Is a special function that is only used by the front-end generated code.
///
#[derive(Debug)]
pub struct RuntimeCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
/// The runtime code AST representation.
inner: B,
/// The `D` phantom data.
_pd: PhantomData<D>,
}
impl<B, D> RuntimeCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
///
/// A shortcut constructor.
///
pub fn new(inner: B) -> Self {
Self {
inner,
_pd: PhantomData,
}
}
}
impl<B, D> WriteLLVM<D> for RuntimeCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type =
context.function_type::<inkwell::types::BasicTypeEnum>(vec![], 0, false);
context.add_function(
Runtime::FUNCTION_RUNTIME_CODE,
function_type,
0,
Some(inkwell::module::Linkage::External),
)?;
self.inner.declare(context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Runtime::FUNCTION_RUNTIME_CODE)?;
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Runtime);
self.inner.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());
context.build_unreachable();
Ok(())
}
}
@@ -0,0 +1,52 @@
//!
//! The LLVM function Vyper data.
//!
use std::collections::HashMap;
///
/// The LLVM function Vyper data.
///
/// Describes some data that is only relevant to Vyper.
///
#[derive(Debug)]
pub struct VyperData {
/// The block-local variables. They are still allocated at the beginning of the function,
/// but their parent block must be known in order to pass the implicit arguments thereto.
/// Is only used by the Vyper LLL IR compiler.
label_arguments: HashMap<String, Vec<String>>,
}
impl Default for VyperData {
fn default() -> Self {
Self {
label_arguments: HashMap::with_capacity(Self::LABEL_ARGUMENTS_HASHMAP_INITIAL_CAPACITY),
}
}
}
impl VyperData {
/// The label arguments hashmap default capacity.
const LABEL_ARGUMENTS_HASHMAP_INITIAL_CAPACITY: usize = 16;
///
/// A shortcut constructor.
///
pub fn new() -> Self {
Self::default()
}
///
/// Returns the list of a Vyper label arguments.
///
pub fn label_arguments(&self, label_name: &str) -> Option<Vec<String>> {
self.label_arguments.get(label_name).cloned()
}
///
/// Inserts arguments for the specified label.
///
pub fn insert_label_arguments(&mut self, label_name: String, arguments: Vec<String>) {
self.label_arguments.insert(label_name, arguments);
}
}
@@ -0,0 +1,53 @@
//!
//! The LLVM function Yul data.
//!
use std::collections::HashMap;
use num::BigUint;
///
/// The LLVM function Yul data.
///
/// Describes some data that is only relevant to Yul.
///
#[derive(Debug)]
pub struct YulData {
/// The constants saved to variables. Used for peculiar cases like call simulation.
/// It is a partial implementation of the constant propagation.
constants: HashMap<String, BigUint>,
}
impl Default for YulData {
fn default() -> Self {
Self {
constants: HashMap::with_capacity(Self::CONSTANTS_HASHMAP_INITIAL_CAPACITY),
}
}
}
impl YulData {
/// The constants hashmap default capacity.
const CONSTANTS_HASHMAP_INITIAL_CAPACITY: usize = 16;
///
/// A shortcut constructor.
///
pub fn new() -> Self {
Self::default()
}
///
/// Returns a constant if it has been saved.
///
pub fn get_constant(&self, name: &str) -> Option<BigUint> {
self.constants.get(name).cloned()
}
///
/// Saves a constant detected with the partial constant propagation.
///
pub fn insert_constant(&mut self, name: String, value: BigUint) {
self.constants.insert(name, value);
}
}
@@ -0,0 +1,63 @@
//!
//! The LLVM global value.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::Context;
use crate::EraVMDependency;
///
/// The LLVM global value.
///
#[derive(Debug, Clone, Copy)]
pub struct Global<'ctx> {
/// The global type.
pub r#type: inkwell::types::BasicTypeEnum<'ctx>,
/// The global value.
pub value: inkwell::values::GlobalValue<'ctx>,
}
impl<'ctx> Global<'ctx> {
///
/// A shortcut constructor.
///
pub fn new<D, T, V>(
context: &mut Context<'ctx, D>,
r#type: T,
address_space: AddressSpace,
initializer: V,
name: &str,
) -> Self
where
D: EraVMDependency + Clone,
T: BasicType<'ctx>,
V: BasicValue<'ctx>,
{
let r#type = r#type.as_basic_type_enum();
let value = context
.module()
.add_global(r#type, Some(address_space.into()), name);
let global = Self { r#type, value };
global.value.set_linkage(inkwell::module::Linkage::Private);
global
.value
.set_visibility(inkwell::GlobalVisibility::Default);
global.value.set_externally_initialized(false);
if let AddressSpace::Code = address_space {
global.value.set_constant(true);
}
if !r#type.is_pointer_type() {
global.value.set_initializer(&initializer);
} else {
global.value.set_initializer(&r#type.const_zero());
context.build_store(global.into(), initializer).unwrap();
}
global
}
}
@@ -0,0 +1,33 @@
//!
//! The LLVM IR generator loop.
//!
///
/// The LLVM IR generator loop.
///
#[derive(Debug, Clone)]
pub struct Loop<'ctx> {
/// The loop current block.
pub body_block: inkwell::basic_block::BasicBlock<'ctx>,
/// The increment block before the body.
pub continue_block: inkwell::basic_block::BasicBlock<'ctx>,
/// The join block after the body.
pub join_block: inkwell::basic_block::BasicBlock<'ctx>,
}
impl<'ctx> Loop<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(
body_block: inkwell::basic_block::BasicBlock<'ctx>,
continue_block: inkwell::basic_block::BasicBlock<'ctx>,
join_block: inkwell::basic_block::BasicBlock<'ctx>,
) -> Self {
Self {
body_block,
continue_block,
join_block,
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,137 @@
//!
//! The LLVM pointer.
//!
use inkwell::types::BasicType;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::global::Global;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// The LLVM pointer.
///
#[derive(Debug, Clone, Copy)]
pub struct Pointer<'ctx> {
/// The pointee type.
pub r#type: inkwell::types::BasicTypeEnum<'ctx>,
/// The address space.
pub address_space: AddressSpace,
/// The pointer value.
pub value: inkwell::values::PointerValue<'ctx>,
}
impl<'ctx> Pointer<'ctx> {
///
/// A shortcut constructor.
///
pub fn new<T>(
r#type: T,
address_space: AddressSpace,
value: inkwell::values::PointerValue<'ctx>,
) -> Self
where
T: BasicType<'ctx>,
{
Self {
r#type: r#type.as_basic_type_enum(),
address_space,
value,
}
}
///
/// Wraps a 256-bit primitive type pointer.
///
pub fn new_stack_field<D>(
context: &Context<'ctx, D>,
value: inkwell::values::PointerValue<'ctx>,
) -> Self
where
D: Dependency + Clone,
{
Self {
r#type: context.field_type().as_basic_type_enum(),
address_space: AddressSpace::Stack,
value,
}
}
///
/// Creates a new pointer with the specified `offset`.
///
pub fn new_with_offset<D, T>(
context: &Context<'ctx, D>,
address_space: AddressSpace,
r#type: T,
offset: inkwell::values::IntValue<'ctx>,
name: &str,
) -> Self
where
D: Dependency + Clone,
T: BasicType<'ctx>,
{
assert_ne!(
address_space,
AddressSpace::Stack,
"Stack pointers cannot be addressed"
);
let value = context
.builder
.build_int_to_ptr(
offset,
context.byte_type().ptr_type(address_space.into()),
name,
)
.unwrap();
Self::new(r#type, address_space, value)
}
///
/// Casts the pointer into another type.
///
pub fn cast<T>(self, r#type: T) -> Self
where
T: BasicType<'ctx>,
{
Self {
r#type: r#type.as_basic_type_enum(),
address_space: self.address_space,
value: self.value,
}
}
pub fn address_space_cast<D>(
self,
context: &Context<'ctx, D>,
address_space: AddressSpace,
name: &str,
) -> anyhow::Result<Self>
where
D: Dependency + Clone,
{
let value = context.builder().build_address_space_cast(
self.value,
self.r#type.ptr_type(address_space.into()),
name,
)?;
Ok(Self {
address_space,
value,
..self
})
}
}
impl<'ctx> From<Global<'ctx>> for Pointer<'ctx> {
fn from(global: Global<'ctx>) -> Self {
Self {
r#type: global.r#type,
address_space: AddressSpace::Stack,
value: global.value.as_pointer_value(),
}
}
}
@@ -0,0 +1,59 @@
//!
//! The LLVM IR generator Solidity data.
//!
use std::collections::BTreeMap;
///
/// The LLVM IR generator Solidity data.
///
/// Describes some data that is only relevant to Solidity.
///
#[derive(Debug, Default)]
pub struct SolidityData {
/// The immutables identifier-to-offset mapping. Is only used by Solidity due to
/// the arbitrariness of its identifiers.
immutables: BTreeMap<String, usize>,
}
impl SolidityData {
///
/// A shortcut constructor.
///
pub fn new() -> Self {
Self::default()
}
///
/// Returns the current number of immutables values in the contract.
///
pub fn immutables_size(&self) -> usize {
self.immutables.len() * era_compiler_common::BYTE_LENGTH_FIELD
}
///
/// Allocates memory for an immutable value in the auxiliary heap.
///
/// If the identifier is already known, just returns its offset.
///
pub fn allocate_immutable(&mut self, identifier: &str) -> usize {
let number_of_elements = self.immutables.len();
let new_offset = number_of_elements * era_compiler_common::BYTE_LENGTH_FIELD;
*self
.immutables
.entry(identifier.to_owned())
.or_insert(new_offset)
}
///
/// Gets the offset of the immutable value.
///
/// If the value is not yet allocated, then it is done forcibly.
///
pub fn get_or_allocate_immutable(&mut self, identifier: &str) -> usize {
match self.immutables.get(identifier).copied() {
Some(offset) => offset,
None => self.allocate_immutable(identifier),
}
}
}
@@ -0,0 +1,136 @@
//!
//! The LLVM IR generator context tests.
//!
use crate::eravm::context::attribute::Attribute;
use crate::eravm::context::Context;
use crate::eravm::DummyDependency;
use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer;
pub fn create_context(
llvm: &inkwell::context::Context,
optimizer_settings: OptimizerSettings,
) -> Context<DummyDependency> {
crate::eravm::initialize_target();
let module = llvm.create_module("test");
let optimizer = Optimizer::new(optimizer_settings);
Context::<DummyDependency>::new(&llvm, module, optimizer, None, true, None)
}
#[test]
pub fn check_attribute_null_pointer_is_invalid() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::cycles());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::NullPointerIsValid as u32, 0)));
}
#[test]
pub fn check_attribute_optimize_for_size_mode_3() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::cycles());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(!function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::OptimizeForSize as u32, 0)));
}
#[test]
pub fn check_attribute_optimize_for_size_mode_z() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::size());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::OptimizeForSize as u32, 0)));
}
#[test]
pub fn check_attribute_min_size_mode_3() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::cycles());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(!function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::MinSize as u32, 0)));
}
#[test]
pub fn check_attribute_min_size_mode_z() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::size());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::MinSize as u32, 0)));
}
@@ -0,0 +1,50 @@
//!
//! The LLVM IR generator Vyper data.
//!
///
/// The LLVM IR generator Vyper data.
///
/// Describes some data that is only relevant to Vyper.
///
#[derive(Debug)]
pub struct VyperData {
/// The immutables size tracker. Stores the size in bytes.
/// Does not take into account the size of the indexes.
immutables_size: usize,
/// Whether the contract forwarder has been used.
is_forwarder_used: bool,
}
impl VyperData {
///
/// A shortcut constructor.
///
pub fn new(immutables_size: usize, is_forwarder_used: bool) -> Self {
Self {
immutables_size,
is_forwarder_used,
}
}
///
/// Returns the size of the immutables data of the contract.
///
pub fn immutables_size(&self) -> usize {
self.immutables_size
}
///
/// Sets the forwarder usage flag.
///
pub fn set_is_forwarder_used(&mut self) {
self.is_forwarder_used = true;
}
///
/// Returns the forwarder usage flag.
///
pub fn is_forwarder_used(&self) -> bool {
self.is_forwarder_used
}
}
@@ -0,0 +1,92 @@
//!
//! The LLVM IR generator Yul data.
//!
use std::collections::BTreeMap;
use num::Zero;
///
/// The LLVM IR generator Yul data.
///
/// Describes some data that is only relevant to Yul.
///
#[derive(Debug, Default)]
pub struct YulData {
/// The system mode flag.
/// The call simulations only work if this mode is enabled.
is_system_mode: bool,
/// The list of constant arrays in the code section.
/// It is a temporary storage used until the finalization method is called.
const_arrays: BTreeMap<u8, Vec<num::BigUint>>,
}
impl YulData {
///
/// A shortcut constructor.
///
pub fn new(is_system_mode: bool) -> Self {
Self {
is_system_mode,
const_arrays: BTreeMap::new(),
}
}
///
/// Whether the system mode is enabled.
///
pub fn is_system_mode(&self) -> bool {
self.is_system_mode
}
///
/// Declares a temporary constant array representation.
///
pub fn const_array_declare(&mut self, index: u8, size: u16) -> anyhow::Result<()> {
if self.const_arrays.contains_key(&index) {
anyhow::bail!(
"The constant array with index {} is already declared",
index
);
}
self.const_arrays
.insert(index, vec![num::BigUint::zero(); size as usize]);
Ok(())
}
///
/// Sets a value in the constant array representation.
///
pub fn const_array_set(
&mut self,
index: u8,
offset: u16,
value: num::BigUint,
) -> anyhow::Result<()> {
let array = self.const_arrays.get_mut(&index).ok_or_else(|| {
anyhow::anyhow!("The constant array with index {} is not declared", index)
})?;
if offset >= array.len() as u16 {
anyhow::bail!(
"The constant array with index {} has size {} but the offset is {}",
index,
array.len(),
offset,
);
}
array[offset as usize] = value;
Ok(())
}
///
/// Finalizes the constant array declaration.
///
pub fn const_array_take(&mut self, index: u8) -> anyhow::Result<Vec<num::BigUint>> {
self.const_arrays.remove(&index).ok_or_else(|| {
anyhow::anyhow!("The constant array with index {} is not declared", index)
})
}
}
@@ -0,0 +1,149 @@
//!
//! Translates the arithmetic operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the arithmetic addition.
///
pub fn addition<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_add(operand_1, operand_2, "addition_result")?
.as_basic_value_enum())
}
///
/// Translates the arithmetic subtraction.
///
pub fn subtraction<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_sub(operand_1, operand_2, "subtraction_result")?
.as_basic_value_enum())
}
///
/// Translates the arithmetic multiplication.
///
pub fn multiplication<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_mul(operand_1, operand_2, "multiplication_result")?
.as_basic_value_enum())
}
///
/// Translates the arithmetic division.
///
pub fn division<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_unsigned_div(operand_1, operand_2, "udiv")?
.into())
}
///
/// Translates the arithmetic remainder.
///
pub fn remainder<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().r#mod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
}
///
/// Translates the signed arithmetic division.
///
/// Two differences between the EVM and LLVM IR:
/// 1. In case of division by zero, 0 is returned.
/// 2. In case of overflow, the first argument is returned.
///
pub fn division_signed<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().sdiv,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
}
///
/// Translates the signed arithmetic remainder.
///
pub fn remainder_signed<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().smod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
}
@@ -0,0 +1,235 @@
//!
//! Translates the bitwise operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the bitwise OR.
///
pub fn or<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_or(operand_1, operand_2, "or_result")?
.as_basic_value_enum())
}
///
/// Translates the bitwise XOR.
///
pub fn xor<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_xor(operand_1, operand_2, "xor_result")?
.as_basic_value_enum())
}
///
/// Translates the bitwise AND.
///
pub fn and<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_and(operand_1, operand_2, "and_result")?
.as_basic_value_enum())
}
///
/// Translates the bitwise shift left.
///
pub fn shift_left<'ctx, D>(
context: &mut Context<'ctx, D>,
shift: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let overflow_block = context.append_basic_block("shift_left_overflow");
let non_overflow_block = context.append_basic_block("shift_left_non_overflow");
let join_block = context.append_basic_block("shift_left_join");
let result_pointer = context.build_alloca(context.field_type(), "shift_left_result_pointer");
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64),
"shift_left_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
context.build_store(result_pointer, context.field_const(0))?;
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value =
context
.builder()
.build_left_shift(value, shift, "shift_left_non_overflow_result")?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_left_result")
}
///
/// Translates the bitwise shift right.
///
pub fn shift_right<'ctx, D>(
context: &mut Context<'ctx, D>,
shift: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let overflow_block = context.append_basic_block("shift_right_overflow");
let non_overflow_block = context.append_basic_block("shift_right_non_overflow");
let join_block = context.append_basic_block("shift_right_join");
let result_pointer = context.build_alloca(context.field_type(), "shift_right_result_pointer");
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64),
"shift_right_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
context.build_store(result_pointer, context.field_const(0))?;
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value = context.builder().build_right_shift(
value,
shift,
false,
"shift_right_non_overflow_result",
)?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_right_result")
}
///
/// Translates the arithmetic bitwise shift right.
///
pub fn shift_right_arithmetic<'ctx, D>(
context: &mut Context<'ctx, D>,
shift: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let overflow_block = context.append_basic_block("shift_right_arithmetic_overflow");
let overflow_positive_block =
context.append_basic_block("shift_right_arithmetic_overflow_positive");
let overflow_negative_block =
context.append_basic_block("shift_right_arithmetic_overflow_negative");
let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow");
let join_block = context.append_basic_block("shift_right_arithmetic_join");
let result_pointer = context.build_alloca(
context.field_type(),
"shift_right_arithmetic_result_pointer",
);
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64),
"shift_right_arithmetic_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
let sign_bit = context.builder().build_right_shift(
value,
context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64),
false,
"shift_right_arithmetic_sign_bit",
)?;
let condition_is_negative = context.builder().build_int_truncate_or_bit_cast(
sign_bit,
context.bool_type(),
"shift_right_arithmetic_sign_bit_truncated",
)?;
context.build_conditional_branch(
condition_is_negative,
overflow_negative_block,
overflow_positive_block,
)?;
context.set_basic_block(overflow_positive_block);
context.build_store(result_pointer, context.field_const(0))?;
context.build_unconditional_branch(join_block);
context.set_basic_block(overflow_negative_block);
context.build_store(result_pointer, context.field_type().const_all_ones())?;
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value = context.builder().build_right_shift(
value,
shift,
true,
"shift_right_arithmetic_non_overflow_result",
)?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_right_arithmetic_result")
}
///
/// Translates the `byte` instruction.
///
pub fn byte<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().byte,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"byte_call",
)
.expect("Always exists"))
}
+756
View File
@@ -0,0 +1,756 @@
//!
//! Translates a contract call.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::argument::Argument;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates a contract call.
///
/// If the `simulation_address` is specified, the call is substituted with another instruction
/// according to the specification.
///
#[allow(clippy::too_many_arguments)]
pub fn default<'ctx, D>(
_context: &mut Context<'ctx, D>,
_function: FunctionDeclaration<'ctx>,
_gas: inkwell::values::IntValue<'ctx>,
_address: inkwell::values::IntValue<'ctx>,
_value: Option<inkwell::values::IntValue<'ctx>>,
_input_offset: inkwell::values::IntValue<'ctx>,
_input_length: inkwell::values::IntValue<'ctx>,
_output_offset: inkwell::values::IntValue<'ctx>,
_output_length: inkwell::values::IntValue<'ctx>,
_constants: Vec<Option<num::BigUint>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!();
/*
if context.is_system_mode() {
let simulation_address = constants
.get_mut(1)
.and_then(|option| option.take())
.and_then(|value| value.to_u16());
match simulation_address {
Some(era_compiler_common::ERAVM_ADDRESS_TO_L1) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"to_l1",
)?;
let is_first = gas;
let in_0 = value.expect("Always exists");
let in_1 = input_offset;
return crate::eravm::extensions::general::to_l1(context, is_first, in_0, in_1);
}
Some(era_compiler_common::ERAVM_ADDRESS_CODE_ADDRESS) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"code_address",
)?;
return crate::eravm::extensions::general::code_source(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_PRECOMPILE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"precompile",
)?;
let in_0 = gas;
let gas_left = input_offset;
return crate::eravm::extensions::general::precompile(context, in_0, gas_left);
}
Some(era_compiler_common::ERAVM_ADDRESS_META) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"meta",
)?;
return crate::eravm::extensions::general::meta(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_MIMIC_CALL) => {
let address = gas;
let abi_data = input_offset;
let mimic = input_length;
return crate::eravm::extensions::call::mimic(
context,
context.llvm_runtime().mimic_call,
address,
mimic,
abi_data.as_basic_value_enum(),
vec![],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_MIMIC_CALL) => {
let address = gas;
let abi_data = input_offset;
let mimic = input_length;
let extra_value_1 = output_offset;
let extra_value_2 = output_length;
return crate::eravm::extensions::call::mimic(
context,
context.llvm_runtime().mimic_call,
address,
mimic,
abi_data.as_basic_value_enum(),
vec![extra_value_1, extra_value_2],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_MIMIC_CALL_BYREF) => {
let address = gas;
let mimic = input_length;
let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
return crate::eravm::extensions::call::mimic(
context,
context.llvm_runtime().mimic_call_byref,
address,
mimic,
abi_data.as_basic_value_enum(),
vec![],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_MIMIC_CALL_BYREF) => {
let address = gas;
let mimic = input_length;
let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let extra_value_1 = output_offset;
let extra_value_2 = output_length;
return crate::eravm::extensions::call::mimic(
context,
context.llvm_runtime().mimic_call_byref,
address,
mimic,
abi_data,
vec![extra_value_1, extra_value_2],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_RAW_FAR_CALL) => {
let address = gas;
let abi_data = input_length;
return crate::eravm::extensions::call::raw_far(
context,
context.llvm_runtime().modify(function, false)?,
address,
abi_data.as_basic_value_enum(),
output_offset,
output_length,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_RAW_FAR_CALL_BYREF) => {
let address = gas;
let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
return crate::eravm::extensions::call::raw_far(
context,
context.llvm_runtime().modify(function, true)?,
address,
abi_data,
output_offset,
output_length,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_CALL) => {
let address = gas;
let abi_data = input_length;
let extra_value_1 = value.expect("Always exists");
let extra_value_2 = input_offset;
let extra_value_3 = output_offset;
let extra_value_4 = output_length;
return crate::eravm::extensions::call::system(
context,
context.llvm_runtime().modify(function, false)?,
address,
abi_data.as_basic_value_enum(),
context.field_const(0),
context.field_const(0),
vec![extra_value_1, extra_value_2, extra_value_3, extra_value_4],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_CALL_BYREF) => {
let address = gas;
let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let extra_value_1 = value.expect("Always exists");
let extra_value_2 = input_offset;
let extra_value_3 = output_offset;
let extra_value_4 = output_length;
return crate::eravm::extensions::call::system(
context,
context.llvm_runtime().modify(function, true)?,
address,
abi_data,
context.field_const(0),
context.field_const(0),
vec![extra_value_1, extra_value_2, extra_value_3, extra_value_4],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SET_CONTEXT_VALUE_CALL) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"set_context_value",
)?;
let value = value.expect("Always exists");
return crate::eravm::extensions::general::set_context_value(context, value);
}
Some(era_compiler_common::ERAVM_ADDRESS_SET_PUBDATA_PRICE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"set_pubdata_price",
)?;
let price = gas;
return crate::eravm::extensions::general::set_pubdata_price(context, price);
}
Some(era_compiler_common::ERAVM_ADDRESS_INCREMENT_TX_COUNTER) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"increment_tx_counter",
)?;
return crate::eravm::extensions::general::increment_tx_counter(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_PTR_CALLDATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"get_global_ptr_calldata",
)?;
let pointer = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_POINTER)?;
let value = context.builder().build_ptr_to_int(
pointer.into_pointer_value(),
context.field_type(),
"calldata_abi_integer",
)?;
return Ok(value.as_basic_value_enum());
}
Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_CALL_FLAGS) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"get_global_call_flags",
)?;
return context.get_global_value(crate::eravm::GLOBAL_CALL_FLAGS);
}
Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_PTR_RETURN_DATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"get_global_ptr_return_data",
)?;
let pointer = context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?;
let value = context.builder().build_ptr_to_int(
pointer.into_pointer_value(),
context.field_type(),
"return_data_abi_integer",
)?;
return Ok(value.as_basic_value_enum());
}
Some(era_compiler_common::ERAVM_ADDRESS_EVENT_INITIALIZE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"event_initialize",
)?;
let operand_1 = gas;
let operand_2 = value.expect("Always exists");
return crate::eravm::extensions::general::event(
context, operand_1, operand_2, true,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_EVENT_WRITE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"event_initialize",
)?;
let operand_1 = gas;
let operand_2 = value.expect("Always exists");
return crate::eravm::extensions::general::event(
context, operand_1, operand_2, false,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_LOAD_CALLDATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_load_calldata",
)?;
return crate::eravm::extensions::abi::calldata_ptr_to_active(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_LOAD_RETURN_DATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_load_return_data",
)?;
return crate::eravm::extensions::abi::return_data_ptr_to_active(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_ADD) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_add",
)?;
let offset = gas;
return crate::eravm::extensions::abi::active_ptr_add_assign(context, offset);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_SHRINK) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_shrink",
)?;
let offset = gas;
return crate::eravm::extensions::abi::active_ptr_shrink_assign(context, offset);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_PACK) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_pack",
)?;
let data = gas;
return crate::eravm::extensions::abi::active_ptr_pack_assign(context, data);
}
Some(era_compiler_common::ERAVM_ADDRESS_MULTIPLICATION_HIGH_REGISTER) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"multiplication_high_register",
)?;
let operand_1 = gas;
let operand_2 = input_offset;
return crate::eravm::extensions::math::multiplication_512(
context, operand_1, operand_2,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_EXTRA_ABI_DATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"get_global_extra_abi_data",
)?;
let index = gas;
return crate::eravm::extensions::abi::get_extra_abi_data(context, index);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_LOAD) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_data_load",
)?;
let offset = gas;
return crate::eravm::extensions::abi::active_ptr_data_load(context, offset);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_SIZE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_data_size",
)?;
return crate::eravm::extensions::abi::active_ptr_data_size(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_COPY) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_data_copy",
)?;
let destination_offset = gas;
let source_offset = input_offset;
let size = input_length;
return crate::eravm::extensions::abi::active_ptr_data_copy(
context,
destination_offset,
source_offset,
size,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_DECLARE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"const_array_declare",
)?;
let index = constants
.get_mut(0)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array index is missing"))?
.to_u8()
.ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?;
let size = constants
.get_mut(2)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array size is missing"))?
.to_u16()
.ok_or_else(|| anyhow::anyhow!("Const array size must fit into 16 bits"))?;
return crate::eravm::extensions::const_array::declare(context, index, size);
}
Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_SET) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"const_array_set",
)?;
let index = constants
.get_mut(0)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array index is missing"))?
.to_u8()
.ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?;
let offset = constants
.get_mut(2)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array offset is missing"))?
.to_u16()
.ok_or_else(|| anyhow::anyhow!("Const array offset must fit into 16 bits"))?;
let value = constants
.get_mut(4)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array assigned value is missing"))?;
return crate::eravm::extensions::const_array::set(context, index, offset, value);
}
Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_FINALIZE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"const_array_finalize",
)?;
let index = constants
.get_mut(0)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array index is missing"))?
.to_u8()
.ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?;
return crate::eravm::extensions::const_array::finalize(context, index);
}
Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_GET) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"const_array_get",
)?;
let index = constants
.get_mut(0)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array index is missing"))?
.to_u8()
.ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?;
let offset = input_offset;
return crate::eravm::extensions::const_array::get(context, index, offset);
}
_ => {}
}
}
let identity_block = context.append_basic_block("contract_call_identity_block");
let ordinary_block = context.append_basic_block("contract_call_ordinary_block");
let join_block = context.append_basic_block("contract_call_join_block");
let result_pointer = context.build_alloca(context.field_type(), "contract_call_result_pointer");
context.build_store(result_pointer, context.field_const(0));
context.builder().build_switch(
address,
ordinary_block,
&[(
context.field_const(zkevm_opcode_defs::ADDRESS_IDENTITY.into()),
identity_block,
)],
)?;
{
context.set_basic_block(identity_block);
let result = identity(context, output_offset, input_offset, output_length)?;
context.build_store(result_pointer, result);
context.build_unconditional_branch(join_block);
}
context.set_basic_block(ordinary_block);
let result = if let Some(value) = value {
default_wrapped(
context,
function,
gas,
value,
address,
input_offset,
input_length,
output_offset,
output_length,
)?
} else {
let function = Runtime::default_call(context, function);
context
.build_call(
function,
&[
gas.as_basic_value_enum(),
address.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_offset.as_basic_value_enum(),
output_length.as_basic_value_enum(),
],
"default_call",
)
.expect("Always exists")
};
context.build_store(result_pointer, result);
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context.build_load(result_pointer, "contract_call_result");
Ok(result)
*/
}
///
/// Translates the Yul `linkersymbol` instruction.
///
pub fn linker_symbol<'ctx, D>(
context: &mut Context<'ctx, D>,
mut arguments: [Argument<'ctx>; 1],
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let path = arguments[0]
.original
.take()
.ok_or_else(|| anyhow::anyhow!("Linker symbol literal is missing"))?;
Ok(context
.resolve_library(path.as_str())?
.as_basic_value_enum())
}
///
/// Generates a custom request to a system contract.
///
pub fn request<'ctx, D>(
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
signature: &'static str,
arguments: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let signature_hash = crate::eravm::utils::keccak256(signature.as_bytes());
let signature_value = context.field_const_str_hex(signature_hash.as_str());
let calldata_size = context.field_const(
(era_compiler_common::BYTE_LENGTH_X32
+ (era_compiler_common::BYTE_LENGTH_FIELD * arguments.len())) as u64,
);
let calldata_array_pointer = context.build_alloca(
context.array_type(context.field_type(), arguments.len()),
"system_request_calldata_array_pointer",
);
for (index, argument) in arguments.into_iter().enumerate() {
let argument_pointer = context.build_gep(
calldata_array_pointer,
&[context.field_const(0), context.field_const(index as u64)],
context.field_type(),
"system_request_calldata_array_pointer",
);
context.build_store(argument_pointer, argument)?;
}
Ok(context
.build_invoke(
context.llvm_runtime().system_request,
&[
address.as_basic_value_enum(),
signature_value.as_basic_value_enum(),
calldata_size.as_basic_value_enum(),
calldata_array_pointer.value.as_basic_value_enum(),
],
"system_request_call",
)
.expect("Always exists"))
}
///
/// The default call wrapper, which redirects the call to the `msg.value` simulator if `msg.value`
/// is not zero.
///
#[allow(clippy::too_many_arguments)]
fn _default_wrapped<'ctx, D>(
context: &mut Context<'ctx, D>,
function: FunctionDeclaration<'ctx>,
gas: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
output_offset: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let value_zero_block = context.append_basic_block("contract_call_value_zero_block");
let value_non_zero_block = context.append_basic_block("contract_call_value_non_zero_block");
let value_join_block = context.append_basic_block("contract_call_value_join_block");
let result_pointer =
context.build_alloca(context.field_type(), "contract_call_address_result_pointer");
context.build_store(result_pointer, context.field_const(0))?;
let is_value_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
value,
context.field_const(0),
"contract_call_is_value_zero",
)?;
context.build_conditional_branch(is_value_zero, value_zero_block, value_non_zero_block)?;
context.set_basic_block(value_non_zero_block);
let abi_data = crate::eravm::utils::abi_data(
context,
input_offset,
input_length,
Some(gas),
AddressSpace::Heap,
true,
)?;
let result = crate::eravm::extensions::call::system(
context,
context.llvm_runtime().modify(function, false)?,
context.field_const(zkevm_opcode_defs::ADDRESS_MSG_VALUE.into()),
abi_data,
output_offset,
output_length,
vec![
value,
address,
context.field_const(u64::from(crate::eravm::r#const::NO_SYSTEM_CALL_BIT)),
],
)?;
context.build_store(result_pointer, result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_zero_block);
let function = Runtime::default_call(context, function);
let result = context
.build_call(
function,
&[
gas.as_basic_value_enum(),
address.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_offset.as_basic_value_enum(),
output_length.as_basic_value_enum(),
],
"default_call",
)
.expect("Always exists");
context.build_store(result_pointer, result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_join_block);
context.build_load(result_pointer, "contract_call_address_result")
}
///
/// Generates a memory copy loop repeating the behavior of the EVM `Identity` precompile.
///
fn _identity<'ctx, D>(
context: &mut Context<'ctx, D>,
destination: inkwell::values::IntValue<'ctx>,
source: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination,
"contract_call_identity_destination",
);
let source = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
source,
"contract_call_identity_source",
);
context.build_memcpy(
context.intrinsics().memory_copy,
destination,
source,
size,
"contract_call_memcpy_to_child",
)?;
Ok(context.field_const(1).as_basic_value_enum())
}
@@ -0,0 +1,88 @@
//!
//! Translates the calldata instructions.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use inkwell::types::BasicType;
///
/// Translates the calldata load.
///
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let calldata_pointer = context
.get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)?
.value
.as_pointer_value();
let offset = context.build_gep(
Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer),
&[offset],
context.field_type().as_basic_type_enum(),
"calldata_pointer_with_offset",
);
context
.build_load(offset, "calldata_value")
.map(|value| context.build_byte_swap(value))
}
///
/// Translates the calldata size.
///
pub fn size<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let value = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_SIZE)?;
Ok(value)
}
///
/// Translates the calldata copy.
///
pub fn copy<'ctx, D>(
context: &mut Context<'ctx, D>,
destination_offset: inkwell::values::IntValue<'ctx>,
source_offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
// TODO: Untested
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination_offset,
"calldata_copy_destination_pointer",
);
let calldata_pointer = context
.get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)?
.value
.as_pointer_value();
let source = context.build_gep(
Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer),
&[source_offset],
context.field_type().as_basic_type_enum(),
"calldata_pointer_with_offset",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"calldata_copy_memcpy_from_child",
)
}
@@ -0,0 +1,36 @@
//!
//! Translates the comparison operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the comparison operations.
///
/// There is not difference between the EVM and LLVM IR behaviors.
///
pub fn compare<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
operation: inkwell::IntPredicate,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let result = context.builder().build_int_compare(
operation,
operand_1,
operand_2,
"comparison_result",
)?;
let result = context.builder().build_int_z_extend_or_bit_cast(
result,
context.field_type(),
"comparison_result_extended",
)?;
Ok(result.as_basic_value_enum())
}
@@ -0,0 +1,189 @@
//!
//! Translates the context getter instructions.
//!
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `gas_limit` instruction.
///
pub fn gas_limit<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"blockGasLimit()",
vec![],
)
}
///
/// Translates the `gas_price` instruction.
///
pub fn gas_price<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"gasPrice()",
vec![],
)
}
///
/// Translates the `tx.origin` instruction.
///
pub fn origin<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"origin()",
vec![],
)
}
///
/// Translates the `chain_id` instruction.
///
pub fn chain_id<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"chainId()",
vec![],
)
}
///
/// Translates the `block_number` instruction.
///
pub fn block_number<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"getBlockNumber()",
vec![],
)
}
///
/// Translates the `block_timestamp` instruction.
///
pub fn block_timestamp<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"getBlockTimestamp()",
vec![],
)
}
///
/// Translates the `block_hash` instruction.
///
pub fn block_hash<'ctx, D>(
context: &mut Context<'ctx, D>,
index: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"getBlockHashEVM(uint256)",
vec![index],
)
}
///
/// Translates the `difficulty` instruction.
///
pub fn difficulty<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"difficulty()",
vec![],
)
}
///
/// Translates the `coinbase` instruction.
///
pub fn coinbase<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"coinbase()",
vec![],
)
}
///
/// Translates the `basefee` instruction.
///
pub fn basefee<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"baseFee()",
vec![],
)
}
///
/// Translates the `msize` instruction.
///
pub fn msize<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
+185
View File
@@ -0,0 +1,185 @@
//!
//! Translates the contract creation instructions.
//!
use inkwell::values::BasicValue;
use num::Zero;
use crate::eravm::context::argument::Argument;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the contract `create` instruction.
///
/// The instruction is simulated by a call to a system contract.
///
pub fn create<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let signature_hash_string =
crate::eravm::utils::keccak256(crate::eravm::DEPLOYER_SIGNATURE_CREATE.as_bytes());
let signature_hash = context.field_const_str_hex(signature_hash_string.as_str());
let salt = context.field_const(0);
let function = Runtime::deployer_call(context);
let result = context
.build_call(
function,
&[
value.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
signature_hash.as_basic_value_enum(),
salt.as_basic_value_enum(),
],
"create_deployer_call",
)
.expect("Always exists");
Ok(result)
}
///
/// Translates the contract `create2` instruction.
///
/// The instruction is simulated by a call to a system contract.
///
pub fn create2<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
salt: Option<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let signature_hash_string =
crate::eravm::utils::keccak256(crate::eravm::DEPLOYER_SIGNATURE_CREATE2.as_bytes());
let signature_hash = context.field_const_str_hex(signature_hash_string.as_str());
let salt = salt.unwrap_or_else(|| context.field_const(0));
let function = Runtime::deployer_call(context);
let result = context
.build_call(
function,
&[
value.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
signature_hash.as_basic_value_enum(),
salt.as_basic_value_enum(),
],
"create2_deployer_call",
)
.expect("Always exists");
Ok(result)
}
///
/// Translates the contract hash instruction, which is actually used to set the hash of the contract
/// being created, or other related auxiliary data.
///
/// Represents `dataoffset` in Yul and `PUSH [$]` in the EVM legacy assembly.
///
pub fn contract_hash<'ctx, D>(
context: &mut Context<'ctx, D>,
identifier: String,
) -> anyhow::Result<Argument<'ctx>>
where
D: Dependency + Clone,
{
let code_type = context
.code_type()
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
let parent = context.module().get_name().to_str().expect("Always valid");
let contract_path =
context
.resolve_path(identifier.as_str())
.map_err(|error| match code_type {
CodeType::Runtime if identifier.ends_with("_deployed") => {
anyhow::anyhow!("type({}).runtimeCode is not supported", identifier)
}
_ => error,
})?;
if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant(
context.field_const(0).as_basic_value_enum(),
num::BigUint::zero(),
));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier);
}
let hash_string = context.compile_dependency(identifier.as_str())?;
let hash_value = context
.field_const_str_hex(hash_string.as_str())
.as_basic_value_enum();
Ok(Argument::new_with_original(hash_value, hash_string))
}
///
/// Translates the deployer call header size instruction, Usually, the header consists of:
/// - the deployer contract method signature
/// - the salt if the call is `create2`, or zero if the call is `create1`
/// - the hash of the bytecode of the contract whose instance is being created
/// - the offset of the constructor arguments
/// - the length of the constructor arguments
///
/// If the call is `create1`, the space for the salt is still allocated, because the memory for the
/// header is allocated by the Yul or EVM legacy assembly before it is known which version of
/// `create` is going to be used.
///
/// Represents `datasize` in Yul and `PUSH #[$]` in the EVM legacy assembly.
///
pub fn header_size<'ctx, D>(
context: &mut Context<'ctx, D>,
identifier: String,
) -> anyhow::Result<Argument<'ctx>>
where
D: Dependency + Clone,
{
let code_type = context
.code_type()
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
let parent = context.module().get_name().to_str().expect("Always valid");
let contract_path =
context
.resolve_path(identifier.as_str())
.map_err(|error| match code_type {
CodeType::Runtime if identifier.ends_with("_deployed") => {
anyhow::anyhow!("type({}).runtimeCode is not supported", identifier)
}
_ => error,
})?;
if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant(
context.field_const(0).as_basic_value_enum(),
num::BigUint::zero(),
));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier);
}
let size_bigint = num::BigUint::from(crate::eravm::DEPLOYER_CALL_HEADER_SIZE);
let size_value = context
.field_const(crate::eravm::DEPLOYER_CALL_HEADER_SIZE as u64)
.as_basic_value_enum();
Ok(Argument::new_with_constant(size_value, size_bigint))
}
@@ -0,0 +1,49 @@
//!
//! Translates the cryptographic operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::Function as EraVMFunction;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `sha3` instruction.
///
pub fn sha3<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(offset.into())
/*
let offset_pointer = context.builder().build_int_to_ptr(
offset,
context.byte_type().ptr_type(AddressSpace::Heap.into()),
"sha3_offset_pointer",
)?;
Ok(context
.build_invoke(
context.llvm_runtime().sha3,
&[
offset_pointer.as_basic_value_enum(),
length.as_basic_value_enum(),
context
.bool_const(
context
.get_function(EraVMFunction::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER)
.is_some(),
)
.as_basic_value_enum(),
],
"sha3_call",
)
.expect("Always exists"))
*/
}
@@ -0,0 +1,50 @@
//!
//! Translates the value and balance operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `gas` instruction.
///
pub fn gas<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context.integer_const(256, 0).as_basic_value_enum())
}
///
/// Translates the `value` instruction.
///
pub fn value<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context.integer_const(256, 0).as_basic_value_enum())
}
///
/// Translates the `balance` instructions.
///
pub fn balance<'ctx, D>(
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_ETH_TOKEN.into()),
"balanceOf(uint256)",
vec![address],
)
}
@@ -0,0 +1,81 @@
//!
//! Translates a log or event call.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates a log or event call.
///
/// The decoding logic is implemented in a system contract, which is called from here.
///
/// There are several cases of the translation for the sake of efficiency, since the front-end
/// emits topics and values sequentially by one, but the LLVM intrinsic and bytecode instruction
/// accept two at once.
///
pub fn log<'ctx, D>(
context: &mut Context<'ctx, D>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
topics: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
/*
let failure_block = context.append_basic_block("event_failure_block");
let join_block = context.append_basic_block("event_join_block");
let gas = crate::eravm::evm::ether_gas::gas(context)?.into_int_value();
let abi_data = crate::eravm::utils::abi_data(
context,
input_offset,
input_length,
Some(gas),
AddressSpace::Heap,
true,
)?;
let mut extra_abi_data = Vec::with_capacity(1 + topics.len());
extra_abi_data.push(context.field_const(topics.len() as u64));
extra_abi_data.extend(topics);
let result = context
.build_call(
context.llvm_runtime().far_call,
crate::eravm::utils::external_call_arguments(
context,
abi_data.as_basic_value_enum(),
context.field_const(zkevm_opcode_defs::ADDRESS_EVENT_WRITER as u64),
extra_abi_data,
None,
)
.as_slice(),
"event_writer_call_external",
)
.expect("Always returns a value");
let result_status_code_boolean = context
.builder()
.build_extract_value(
result.into_struct_value(),
1,
"event_writer_external_result_status_code_boolean",
)
.expect("Always exists");
context.build_conditional_branch(
result_status_code_boolean.into_int_value(),
join_block,
failure_block,
)?;
context.set_basic_block(failure_block);
crate::eravm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?;
context.set_basic_block(join_block);
*/
Ok(())
}
@@ -0,0 +1,42 @@
//!
//! Translates the external code operations.
//!
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `extcodesize` instruction.
///
pub fn size<'ctx, D>(
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_ACCOUNT_CODE_STORAGE.into()),
"getCodeSize(uint256)",
vec![address],
)
}
///
/// Translates the `extcodehash` instruction.
///
pub fn hash<'ctx, D>(
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_ACCOUNT_CODE_STORAGE.into()),
"getCodeHash(uint256)",
vec![address],
)
}
@@ -0,0 +1,120 @@
//!
//! Translates the contract immutable operations.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the contract immutable load.
///
/// In the deploy code the values are read from the auxiliary heap.
/// In the runtime code they are requested from the system contract.
///
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
index: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
match context.code_type() {
None => {
anyhow::bail!("Immutables are not available if the contract part is undefined");
}
Some(CodeType::Deploy) => {
let index_double = context.builder().build_int_mul(
index,
context.field_const(2),
"immutable_load_index_double",
)?;
let offset_absolute = context.builder().build_int_add(
index_double,
context.field_const(
crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (3 * era_compiler_common::BYTE_LENGTH_FIELD) as u64,
),
"immutable_offset_absolute",
)?;
let immutable_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
offset_absolute,
"immutable_pointer",
);
context.build_load(immutable_pointer, "immutable_value")
}
Some(CodeType::Runtime) => {
todo!()
}
}
}
///
/// Translates the contract immutable store.
///
/// In the deploy code the values are written to the auxiliary heap at the predefined offset,
/// being prepared for returning to the system contract for saving.
///
/// Ignored in the runtime code.
///
pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>,
index: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
match context.code_type() {
None => {
anyhow::bail!("Immutables are not available if the contract part is undefined");
}
Some(CodeType::Deploy) => {
let index_double = context.builder().build_int_mul(
index,
context.field_const(2),
"immutable_load_index_double",
)?;
let index_offset_absolute = context.builder().build_int_add(
index_double,
context.field_const(
crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (2 * era_compiler_common::BYTE_LENGTH_FIELD) as u64,
),
"index_offset_absolute",
)?;
let index_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
index_offset_absolute,
"immutable_index_pointer",
);
context.build_store(index_offset_pointer, index)?;
let value_offset_absolute = context.builder().build_int_add(
index_offset_absolute,
context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64),
"value_offset_absolute",
)?;
let value_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
value_offset_absolute,
"immutable_value_pointer",
);
context.build_store(value_offset_pointer, value)?;
Ok(())
}
Some(CodeType::Runtime) => {
anyhow::bail!("Immutable writes are not available in the runtime code");
}
}
}
+98
View File
@@ -0,0 +1,98 @@
//!
//! Translates the mathematical operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `addmod` instruction.
///
pub fn add_mod<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
modulo: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().add_mod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
modulo.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
}
///
/// Translates the `mulmod` instruction.
///
pub fn mul_mod<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
modulo: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().mul_mod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
modulo.as_basic_value_enum(),
],
"mul_mod_call",
)
.expect("Always exists"))
}
///
/// Translates the `exp` instruction.
///
pub fn exponent<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
exponent: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().exp,
&[value.as_basic_value_enum(), exponent.as_basic_value_enum()],
"exp_call",
)
.expect("Always exists"))
}
///
/// Translates the `signextend` instruction.
///
pub fn sign_extend<'ctx, D>(
context: &mut Context<'ctx, D>,
bytes: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().sign_extend,
&[bytes.as_basic_value_enum(), value.as_basic_value_enum()],
"sign_extend_call",
)
.expect("Always exists"))
}
@@ -0,0 +1,87 @@
//!
//! Translates the heap memory operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `mload` instruction.
///
/// Uses the main heap.
///
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let pointer = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.field_type(),
offset,
"memory_load_pointer",
);
context.build_load(pointer, "memory_load_result")
}
///
/// Translates the `mstore` instruction.
///
/// Uses the main heap.
///
pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let pointer = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.field_type(),
offset,
"memory_store_pointer",
);
context.build_store(pointer, value)?;
Ok(())
}
///
/// Translates the `mstore8` instruction.
///
/// Uses the main heap.
///
pub fn store_byte<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
offset,
"mstore8_offset_pointer",
);
context.build_call(
context.llvm_runtime().mstore8,
&[
offset_pointer.value.as_basic_value_enum(),
value.as_basic_value_enum(),
],
"mstore8_call",
);
Ok(())
}
+21
View File
@@ -0,0 +1,21 @@
//!
//! The EVM instructions translation utils.
//!
pub mod arithmetic;
pub mod bitwise;
pub mod call;
pub mod calldata;
pub mod comparison;
pub mod context;
pub mod create;
pub mod crypto;
pub mod ether_gas;
pub mod event;
pub mod ext_code;
pub mod immutable;
pub mod math;
pub mod memory;
pub mod r#return;
pub mod return_data;
pub mod storage;
+129
View File
@@ -0,0 +1,129 @@
//!
//! Translates the transaction return operations.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `return` instruction.
///
/// Unlike in EVM, zkSync constructors return the array of contract immutables.
///
pub fn r#return<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
match context.code_type() {
None => {
anyhow::bail!("Return is not available if the contract part is undefined");
}
Some(CodeType::Deploy) => {
let immutables_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
context.field_const(crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA),
"immutables_offset_pointer",
);
context.build_store(
immutables_offset_pointer,
context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64),
)?;
let immutables_number_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
context.field_const(
crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (era_compiler_common::BYTE_LENGTH_FIELD as u64),
),
"immutables_number_pointer",
);
let immutable_values_size = context.immutables_size()?;
context.build_store(
immutables_number_pointer,
context.field_const(
(immutable_values_size / era_compiler_common::BYTE_LENGTH_FIELD) as u64,
),
)?;
let immutables_size = context.builder().build_int_mul(
context.field_const(immutable_values_size as u64),
context.field_const(2),
"immutables_size",
)?;
let return_data_length = context.builder().build_int_add(
immutables_size,
context.field_const((era_compiler_common::BYTE_LENGTH_FIELD * 2) as u64),
"return_data_length",
)?;
context.build_exit(
context.integer_const(32, 0),
context.field_const(crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA),
return_data_length,
)?;
}
Some(CodeType::Runtime) => {
context.build_exit(context.integer_const(32, 0), offset, length)?;
}
}
Ok(())
}
///
/// Translates the `revert` instruction.
///
pub fn revert<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
context.build_exit(context.integer_const(32, 1), offset, length)
}
///
/// Translates the `stop` instruction.
///
/// Is the same as `return(0, 0)`.
///
pub fn stop<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
r#return(
context,
context.integer_const(32, 0),
context.integer_const(32, 0),
)
}
///
/// Translates the `invalid` instruction.
///
/// Burns all gas using an out-of-bounds memory store, causing a panic.
///
pub fn invalid<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
crate::eravm::evm::memory::store(
context,
context.field_type().const_all_ones(),
context.field_const(0),
)?;
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
Ok(())
}
@@ -0,0 +1,93 @@
//!
//! Translates the return data instructions.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the return data size.
///
pub fn size<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
match context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_SIZE) {
Ok(global) => Ok(global),
Err(_error) => Ok(context.field_const(0).as_basic_value_enum()),
}
}
///
/// Translates the return data copy.
///
pub fn copy<'ctx, D>(
context: &mut Context<'ctx, D>,
destination_offset: inkwell::values::IntValue<'ctx>,
source_offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let error_block = context.append_basic_block("return_data_copy_error_block");
let join_block = context.append_basic_block("return_data_copy_join_block");
let return_data_size = self::size(context)?.into_int_value();
let copy_slice_end =
context
.builder()
.build_int_add(source_offset, size, "return_data_copy_slice_end")?;
let is_copy_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
copy_slice_end,
return_data_size,
"return_data_copy_is_out_of_bounds",
)?;
context.build_conditional_branch(is_copy_out_of_bounds, error_block, join_block)?;
context.set_basic_block(error_block);
crate::eravm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?;
context.set_basic_block(join_block);
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination_offset,
"return_data_copy_destination_pointer",
);
let return_data_pointer_global =
context.get_global(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?;
let return_data_pointer_pointer = return_data_pointer_global.into();
let return_data_pointer =
context.build_load(return_data_pointer_pointer, "return_data_pointer")?;
let source = context.build_gep(
Pointer::new(
context.byte_type(),
return_data_pointer_pointer.address_space,
return_data_pointer.into_pointer_value(),
),
&[source_offset],
context.byte_type().as_basic_type_enum(),
"return_data_source_pointer",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"return_data_copy_memcpy_from_return_data",
)?;
Ok(())
}
@@ -0,0 +1,92 @@
//!
//! Translates the storage operations.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the storage load.
///
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let position_pointer = Pointer::new_with_offset(
context,
AddressSpace::Storage,
context.field_type(),
position,
"storage_load_position_pointer",
);
context.build_load(position_pointer, "storage_load_value")
}
///
/// Translates the storage store.
///
pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let position_pointer = Pointer::new_with_offset(
context,
AddressSpace::Storage,
context.field_type(),
position,
"storage_store_position_pointer",
);
context.build_store(position_pointer, value)?;
Ok(())
}
///
/// Translates the transient storage load.
///
pub fn transient_load<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let position_pointer = Pointer::new_with_offset(
context,
AddressSpace::TransientStorage,
context.field_type(),
position,
"transient_storage_load_position_pointer",
);
context.build_load(position_pointer, "transient_storage_load_value")
}
///
/// Translates the transient storage store.
///
pub fn transient_store<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let position_pointer = Pointer::new_with_offset(
context,
AddressSpace::TransientStorage,
context.field_type(),
position,
"transient_storage_store_position_pointer",
);
context.build_store(position_pointer, value)?;
Ok(())
}
@@ -0,0 +1,225 @@
//!
//! Translates the ABI instructions of the EraVM Yul extension.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Generates an extra ABI data getter call.
///
pub fn get_extra_abi_data<'ctx, D>(
context: &mut Context<'ctx, D>,
index: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let extra_active_data_global = context.get_global(crate::eravm::GLOBAL_EXTRA_ABI_DATA)?;
let extra_active_data_pointer = extra_active_data_global.into();
let extra_active_data_element_pointer = context.build_gep(
extra_active_data_pointer,
&[context.field_const(0), index],
context.field_type().as_basic_type_enum(),
"extra_active_data_element_pointer",
);
context.build_load(
extra_active_data_element_pointer,
"extra_active_data_element_value",
)
}
///
/// Loads the calldata pointer to the active pointer.
///
pub fn calldata_ptr_to_active<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let calldata_pointer = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_POINTER)?;
context.set_global(
crate::eravm::GLOBAL_ACTIVE_POINTER,
context.byte_type().ptr_type(AddressSpace::Generic.into()),
AddressSpace::Stack,
calldata_pointer,
);
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Loads the return data pointer to the active pointer.
///
pub fn return_data_ptr_to_active<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let calldata_pointer = context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?;
context.set_global(
crate::eravm::GLOBAL_ACTIVE_POINTER,
context.byte_type().ptr_type(AddressSpace::Generic.into()),
AddressSpace::Stack,
calldata_pointer,
);
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Shifts the active pointer by the specified `offset`.
///
pub fn active_ptr_add_assign<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let active_pointer_shifted = context.build_gep(
Pointer::new(
context.byte_type(),
AddressSpace::Generic,
active_pointer.into_pointer_value(),
),
&[offset],
context.byte_type().as_basic_type_enum(),
"active_pointer_shifted",
);
context.set_global(
crate::eravm::GLOBAL_ACTIVE_POINTER,
context.byte_type().ptr_type(AddressSpace::Generic.into()),
AddressSpace::Stack,
active_pointer_shifted.value,
);
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Shrinks the active pointer by the specified `offset`.
///
pub fn active_ptr_shrink_assign<'ctx, D>(
_context: &mut Context<'ctx, D>,
_offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Writes the specified `data` into the upper 128 bits of the active pointer.
///
pub fn active_ptr_pack_assign<'ctx, D>(
_context: &mut Context<'ctx, D>,
_data: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Loads a single word from the active pointer to the stack.
///
pub fn active_ptr_data_load<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let active_pointer = context.build_gep(
Pointer::new(
context.byte_type(),
AddressSpace::Generic,
active_pointer.into_pointer_value(),
),
&[offset],
context.field_type().as_basic_type_enum(),
"active_pointer_with_offset",
);
context.build_load(active_pointer, "active_pointer_value")
}
///
/// Returns the active pointer data size.
///
pub fn active_ptr_data_size<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let active_pointer_value = context.builder().build_ptr_to_int(
active_pointer.into_pointer_value(),
context.field_type(),
"active_pointer_value",
)?;
let active_pointer_value_shifted = context.builder().build_right_shift(
active_pointer_value,
context.field_const((era_compiler_common::BIT_LENGTH_X32 * 3) as u64),
false,
"active_pointer_value_shifted",
)?;
let active_pointer_length = context.builder().build_and(
active_pointer_value_shifted,
context.field_const(u32::MAX as u64),
"active_pointer_length",
)?;
Ok(active_pointer_length.as_basic_value_enum())
}
///
/// Copies a chunk of data from the active pointer to the heap.
///
pub fn active_ptr_data_copy<'ctx, D>(
context: &mut Context<'ctx, D>,
destination_offset: inkwell::values::IntValue<'ctx>,
source_offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination_offset,
"active_pointer_data_copy_destination_pointer",
);
let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let source = context.build_gep(
Pointer::new(
context.byte_type(),
AddressSpace::Generic,
active_pointer.into_pointer_value(),
),
&[source_offset],
context.byte_type().as_basic_type_enum(),
"active_pointer_data_copy_source_pointer",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"active_pointer_data_copy_memcpy_from_child",
)?;
Ok(context.field_const(1).as_basic_value_enum())
}
@@ -0,0 +1,302 @@
//!
//! Translates the call instructions of the EraVM Yul extension.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Generates a mimic call.
///
/// The mimic call is a special type of call that can only be used in the system contracts of
/// zkSync. The call allows to call a contract with custom `msg.sender`, allowing to insert
/// system contracts as middlewares.
///
pub fn mimic<'ctx, D>(
context: &mut Context<'ctx, D>,
function: FunctionDeclaration<'ctx>,
address: inkwell::values::IntValue<'ctx>,
mimic: inkwell::values::IntValue<'ctx>,
abi_data: inkwell::values::BasicValueEnum<'ctx>,
extra_abi_data: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let status_code_result_pointer = context.build_alloca(
context.field_type(),
"mimic_call_result_status_code_pointer",
);
context.build_store(status_code_result_pointer, context.field_const(0))?;
let far_call_result = context
.build_call(
function,
crate::eravm::utils::external_call_arguments(
context,
abi_data,
address,
extra_abi_data,
Some(mimic),
)
.as_slice(),
"mimic_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
0,
"mimic_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_status_code_boolean = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
1,
"mimic_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"mimic_call_external_result_status_code",
)?;
context.build_store(status_code_result_pointer, result_status_code)?;
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
context.build_load(status_code_result_pointer, "mimic_call_status_code")
}
///
/// Generates a raw far call.
///
/// Such calls can accept extra ABI arguments passed via the virtual machine registers.
///
pub fn raw_far<'ctx, D>(
_context: &mut Context<'ctx, D>,
_function: FunctionDeclaration<'ctx>,
_address: inkwell::values::IntValue<'ctx>,
_abi_data: inkwell::values::BasicValueEnum<'ctx>,
_output_offset: inkwell::values::IntValue<'ctx>,
_output_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!();
/*
let status_code_result_pointer = context.build_alloca(
context.field_type(),
"system_far_call_result_status_code_pointer",
);
context.build_store(status_code_result_pointer, context.field_const(0));
let far_call_result = context
.build_call(
function,
crate::eravm::utils::external_call_arguments(context, abi_data, address, vec![], None)
.as_slice(),
"system_far_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
0,
"system_far_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_status_code_boolean = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
1,
"system_far_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"system_far_call_external_result_status_code",
);
context.build_store(status_code_result_pointer, result_status_code);
let source = result_abi_data_pointer;
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
output_offset,
"system_far_call_destination",
);
context.build_memcpy_return_data(
context.intrinsics().memory_copy_from_generic,
destination,
source,
output_length,
"system_far_call_memcpy_from_child",
);
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
let status_code_result =
context.build_load(status_code_result_pointer, "system_call_status_code");
Ok(status_code_result)
*/
}
///
/// Generates a system call.
///
/// Such calls can accept extra ABI arguments passed via the virtual machine registers. It is used,
/// for example, to pass the callee address and the Ether value to the `msg.value` simulator.
///
pub fn system<'ctx, D>(
context: &mut Context<'ctx, D>,
function: FunctionDeclaration<'ctx>,
address: inkwell::values::IntValue<'ctx>,
abi_data: inkwell::values::BasicValueEnum<'ctx>,
output_offset: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
extra_abi_data: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let status_code_result_pointer = context.build_alloca(
context.field_type(),
"system_far_call_result_status_code_pointer",
);
context.build_store(status_code_result_pointer, context.field_const(0))?;
let far_call_result = context
.build_call(
function,
crate::eravm::utils::external_call_arguments(
context,
abi_data,
address,
extra_abi_data,
None,
)
.as_slice(),
"system_far_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
0,
"system_far_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_status_code_boolean = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
1,
"system_far_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"system_far_call_external_result_status_code",
)?;
context.build_store(status_code_result_pointer, result_status_code)?;
let source = result_abi_data_pointer;
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
output_offset,
"system_far_call_destination",
);
context.build_memcpy_return_data(
context.intrinsics().memory_copy_from_generic,
destination,
source,
output_length,
"system_far_call_memcpy_from_child",
)?;
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
context.build_load(status_code_result_pointer, "system_call_status_code")
}
///
/// Checks if the instruction was called with a correct call type.
///
pub fn validate_call_type<'ctx>(
expected: FunctionDeclaration<'ctx>,
found: FunctionDeclaration<'ctx>,
instruction_name: &'static str,
) -> anyhow::Result<()> {
if expected != found {
anyhow::bail!(
"Only `{}` is allowed for the `{}` simulation, found `{}`",
expected.value.get_name().to_string_lossy(),
instruction_name,
found.value.get_name().to_string_lossy()
);
}
Ok(())
}
@@ -0,0 +1,107 @@
//!
//! Translates the const array instructions of the EraVM Yul extension.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Declares a constant array in the code section.
///
pub fn _declare<'ctx, D>(
context: &mut Context<'ctx, D>,
index: u8,
size: u16,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
context.yul_mut().const_array_declare(index, size)?;
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Sets a value in a constant array in the code section.
///
pub fn _set<'ctx, D>(
context: &mut Context<'ctx, D>,
index: u8,
offset: u16,
value: num::BigUint,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
context.yul_mut().const_array_set(index, offset, value)?;
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Finalizes a constant array in the code section, by extracting it from
/// the temporary compile-time storage, and initializing it in LLVM IR.
///
pub fn _finalize<'ctx, D>(
context: &mut Context<'ctx, D>,
index: u8,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let const_array = context.yul_mut().const_array_take(index)?;
let array_type = context.field_type().array_type(const_array.len() as u32);
let array_value = context.field_type().const_array(
const_array
.into_iter()
.map(|value| context.field_const_str_dec(value.to_string().as_str()))
.collect::<Vec<inkwell::values::IntValue<'ctx>>>()
.as_slice(),
);
context.set_global(
format!(
"{}{:03}",
crate::eravm::r#const::GLOBAL_CONST_ARRAY_PREFIX,
index
)
.as_str(),
array_type,
AddressSpace::Code,
array_value,
);
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Gets a value from a constant array in the code section.
///
pub fn _get<'ctx, D>(
context: &mut Context<'ctx, D>,
index: u8,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let identifier = format!(
"{}{:03}",
crate::eravm::r#const::GLOBAL_CONST_ARRAY_PREFIX,
index
);
let global = context.get_global(identifier.as_str())?;
let pointer = global.into();
let pointer = context.build_gep(
pointer,
&[context.field_const(0), offset],
context.field_type().as_basic_type_enum(),
format!("{}_pointer", identifier).as_str(),
);
context.build_load(pointer, format!("{}_value", identifier).as_str())
}
@@ -0,0 +1,112 @@
//!
//! Translates the general instructions of the EraVM Yul extension.
//!
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Generates a call to L1.
///
pub fn to_l1<'ctx, D>(
_context: &mut Context<'ctx, D>,
_is_first: inkwell::values::IntValue<'ctx>,
_in_0: inkwell::values::IntValue<'ctx>,
_in_1: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates a `code source` call.
///
pub fn code_source<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
///
/// Generates a precompile call.
///
pub fn precompile<'ctx, D>(
_context: &mut Context<'ctx, D>,
_in_0: inkwell::values::IntValue<'ctx>,
_gas_left: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
///
/// Generates a `meta` call.
///
pub fn meta<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates a `u128` context value setter call.
///
pub fn set_context_value<'ctx, D>(
_context: &mut Context<'ctx, D>,
_value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates a public data price setter call.
///
pub fn set_pubdata_price<'ctx, D>(
_context: &mut Context<'ctx, D>,
_value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates a transaction counter increment call.
///
pub fn increment_tx_counter<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates an event call.
///
pub fn event<'ctx, D>(
_context: &mut Context<'ctx, D>,
_operand_1: inkwell::values::IntValue<'ctx>,
_operand_2: inkwell::values::IntValue<'ctx>,
_is_initializer: bool,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
@@ -0,0 +1,52 @@
//!
//! Translates the math instructions of the EraVM Yul extension.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Performs a multiplication, returning the higher register, that is the overflown part.
///
pub fn multiplication_512<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let operand_1_extended = context.builder().build_int_z_extend_or_bit_cast(
operand_1,
context.integer_type(era_compiler_common::BIT_LENGTH_FIELD * 2),
"multiplication_512_operand_1_extended",
)?;
let operand_2_extended = context.builder().build_int_z_extend_or_bit_cast(
operand_2,
context.integer_type(era_compiler_common::BIT_LENGTH_FIELD * 2),
"multiplication_512_operand_2_extended",
)?;
let result_extended = context.builder().build_int_mul(
operand_1_extended,
operand_2_extended,
"multiplication_512_result_extended",
)?;
let result_shifted = context.builder().build_right_shift(
result_extended,
context.integer_const(
era_compiler_common::BIT_LENGTH_FIELD * 2,
era_compiler_common::BIT_LENGTH_FIELD as u64,
),
false,
"multiplication_512_result_shifted",
)?;
let result = context.builder().build_int_truncate_or_bit_cast(
result_shifted,
context.field_type(),
"multiplication_512_result",
)?;
Ok(result.as_basic_value_enum())
}
@@ -0,0 +1,9 @@
//!
//! The EraVM instructions translation utils.
//!
pub mod abi;
pub mod call;
pub mod const_array;
pub mod general;
pub mod math;
@@ -0,0 +1,33 @@
//!
//! The metadata hash mode.
//!
use std::str::FromStr;
use serde::Deserialize;
use serde::Serialize;
///
/// The metadata hash mode.
///
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MetadataHash {
/// Do not include bytecode hash.
#[serde(rename = "none")]
None,
/// The default keccak256 hash.
#[serde(rename = "keccak256")]
Keccak256,
}
impl FromStr for MetadataHash {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"none" => Ok(Self::None),
"keccak256" => Ok(Self::Keccak256),
_ => anyhow::bail!("Unknown bytecode hash mode: `{}`", string),
}
}
}

Some files were not shown because too many files have changed in this diff Show More