Skip to content

Commit

Permalink
ABI array packing
Browse files Browse the repository at this point in the history
release notes
  • Loading branch information
g-r-a-n-t committed Jan 5, 2021
1 parent 6e18c79 commit 5f240d1
Show file tree
Hide file tree
Showing 16 changed files with 559 additions and 262 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 57 additions & 49 deletions compiler/src/yul/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,76 @@ use fe_semantics::namespace::types::{
};
use yultsur::*;

/// Builds a contract constructor.
/// Builds a constructor for a contract with no init function.
///
/// Takes an optional init function and its parameter types.
pub fn build(
init: Option<(yul::Statement, Vec<FixedSize>)>,
/// The contract is simply deployed by loading the code into memory and
/// returning it.
pub fn build() -> yul::Code {
// we get the deployment statements and wrap them in a code block
let deployment = deployment();
code! { [deployment...] }
}

/// Builds a constructor for a contract with an init function.
///
/// We include the entire contact runtime inside of the constructor (without the
/// ABI dispatcher), run the init function, and return the contract code.
pub fn build_with_init(
init_func: yul::Statement,
init_params: Vec<FixedSize>,
runtime: Vec<yul::Statement>,
) -> yul::Code {
// statements that return the contract code
let deploy_stmts = statements! {
(let size := datasize("runtime"))
(datacopy(0, (dataoffset("runtime")), size))
(return(0, size))
};
// get the deplyment statements
let deployment = deployment();
// we need to decode the init parameters before passing them into `__init__`
// `params_start_mem` is added to the scope of the code block found below
let decoded_params = abi_operations::decode(
init_params,
expression! { params_start_mem },
AbiDecodeLocation::Memory,
);
// the name of the user defined init function ager it is mapped
let init_func_name = identifier! { ("$$__init__") };

let block = if let Some((init, params)) = init {
// build a constructor with an init function
// Build a constructor that runs a user defined init function. Parameters for
// init functions are appended to the end of the initialization code.
//
// The start of the init parameters in code is stored in `params_start_code`.
// The parameters are immediately copied from this location into memory at
// `mem_start`. From there, parameters are decoded and passed into the
// init function.
code! {
// copy params to memory where they can be decoded
(let params_start_code := datasize("Contract"))
(let params_end_code := codesize())
(let params_size := sub(params_end_code, params_start_code))
(let params_start_mem := alloc(params_size))
(codecopy(params_start_mem, params_start_code, params_size))

// decode operations for `__init__` parameters
let decoded_params = abi_operations::decode(
params,
expression! { params_start_mem },
AbiDecodeLocation::Memory,
);
// add init function amd call it
[init_func]
([init_func_name]([decoded_params...]))

// Build a constructor that runs a user defined init function. Parameters for
// init functions are appended to the end of the initialization code.
//
// The start of the init parameters in code is stored in `params_start_code`.
// The parameters are immediately copied from this location into memory at
// `mem_start`. From there, parameters are decoded and passed into the
// init function.
let init_func = identifier! { ("$$__init__") };
block! {
// copy params to memory where they can be decoded
(let params_start_code := datasize("Contract"))
(let params_end_code := codesize())
(let params_size := sub(params_end_code, params_start_code))
(let params_start_mem := alloc(params_size))
(codecopy(params_start_mem, params_start_code, params_size))
// add the runtime functions
[runtime...]

// add init function amd call it
[init]
([init_func]([decoded_params...]))

// add the runtime functions
[runtime...]

// deploy the contract
[deploy_stmts...]
}
} else {
// build a constructor without an init function
block! { [deploy_stmts...] }
};
// deploy the contract
[deployment...]
}
}

yul::Code { block }
fn deployment() -> Vec<yul::Statement> {
statements! {
(let size := datasize("runtime"))
(datacopy(0, (dataoffset("runtime")), size))
(return(0, size))
}
}

#[test]
fn test_constructor_without_func() {
assert_eq!(
build(None, vec![]).to_string(),
build().to_string(),
r#"code { let size := datasize("runtime") datacopy(0, dataoffset("runtime"), size) return(0, size) }"#,
)
}
52 changes: 14 additions & 38 deletions compiler/src/yul/mappers/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
use crate::errors::CompileError;
use crate::yul::constructor;
use crate::yul::mappers::functions;
use crate::yul::runtime::abi_dispatcher as runtime_abi;
use crate::yul::runtime::functions::abi as abi_functions;
use crate::yul::runtime::functions::data as data_functions;
use crate::yul::runtime;
use fe_parser::ast as fe;
use fe_parser::span::Spanned;
use fe_semantics::{
Context,
RuntimeOperations,
};
use fe_semantics::Context;
use yultsur::*;

/// Builds a Yul object from a Fe contract.
pub fn contract_def(
context: &Context,
stmt: &Spanned<fe::ModuleStmt>,
) -> Result<yul::Object, CompileError> {
if let (Some(attributes), fe::ModuleStmt::ContractDef { name: _, body }) =
(context.get_contract(stmt), &stmt.node)
{
if let fe::ModuleStmt::ContractDef { name: _, body } = &stmt.node {
let mut init = None;
let mut user_functions = vec![];

Expand All @@ -38,31 +31,26 @@ pub fn contract_def(
}
}

let runtime = {
let mut runtime = data_functions::std();
runtime.append(&mut build_runtime_functions(
attributes.runtime_operations.to_owned(),
));
runtime.append(&mut user_functions);
runtime
let constructor = if let Some((init_func, init_params)) = init {
let init_runtime = [runtime::build(context, stmt), user_functions.clone()].concat();
constructor::build_with_init(init_func, init_params, init_runtime)
} else {
constructor::build()
};

let runtime_with_dispatcher = {
let mut runtime = runtime.clone();
runtime.push(runtime_abi::dispatcher(
attributes.public_functions.to_owned(),
)?);
runtime
};
let runtime = runtime::build_with_abi_dispatcher(context, stmt);

return Ok(yul::Object {
name: identifier! { Contract },
code: constructor::build(init, runtime),
code: constructor,
objects: vec![yul::Object {
name: identifier! { runtime },
code: yul::Code {
block: yul::Block {
statements: runtime_with_dispatcher,
statements: statements! {
[user_functions...]
[runtime...]
},
},
},
objects: vec![],
Expand All @@ -72,15 +60,3 @@ pub fn contract_def(

unreachable!()
}

fn build_runtime_functions(functions: Vec<RuntimeOperations>) -> Vec<yul::Statement> {
functions
.into_iter()
.map(|function| match function {
RuntimeOperations::AbiEncode { params } => abi_functions::encode(params),
RuntimeOperations::AbiDecode { param, location } => {
abi_functions::decode(param, location)
}
})
.collect()
}
1 change: 1 addition & 0 deletions compiler/src/yul/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub fn decode_name<T: AbiEncoding>(typ: &T, location: AbiDecodeLocation) -> yul:

identifier! { (full_name) }
}

#[cfg(test)]
mod tests {
use crate::yul::names::{
Expand Down
15 changes: 7 additions & 8 deletions compiler/src/yul/operations/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,16 @@ pub fn decode<T: AbiEncoding>(
start: yul::Expression,
location: AbiDecodeLocation,
) -> Vec<yul::Expression> {
let heads = utils::abi_head_offsets(&types).0.into_iter().map(|offset| {
let offset = literal_expression! { (offset) };
expression! { add([start.clone()], [offset]) }
let offsets = utils::abi_head_offsets(&types).0.into_iter().map(|offset| {
literal_expression! { (offset) }
});
let typed_heads = types.iter().zip(heads);
let typed_offsets = types.iter().zip(offsets);

typed_heads
typed_offsets
.into_iter()
.map(|(typ, head_ptr)| {
.map(|(typ, offset)| {
let func_name = names::decode_name(typ, location.clone());
expression! { [func_name]([start.clone()], [head_ptr]) }
expression! { [func_name]([start.clone()], [offset]) }
})
.collect()
}
Expand Down Expand Up @@ -115,7 +114,7 @@ mod tests {
AbiDecodeLocation::Calldata
)[0]
.to_string(),
"abi_decode_string26_calldata(42, add(42, 0))"
"abi_decode_string26_calldata(42, 0)"
)
}
}
7 changes: 3 additions & 4 deletions compiler/src/yul/runtime/abi_dispatcher.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::abi::utils as abi_utils;
use crate::errors::CompileError;
use crate::yul::names;
use crate::yul::operations::abi as abi_operations;
use fe_semantics::namespace::types::{
Expand All @@ -11,16 +10,16 @@ use fe_semantics::FunctionAttributes;
use yultsur::*;

/// Builds a switch statement that dispatches calls to the contract.
pub fn dispatcher(attributes: Vec<FunctionAttributes>) -> Result<yul::Statement, CompileError> {
pub fn dispatcher(attributes: Vec<FunctionAttributes>) -> yul::Statement {
let arms = attributes
.iter()
.map(|arm| dispatch_arm(arm.to_owned()))
.collect::<Vec<_>>();

Ok(switch! {
switch! {
switch (cloadn(0, 4))
[arms...]
})
}
}

fn dispatch_arm(attributes: FunctionAttributes) -> yul::Case {
Expand Down
1 change: 0 additions & 1 deletion compiler/src/yul/runtime/builder.rs

This file was deleted.

Loading

0 comments on commit 5f240d1

Please sign in to comment.