From b9cbddb74f1682947d1d1f0a34ea91bd67856835 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Thu, 18 Jun 2026 11:32:01 +0300 Subject: [PATCH 01/15] codegen: introduce TargetCodegen trait and delegate target dispatch Add the TargetCodegen strategy trait (interface.rs) plus a make_target factory (targets/mod.rs) that the orchestrator drives instead of branching on ns.target. Re-export EventEmitter from interface.rs as the second boundary trait (visibility bumped to pub(crate)). Signed-off-by: Islam-Imad --- src/codegen/dispatch/mod.rs | 18 -------- src/codegen/events/mod.rs | 5 ++- src/codegen/interface.rs | 32 +++++++++++++ src/codegen/mod.rs | 49 +++++++------------- src/codegen/targets/mod.rs | 90 +++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 53 deletions(-) create mode 100644 src/codegen/interface.rs create mode 100644 src/codegen/targets/mod.rs diff --git a/src/codegen/dispatch/mod.rs b/src/codegen/dispatch/mod.rs index be718d9ec..4293f5b43 100644 --- a/src/codegen/dispatch/mod.rs +++ b/src/codegen/dispatch/mod.rs @@ -1,23 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -use super::{cfg::ControlFlowGraph, Options}; -use crate::{sema::ast::Namespace, Target}; - pub(crate) mod polkadot; pub(super) mod solana; pub(super) mod soroban; - -pub(super) fn function_dispatch( - contract_no: usize, - all_cfg: &mut [ControlFlowGraph], - ns: &mut Namespace, - opt: &Options, -) -> Vec { - match &ns.target { - Target::Solana => vec![solana::function_dispatch(contract_no, all_cfg, ns, opt)], - Target::Polkadot { .. } | Target::EVM => { - polkadot::function_dispatch(contract_no, all_cfg, ns, opt) - } - Target::Soroban => soroban::function_dispatch(contract_no, all_cfg, ns, opt), - } -} diff --git a/src/codegen/events/mod.rs b/src/codegen/events/mod.rs index 16b49320d..ae1bb3e39 100644 --- a/src/codegen/events/mod.rs +++ b/src/codegen/events/mod.rs @@ -17,7 +17,10 @@ use solang_parser::pt; /// This traits delineates the common behavior of event emission. As each target uses a different /// encoding scheme, there must be an implementation of this trait for each. -pub(super) trait EventEmitter { +/// +/// Re-exported from [`crate::codegen::interface`] as part of the target-codegen boundary; it is +/// `pub(crate)` so it can be named there. +pub(crate) trait EventEmitter { /// Generate the CFG instructions for emitting an event. /// All necessary code analysis should have been done during parsing and 'sema'; /// If code generation does not work here, there is a bug in the compiler. diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs new file mode 100644 index 000000000..326843b53 --- /dev/null +++ b/src/codegen/interface.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +use crate::codegen::cfg::ControlFlowGraph; +use crate::codegen::Options; +use crate::sema::ast::Namespace; + +/// The per-event emission strategy, produced by a target. Defined in +/// [`crate::codegen::events`]; re-exported here so both boundary traits have a +/// single home. `TargetCodegen::event_emitter` is wired to return it in a later phase, +/// hence the allow until the first in-crate use lands. +#[allow(unused_imports)] +pub(crate) use crate::codegen::events::EventEmitter; + +pub(crate) trait TargetCodegen { + /// Pre-CFG validation. Runs after storage layout, before any CFG is built. + fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace) {} + + /// Post-CFG validation; needs the freshly built CFGs. + fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace) {} + + /// Build the dispatcher CFG(s) appended after every function CFG is generated. + fn function_dispatch( + &self, + contract_no: usize, + all_cfg: &mut [ControlFlowGraph], + ns: &mut Namespace, + opt: &Options, + ) -> Vec; + + /// Whole-program post-processing, called once after every contract's CFGs. + fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {} +} diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 7b496bc02..2e2e62de8 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod encoding; pub(crate) mod error; mod events; mod expression; +mod interface; pub(super) mod polkadot; mod reaching_definitions; pub mod revert; @@ -20,6 +21,7 @@ mod statements; mod storage; mod strength_reduce; pub(crate) mod subexpression_elimination; +mod targets; mod tests; mod undefined_variable; mod unused_variable; @@ -29,9 +31,8 @@ mod yul; use self::{ cfg::{optimize_and_check_cfg, ControlFlowGraph, Instr}, - dispatch::function_dispatch, expression::expression, - solana_accounts::account_collection::collect_accounts_from_contract, + interface::TargetCodegen, vartable::Vartable, }; use crate::sema::ast::{ @@ -41,7 +42,6 @@ use crate::{sema::ast, Target}; use std::cmp::Ordering; use crate::codegen::cfg::ASTFunction; -use crate::codegen::solana_accounts::account_management::manage_contract_accounts; use crate::codegen::yul::generate_yul_function_cfg; use crate::sema::diagnostics::Diagnostics; use crate::sema::eval::eval_const_number; @@ -247,6 +247,8 @@ pub fn codegen(ns: &mut Namespace, opt: &Options) { return; } + let target = targets::make_target(ns); + let mut contracts_done = Vec::new(); contracts_done.resize(ns.contracts.len(), false); @@ -272,7 +274,7 @@ pub fn codegen(ns: &mut Namespace, opt: &Options) { continue; } - contract(contract_no, ns, opt); + contract(contract_no, ns, opt, target.as_ref()); if ns.diagnostics.any_errors() { return; @@ -282,37 +284,18 @@ pub fn codegen(ns: &mut Namespace, opt: &Options) { } } - if ns.target == Target::Solana { - for contract_no in 0..ns.contracts.len() { - if ns.contracts[contract_no].instantiable { - let diag = collect_accounts_from_contract(contract_no, ns); - ns.diagnostics.extend(diag); - } - } + target.post_process_program(ns, opt); - for contract_no in 0..ns.contracts.len() { - if ns.contracts[contract_no].instantiable { - manage_contract_accounts(contract_no, ns); - } - } - } ns.diagnostics.sort_and_dedup(); } -fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options) { +fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options, target: &dyn TargetCodegen) { if !ns.diagnostics.any_errors() && ns.contracts[contract_no].instantiable { layout(contract_no, ns); - if ns.target == Target::Soroban { - soroban::validate_accessor_abi_types(contract_no, ns); - if ns.diagnostics.any_errors() { - return; - } - - soroban::validate_event_abi_types(contract_no, ns); - if ns.diagnostics.any_errors() { - return; - } + target.validate_contract(contract_no, ns); + if ns.diagnostics.any_errors() { + return; } let mut cfg_no = 0; @@ -372,14 +355,12 @@ fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options) { ns.contracts[contract_no].default_constructor = Some((func, cfg_no)); } - if ns.target == Target::Soroban { - soroban::validate_abi_types(&all_cfg, ns); - if ns.diagnostics.any_errors() { - return; - } + target.validate_cfgs(&all_cfg, ns); + if ns.diagnostics.any_errors() { + return; } - for mut dispatch_cfg in function_dispatch(contract_no, &mut all_cfg, ns, opt) { + for mut dispatch_cfg in target.function_dispatch(contract_no, &mut all_cfg, ns, opt) { optimize_and_check_cfg(&mut dispatch_cfg, ns, ASTFunction::None, opt); all_cfg.push(dispatch_cfg); } diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs new file mode 100644 index 000000000..d4883aff9 --- /dev/null +++ b/src/codegen/targets/mod.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 + +use crate::codegen::cfg::ControlFlowGraph; +use crate::codegen::interface::TargetCodegen; +use crate::codegen::solana_accounts::account_collection::collect_accounts_from_contract; +use crate::codegen::solana_accounts::account_management::manage_contract_accounts; +use crate::codegen::{dispatch, soroban, Options}; +use crate::sema::ast::Namespace; +use crate::Target; + +pub(crate) fn make_target(ns: &Namespace) -> Box { + match &ns.target { + Target::Soroban => Box::new(SorobanTarget), + Target::Solana => Box::new(SolanaTarget), + // EVM reuses the Polkadot codegen path — intentional, not a gap. + Target::Polkadot { .. } | Target::EVM => Box::new(PolkadotTarget), + } +} + +pub(crate) struct SorobanTarget; +pub(crate) struct SolanaTarget; +pub(crate) struct PolkadotTarget; + +impl TargetCodegen for SorobanTarget { + fn validate_contract(&self, contract_no: usize, ns: &mut Namespace) { + soroban::validate_accessor_abi_types(contract_no, ns); + if ns.diagnostics.any_errors() { + return; + } + soroban::validate_event_abi_types(contract_no, ns); + } + + fn validate_cfgs(&self, all_cfg: &[ControlFlowGraph], ns: &mut Namespace) { + soroban::validate_abi_types(all_cfg, ns); + } + + fn function_dispatch( + &self, + contract_no: usize, + all_cfg: &mut [ControlFlowGraph], + ns: &mut Namespace, + opt: &Options, + ) -> Vec { + dispatch::soroban::function_dispatch(contract_no, all_cfg, ns, opt) + } +} + +impl TargetCodegen for SolanaTarget { + fn function_dispatch( + &self, + contract_no: usize, + all_cfg: &mut [ControlFlowGraph], + ns: &mut Namespace, + opt: &Options, + ) -> Vec { + vec![dispatch::solana::function_dispatch( + contract_no, + all_cfg, + ns, + opt, + )] + } + + fn post_process_program(&self, ns: &mut Namespace, _opt: &Options) { + for contract_no in 0..ns.contracts.len() { + if ns.contracts[contract_no].instantiable { + let diag = collect_accounts_from_contract(contract_no, ns); + ns.diagnostics.extend(diag); + } + } + + for contract_no in 0..ns.contracts.len() { + if ns.contracts[contract_no].instantiable { + manage_contract_accounts(contract_no, ns); + } + } + } +} + +impl TargetCodegen for PolkadotTarget { + fn function_dispatch( + &self, + contract_no: usize, + all_cfg: &mut [ControlFlowGraph], + ns: &mut Namespace, + opt: &Options, + ) -> Vec { + dispatch::polkadot::function_dispatch(contract_no, all_cfg, ns, opt) + } +} From 9013b469dfbcee09255d2b999fc07c20ec43dc24 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Thu, 18 Jun 2026 12:16:19 +0300 Subject: [PATCH 02/15] codegen: relocate Soroban target into targets/soroban/ Move soroban.rs, dispatch/soroban.rs, encoding/soroban_encoding.rs and events/soroban.rs under targets/soroban/ as history-preserving renames, and move the SorobanTarget impl alongside them. Repoint imports and bump the cross-boundary helpers to pub(crate). Pure relocation: golden CFG tests pass with zero CHECK edits. Signed-off-by: Islam-Imad --- src/codegen/dispatch/mod.rs | 1 - src/codegen/encoding/mod.rs | 3 +- src/codegen/events/mod.rs | 3 +- src/codegen/expression.rs | 5 +- src/codegen/mod.rs | 5 +- src/codegen/storage.rs | 4 +- src/codegen/targets/mod.rs | 31 ++--------- .../soroban/dispatch.rs} | 2 +- .../soroban/encoding.rs} | 0 .../soroban.rs => targets/soroban/events.rs} | 10 ++-- .../{soroban.rs => targets/soroban/mod.rs} | 51 +++++++++++++++---- 11 files changed, 62 insertions(+), 53 deletions(-) rename src/codegen/{dispatch/soroban.rs => targets/soroban/dispatch.rs} (98%) rename src/codegen/{encoding/soroban_encoding.rs => targets/soroban/encoding.rs} (100%) rename src/codegen/{events/soroban.rs => targets/soroban/events.rs} (90%) rename src/codegen/{soroban.rs => targets/soroban/mod.rs} (93%) diff --git a/src/codegen/dispatch/mod.rs b/src/codegen/dispatch/mod.rs index 4293f5b43..ff9c29d40 100644 --- a/src/codegen/dispatch/mod.rs +++ b/src/codegen/dispatch/mod.rs @@ -2,4 +2,3 @@ pub(crate) mod polkadot; pub(super) mod solana; -pub(super) mod soroban; diff --git a/src/codegen/encoding/mod.rs b/src/codegen/encoding/mod.rs index 191738bcd..26030101e 100644 --- a/src/codegen/encoding/mod.rs +++ b/src/codegen/encoding/mod.rs @@ -11,13 +11,12 @@ mod borsh_encoding; mod buffer_validator; pub(super) mod scale_encoding; -pub mod soroban_encoding; use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::encoding::borsh_encoding::BorshEncoding; use crate::codegen::encoding::scale_encoding::ScaleEncoding; -use crate::codegen::encoding::soroban_encoding::{soroban_decode, soroban_encode}; use crate::codegen::expression::load_storage; +use crate::codegen::targets::soroban::encoding::{soroban_decode, soroban_encode}; use crate::codegen::vartable::Vartable; use crate::codegen::{Builtin, Expression}; use crate::sema::ast::{ArrayLength, Namespace, RetrieveType, StructType, Type, Type::Uint}; diff --git a/src/codegen/events/mod.rs b/src/codegen/events/mod.rs index ae1bb3e39..330cd50c3 100644 --- a/src/codegen/events/mod.rs +++ b/src/codegen/events/mod.rs @@ -2,12 +2,11 @@ mod polkadot; mod solana; -mod soroban; use crate::codegen::cfg::ControlFlowGraph; use crate::codegen::events::polkadot::PolkadotEventEmitter; use crate::codegen::events::solana::SolanaEventEmitter; -use crate::codegen::events::soroban::SorobanEventEmitter; +use crate::codegen::targets::soroban::events::SorobanEventEmitter; use crate::codegen::vartable::Vartable; use crate::codegen::Options; use crate::sema::ast; diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 6dbed71ad..ac8665fae 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -1,13 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 -use super::encoding::soroban_encoding::{soroban_decode_arg, soroban_encode_arg}; -use super::encoding::{abi_decode, abi_encode, soroban_encoding::soroban_encode}; +use super::encoding::{abi_decode, abi_encode}; use super::revert::{ assert_failure, expr_assert, log_runtime_error, require, PanicCode, SolidityError, }; use super::storage::{ array_offset, array_pop, array_push, storage_slots_array_pop, storage_slots_array_push, }; +use super::targets::soroban::encoding::soroban_encode; +use super::targets::soroban::encoding::{soroban_decode_arg, soroban_encode_arg}; use super::{ cfg::{ControlFlowGraph, Instr, InternalCallTy}, vartable::Vartable, diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 2e2e62de8..bca688485 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -16,7 +16,6 @@ mod reaching_definitions; pub mod revert; mod solana_accounts; mod solana_deploy; -mod soroban; mod statements; mod storage; mod strength_reduce; @@ -48,12 +47,12 @@ use crate::sema::eval::eval_const_number; use crate::sema::Recurse; #[cfg(feature = "wasm_opt")] use contract_build::OptimizationPasses; -use encoding::soroban_encoding::soroban_encode_arg; use num_bigint::{BigInt, Sign}; use num_rational::BigRational; use num_traits::{FromPrimitive, Zero}; use solang_parser::diagnostics::Diagnostic; use solang_parser::{pt, pt::CodeLocation}; +use targets::soroban::encoding::soroban_encode_arg; // The sizeof(struct account_data_header) pub const SOLANA_FIRST_OFFSET: u64 = 16; @@ -390,7 +389,7 @@ fn storage_initializer(contract_no: usize, ns: &mut Namespace, opt: &Options) -> let mut value = if let Some(init) = &var.initializer { expression(init, &mut cfg, contract_no, None, ns, &mut vartab, opt) } else if soroban_init_with_vec { - soroban::soroban_vec_new(&var.loc, &var.ty, &mut cfg, &mut vartab) + targets::soroban::soroban_vec_new(&var.loc, &var.ty, &mut cfg, &mut vartab) } else { continue; }; diff --git a/src/codegen/storage.rs b/src/codegen/storage.rs index bb477c36c..722a89843 100644 --- a/src/codegen/storage.rs +++ b/src/codegen/storage.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -use crate::codegen::encoding::soroban_encoding::soroban_encode_arg; +use crate::codegen::targets::soroban::encoding::soroban_encode_arg; use crate::codegen::Expression; use crate::sema::ast; use crate::Target; @@ -15,7 +15,7 @@ use super::revert::SolidityError; use super::Options; use super::{ cfg::{ControlFlowGraph, Instr}, - soroban::{soroban_storage_pop, soroban_storage_push}, + targets::soroban::{soroban_storage_pop, soroban_storage_push}, vartable::Vartable, }; use crate::codegen::revert::{assert_failure, log_runtime_error}; diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index d4883aff9..114c68c95 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -1,13 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 +pub(crate) mod soroban; + use crate::codegen::cfg::ControlFlowGraph; use crate::codegen::interface::TargetCodegen; use crate::codegen::solana_accounts::account_collection::collect_accounts_from_contract; use crate::codegen::solana_accounts::account_management::manage_contract_accounts; -use crate::codegen::{dispatch, soroban, Options}; +use crate::codegen::{dispatch, Options}; use crate::sema::ast::Namespace; use crate::Target; +use self::soroban::SorobanTarget; + pub(crate) fn make_target(ns: &Namespace) -> Box { match &ns.target { Target::Soroban => Box::new(SorobanTarget), @@ -17,34 +21,9 @@ pub(crate) fn make_target(ns: &Namespace) -> Box { } } -pub(crate) struct SorobanTarget; pub(crate) struct SolanaTarget; pub(crate) struct PolkadotTarget; -impl TargetCodegen for SorobanTarget { - fn validate_contract(&self, contract_no: usize, ns: &mut Namespace) { - soroban::validate_accessor_abi_types(contract_no, ns); - if ns.diagnostics.any_errors() { - return; - } - soroban::validate_event_abi_types(contract_no, ns); - } - - fn validate_cfgs(&self, all_cfg: &[ControlFlowGraph], ns: &mut Namespace) { - soroban::validate_abi_types(all_cfg, ns); - } - - fn function_dispatch( - &self, - contract_no: usize, - all_cfg: &mut [ControlFlowGraph], - ns: &mut Namespace, - opt: &Options, - ) -> Vec { - dispatch::soroban::function_dispatch(contract_no, all_cfg, ns, opt) - } -} - impl TargetCodegen for SolanaTarget { fn function_dispatch( &self, diff --git a/src/codegen/dispatch/soroban.rs b/src/codegen/targets/soroban/dispatch.rs similarity index 98% rename from src/codegen/dispatch/soroban.rs rename to src/codegen/targets/soroban/dispatch.rs index 05bb648e3..122d2ea50 100644 --- a/src/codegen/dispatch/soroban.rs +++ b/src/codegen/targets/soroban/dispatch.rs @@ -5,7 +5,7 @@ use crate::sema::ast; use crate::{ codegen::{ cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}, - encoding::soroban_encoding::{soroban_decode_arg, soroban_encode_arg}, + targets::soroban::encoding::{soroban_decode_arg, soroban_encode_arg}, vartable::Vartable, Expression, Options, }, diff --git a/src/codegen/encoding/soroban_encoding.rs b/src/codegen/targets/soroban/encoding.rs similarity index 100% rename from src/codegen/encoding/soroban_encoding.rs rename to src/codegen/targets/soroban/encoding.rs diff --git a/src/codegen/events/soroban.rs b/src/codegen/targets/soroban/events.rs similarity index 90% rename from src/codegen/events/soroban.rs rename to src/codegen/targets/soroban/events.rs index 4ea78352a..06ef448bc 100644 --- a/src/codegen/events/soroban.rs +++ b/src/codegen/targets/soroban/events.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::codegen::cfg::{ControlFlowGraph, Instr}; -use crate::codegen::encoding::soroban_encoding::soroban_encode_arg; use crate::codegen::events::EventEmitter; use crate::codegen::expression::expression; +use crate::codegen::targets::soroban::encoding::soroban_encode_arg; use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; use crate::sema::ast::{self, Function, Namespace, Type}; @@ -15,10 +15,10 @@ use solang_parser::pt; /// Each indexed Solidity event field becomes a topic Val in the Soroban /// `contract_event` call. The first non-indexed field becomes the data Val. /// If there are no non-indexed fields, a zero Val is passed as data. -pub(super) struct SorobanEventEmitter<'a> { - pub(super) args: &'a [ast::Expression], - pub(super) ns: &'a Namespace, - pub(super) event_no: usize, +pub(crate) struct SorobanEventEmitter<'a> { + pub(crate) args: &'a [ast::Expression], + pub(crate) ns: &'a Namespace, + pub(crate) event_no: usize, } impl EventEmitter for SorobanEventEmitter<'_> { diff --git a/src/codegen/soroban.rs b/src/codegen/targets/soroban/mod.rs similarity index 93% rename from src/codegen/soroban.rs rename to src/codegen/targets/soroban/mod.rs index 7a60de685..2f501d798 100644 --- a/src/codegen/soroban.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -1,11 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 -use super::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}; -use super::encoding::soroban_encoding::soroban_encode_arg; -use super::error::CodegenError; -use super::expression::{expression, load_storage}; -use super::vartable::Vartable; -use super::Options; +pub(crate) mod dispatch; +pub(crate) mod encoding; +pub(crate) mod events; + +use self::encoding::soroban_encode_arg; +use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}; +use crate::codegen::error::CodegenError; +use crate::codegen::expression::{expression, load_storage}; +use crate::codegen::interface::TargetCodegen; +use crate::codegen::vartable::Vartable; +use crate::codegen::Options; use crate::codegen::{Builtin, Expression, HostFunctions}; use crate::sema::ast; use crate::sema::ast::{Function, Namespace, RetrieveType, Type}; @@ -15,6 +20,34 @@ use solang_parser::helpers::CodeLocation; use solang_parser::{diagnostics::Diagnostic, pt}; use std::collections::BTreeSet; +/// Codegen for the Soroban target. All Soroban-specific lowering lives under this module +/// (`dispatch`, `encoding`, `events`, plus the validation and storage helpers below). +pub(crate) struct SorobanTarget; + +impl TargetCodegen for SorobanTarget { + fn validate_contract(&self, contract_no: usize, ns: &mut Namespace) { + validate_accessor_abi_types(contract_no, ns); + if ns.diagnostics.any_errors() { + return; + } + validate_event_abi_types(contract_no, ns); + } + + fn validate_cfgs(&self, all_cfg: &[ControlFlowGraph], ns: &mut Namespace) { + validate_abi_types(all_cfg, ns); + } + + fn function_dispatch( + &self, + contract_no: usize, + all_cfg: &mut [ControlFlowGraph], + ns: &mut Namespace, + opt: &Options, + ) -> Vec { + dispatch::function_dispatch(contract_no, all_cfg, ns, opt) + } +} + pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) { if ns.target != Target::Soroban { return; @@ -531,7 +564,7 @@ fn soroban_vec_handle_ty(vec_ty: &Type) -> Type { Type::SorobanHandle(Box::new(inner_ty)) } -pub(super) fn soroban_vec_new( +pub(crate) fn soroban_vec_new( loc: &pt::Loc, vec_ty: &Type, cfg: &mut ControlFlowGraph, @@ -625,7 +658,7 @@ fn soroban_vec_pop_back( new_vec_var } -pub(super) fn soroban_storage_push( +pub(crate) fn soroban_storage_push( loc: &pt::Loc, args: &[ast::Expression], cfg: &mut ControlFlowGraph, @@ -656,7 +689,7 @@ pub(super) fn soroban_storage_push( var_expr } -pub(super) fn soroban_storage_pop( +pub(crate) fn soroban_storage_pop( loc: &pt::Loc, args: &[ast::Expression], return_ty: &Type, From f69612fb61968eb1ebc1553c0b1dfe23ea7200c5 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Thu, 18 Jun 2026 13:51:13 +0300 Subject: [PATCH 03/15] codegen: thread target param through the lowering call graph Signed-off-by: Islam-Imad --- src/codegen/cfg.rs | 24 +- src/codegen/constructor.rs | 15 +- src/codegen/dispatch/solana.rs | 8 +- src/codegen/events/mod.rs | 2 + src/codegen/events/polkadot.rs | 26 +- src/codegen/events/solana.rs | 15 +- src/codegen/expression.rs | 985 ++++++++++++++++++++------ src/codegen/mod.rs | 25 +- src/codegen/revert.rs | 12 +- src/codegen/solana_deploy.rs | 17 +- src/codegen/statements/mod.rs | 147 +++- src/codegen/statements/try_catch.rs | 74 +- src/codegen/storage.rs | 21 +- src/codegen/targets/mod.rs | 1 + src/codegen/targets/soroban/events.rs | 13 +- src/codegen/targets/soroban/mod.rs | 8 +- src/codegen/unused_variable.rs | 2 + src/codegen/yul/builtin.rs | 32 +- src/codegen/yul/expression.rs | 43 +- src/codegen/yul/mod.rs | 19 +- src/codegen/yul/statements.rs | 141 +++- src/codegen/yul/tests/expression.rs | 251 ++++++- 22 files changed, 1542 insertions(+), 339 deletions(-) diff --git a/src/codegen/cfg.rs b/src/codegen/cfg.rs index db6b40021..24faa63a1 100644 --- a/src/codegen/cfg.rs +++ b/src/codegen/cfg.rs @@ -8,6 +8,7 @@ use super::{ vartable::{Vars, Vartable}, vector_to_slice, Options, }; +use crate::codegen::interface::TargetCodegen; use crate::codegen::subexpression_elimination::common_sub_expression_elimination; use crate::codegen::{undefined_variable, Expression, LLVMName}; use crate::sema::ast::{ @@ -1483,19 +1484,20 @@ fn is_there_virtual_function( /// Generate the CFG for a function. If function_no is None, generate the implicit default /// constructor -pub fn generate_cfg( +pub(crate) fn generate_cfg( contract_no: usize, function_no: Option, cfg_no: usize, all_cfgs: &mut Vec, ns: &mut Namespace, opt: &Options, + target: &dyn TargetCodegen, ) { if is_there_virtual_function(ns, contract_no, function_no) { return; } - let mut cfg = function_cfg(contract_no, function_no, ns, opt); + let mut cfg = function_cfg(contract_no, function_no, ns, opt, target); let ast_fn = function_no .map(ASTFunction::SolidityFunction) .unwrap_or(ASTFunction::None); @@ -1524,6 +1526,7 @@ pub fn generate_cfg( chain_no, ns, opt, + target, ); optimize_and_check_cfg(&mut cfg, ns, ast_fn, opt); } @@ -1609,6 +1612,7 @@ fn function_cfg( function_no: Option, ns: &mut Namespace, opt: &Options, + target: &dyn TargetCodegen, ) -> ControlFlowGraph { let mut vartab = match function_no { Some(function_no) => { @@ -1727,8 +1731,16 @@ fn function_cfg( .iter() .enumerate() .map(|(i, a)| { - let expr = - expression(a, &mut cfg, contract_no, Some(func), ns, &mut vartab, opt); + let expr = expression( + a, + &mut cfg, + contract_no, + Some(func), + ns, + &mut vartab, + opt, + target, + ); if let Some(id) = &func.symtable.arguments[i] { let ty = expr.ty(); @@ -1806,6 +1818,7 @@ fn function_cfg( None, None, opt, + target, ); if !stmt.reachable() { @@ -1920,6 +1933,7 @@ fn generate_modifier_dispatch( chain_no: usize, ns: &mut Namespace, opt: &Options, + target: &dyn TargetCodegen, ) -> ControlFlowGraph { let (modifier_no, args) = resolve_modifier_call( &ns.functions[func_no].modifiers[chain_no], @@ -1976,6 +1990,7 @@ fn generate_modifier_dispatch( ns, &mut vartab, opt, + target, ); cfg.add( &mut vartab, @@ -2033,6 +2048,7 @@ fn generate_modifier_dispatch( Some(&placeholder), Some(&return_instr), opt, + target, ); } diff --git a/src/codegen/constructor.rs b/src/codegen/constructor.rs index c020c987d..6f4fd5d74 100644 --- a/src/codegen/constructor.rs +++ b/src/codegen/constructor.rs @@ -2,6 +2,7 @@ use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::expression::{default_gas, expression}; +use crate::codegen::interface::TargetCodegen; use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; use crate::sema::{ @@ -30,14 +31,15 @@ pub(super) fn call_constructor( vartab: &mut Vartable, cfg: &mut ControlFlowGraph, opt: &Options, + target: &dyn TargetCodegen, ) { let value = call_args .value .as_ref() - .map(|v| expression(v, cfg, callee_contract_no, func, ns, vartab, opt)); + .map(|v| expression(v, cfg, callee_contract_no, func, ns, vartab, opt, target)); let gas = if let Some(gas) = &call_args.gas { - expression(gas, cfg, callee_contract_no, func, ns, vartab, opt) + expression(gas, cfg, callee_contract_no, func, ns, vartab, opt, target) } else { default_gas(ns) }; @@ -45,7 +47,7 @@ pub(super) fn call_constructor( let salt = call_args .salt .as_ref() - .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt)); + .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt, target)); let address = if ns.target == Target::Solana { if let Some(literal_id) = &ns.contracts[contract_no].program_id { Some(Expression::NumberLiteral { @@ -62,6 +64,7 @@ pub(super) fn call_constructor( ns, vartab, opt, + target, ); Some(address) } @@ -71,14 +74,14 @@ pub(super) fn call_constructor( let seeds = call_args .seeds .as_ref() - .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt)); + .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt, target)); let accounts = call_args .accounts - .map(|expr| expression(expr, cfg, contract_no, func, ns, vartab, opt)); + .map(|expr| expression(expr, cfg, contract_no, func, ns, vartab, opt, target)); let mut constructor_args = constructor_args .iter() - .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt)) + .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt, target)) .collect::>(); let selector = match constructor_no { diff --git a/src/codegen/dispatch/solana.rs b/src/codegen/dispatch/solana.rs index fd3b4f4f1..0b3245061 100644 --- a/src/codegen/dispatch/solana.rs +++ b/src/codegen/dispatch/solana.rs @@ -2,6 +2,7 @@ use crate::codegen::{ cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy, ReturnCode}, + interface::TargetCodegen, solana_deploy::solana_deploy, vartable::Vartable, Builtin, Expression, Options, @@ -22,6 +23,7 @@ pub(crate) fn function_dispatch( all_cfg: &[ControlFlowGraph], ns: &mut Namespace, opt: &Options, + target: &dyn TargetCodegen, ) -> ControlFlowGraph { let mut vartab = Vartable::new(ns.next_id); let mut cfg = ControlFlowGraph::new(SOLANA_DISPATCH_CFG_NAME.into(), ASTFunction::None); @@ -175,6 +177,7 @@ pub(crate) fn function_dispatch( &mut vartab, &mut cfg, opt, + target, ) } else { continue; @@ -401,6 +404,7 @@ fn add_constructor_dispatch_case( vartab: &mut Vartable, cfg: &mut ControlFlowGraph, opt: &Options, + target: &dyn TargetCodegen, ) -> usize { let entry = cfg.new_basic_block(format!("constructor_cfg_{cfg_no}")); cfg.set_basic_block(entry); @@ -434,9 +438,9 @@ fn add_constructor_dispatch_case( if let ASTFunction::SolidityFunction(function_no) = func_cfg.function_no { let func = &ns.functions[function_no]; - solana_deploy(func, &returns, contract_no, vartab, cfg, ns, opt); + solana_deploy(func, &returns, contract_no, vartab, cfg, ns, opt, target); } else if let Some((func, _)) = &ns.contracts[contract_no].default_constructor { - solana_deploy(func, &returns, contract_no, vartab, cfg, ns, opt); + solana_deploy(func, &returns, contract_no, vartab, cfg, ns, opt, target); } else { unreachable!(); } diff --git a/src/codegen/events/mod.rs b/src/codegen/events/mod.rs index 330cd50c3..c35e90ad6 100644 --- a/src/codegen/events/mod.rs +++ b/src/codegen/events/mod.rs @@ -6,6 +6,7 @@ mod solana; use crate::codegen::cfg::ControlFlowGraph; use crate::codegen::events::polkadot::PolkadotEventEmitter; use crate::codegen::events::solana::SolanaEventEmitter; +use crate::codegen::interface::TargetCodegen; use crate::codegen::targets::soroban::events::SorobanEventEmitter; use crate::codegen::vartable::Vartable; use crate::codegen::Options; @@ -30,6 +31,7 @@ pub(crate) trait EventEmitter { cfg: &mut ControlFlowGraph, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ); /// Generates the selector diff --git a/src/codegen/events/polkadot.rs b/src/codegen/events/polkadot.rs index e5618f7b0..077b664ab 100644 --- a/src/codegen/events/polkadot.rs +++ b/src/codegen/events/polkadot.rs @@ -6,6 +6,7 @@ use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::encoding::abi_encode; use crate::codegen::events::EventEmitter; use crate::codegen::expression::expression; +use crate::codegen::interface::TargetCodegen; use crate::codegen::vartable::Vartable; use crate::codegen::{Builtin, Expression, Options}; use crate::sema::ast::{self, Function, Namespace, RetrieveType, Type}; @@ -38,6 +39,7 @@ impl EventEmitter for PolkadotEventEmitter<'_> { cfg: &mut ControlFlowGraph, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) { let loc = pt::Loc::Builtin; let event = &self.ns.events[self.event_no]; @@ -60,7 +62,16 @@ impl EventEmitter for PolkadotEventEmitter<'_> { }; for (ast_exp, field) in self.args.iter().zip(event.fields.iter()) { - let value_exp = expression(ast_exp, cfg, contract_no, Some(func), self.ns, vartab, opt); + let value_exp = expression( + ast_exp, + cfg, + contract_no, + Some(func), + self.ns, + vartab, + opt, + target, + ); let value_var = vartab.temp_anonymous(&value_exp.ty()); let value = Expression::Variable { loc, @@ -146,7 +157,18 @@ impl EventEmitter for PolkadotEventEmitter<'_> { let data = self .args .iter() - .map(|e| expression(e, cfg, contract_no, Some(func), self.ns, vartab, opt)) + .map(|e| { + expression( + e, + cfg, + contract_no, + Some(func), + self.ns, + vartab, + opt, + target, + ) + }) .collect::>(); let encoded_data = if data.is_empty() { Expression::AllocDynamicBytes { diff --git a/src/codegen/events/solana.rs b/src/codegen/events/solana.rs index 8eadfdc72..8218a7258 100644 --- a/src/codegen/events/solana.rs +++ b/src/codegen/events/solana.rs @@ -5,6 +5,7 @@ use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::encoding::abi_encode; use crate::codegen::events::EventEmitter; use crate::codegen::expression::expression; +use crate::codegen::interface::TargetCodegen; use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; use crate::sema::ast; @@ -32,6 +33,7 @@ impl EventEmitter for SolanaEventEmitter<'_> { cfg: &mut ControlFlowGraph, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) { let discriminator = Expression::BytesLiteral { loc: Loc::Codegen, @@ -42,7 +44,18 @@ impl EventEmitter for SolanaEventEmitter<'_> { let mut codegen_args = self .args .iter() - .map(|e| expression(e, cfg, contract_no, Some(func), self.ns, vartab, opt)) + .map(|e| { + expression( + e, + cfg, + contract_no, + Some(func), + self.ns, + vartab, + opt, + target, + ) + }) .collect::>(); let mut to_be_encoded: Vec = vec![discriminator]; diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index ac8665fae..e8eeac4bd 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -17,6 +17,7 @@ use super::{polkadot, Options}; use crate::codegen::array_boundary::handle_array_assign; use crate::codegen::constructor::call_constructor; use crate::codegen::events::new_event_emitter; +use crate::codegen::interface::TargetCodegen; use crate::codegen::unused_variable::should_remove_assignment; use crate::codegen::{Builtin, Expression, HostFunctions}; use crate::sema::ast::ExternalCallAccounts; @@ -46,6 +47,7 @@ pub fn expression( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let evaluated = eval_constants_in_expression(expr, &mut Diagnostics::default()); let expr = evaluated.0.as_ref().unwrap_or(expr); @@ -62,7 +64,7 @@ pub fn expression( } ast::Expression::StorageLoad { loc, ty, expr } => { let storage_type = storage_type(expr, ns); - let storage = expression(expr, cfg, contract_no, func, ns, vartab, opt); + let storage = expression(expr, cfg, contract_no, func, ns, vartab, opt, target); load_storage(loc, ty, storage, cfg, vartab, storage_type, ns) } @@ -84,6 +86,7 @@ pub fn expression( vartab, right, opt, + target, ), ast::Expression::Subtract { loc, @@ -103,6 +106,7 @@ pub fn expression( vartab, right, opt, + target, ), ast::Expression::Multiply { loc, @@ -124,8 +128,26 @@ pub fn expression( loc: *loc, ty: ty.clone(), overflowing: *unchecked, - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), } } } @@ -135,8 +157,8 @@ pub fn expression( left, right, } => { - let l = expression(left, cfg, contract_no, func, ns, vartab, opt); - let r = expression(right, cfg, contract_no, func, ns, vartab, opt); + let l = expression(left, cfg, contract_no, func, ns, vartab, opt, target); + let r = expression(right, cfg, contract_no, func, ns, vartab, opt, target); if ty.is_signed_int(ns) { Expression::SignedDivide { loc: *loc, @@ -159,8 +181,8 @@ pub fn expression( left, right, } => { - let l = expression(left, cfg, contract_no, func, ns, vartab, opt); - let r = expression(right, cfg, contract_no, func, ns, vartab, opt); + let l = expression(left, cfg, contract_no, func, ns, vartab, opt, target); + let r = expression(right, cfg, contract_no, func, ns, vartab, opt, target); if ty.is_signed_int(ns) { Expression::SignedModulo { loc: *loc, @@ -187,8 +209,26 @@ pub fn expression( loc: *loc, ty: ty.clone(), overflowing: *unchecked, - base: Box::new(expression(base, cfg, contract_no, func, ns, vartab, opt)), - exp: Box::new(expression(exp, cfg, contract_no, func, ns, vartab, opt)), + base: Box::new(expression( + base, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + exp: Box::new(expression( + exp, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::BitwiseOr { loc, @@ -198,8 +238,26 @@ pub fn expression( } => Expression::BitwiseOr { loc: *loc, ty: ty.clone(), - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::BitwiseAnd { loc, @@ -209,8 +267,26 @@ pub fn expression( } => Expression::BitwiseAnd { loc: *loc, ty: ty.clone(), - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::BitwiseXor { loc, @@ -220,8 +296,26 @@ pub fn expression( } => Expression::BitwiseXor { loc: *loc, ty: ty.clone(), - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::ShiftLeft { loc, @@ -231,8 +325,26 @@ pub fn expression( } => Expression::ShiftLeft { loc: *loc, ty: ty.clone(), - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::ShiftRight { loc, @@ -243,23 +355,77 @@ pub fn expression( } => Expression::ShiftRight { loc: *loc, ty: ty.clone(), - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), signed: *sign, }, ast::Expression::Equal { loc, left, right } => Expression::Equal { loc: *loc, - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::NotEqual { loc, left, right } => Expression::NotEqual { loc: *loc, - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::More { loc, left, right } => { - let l = expression(left, cfg, contract_no, func, ns, vartab, opt); - let r = expression(right, cfg, contract_no, func, ns, vartab, opt); + let l = expression(left, cfg, contract_no, func, ns, vartab, opt, target); + let r = expression(right, cfg, contract_no, func, ns, vartab, opt, target); Expression::More { loc: *loc, @@ -271,12 +437,30 @@ pub fn expression( ast::Expression::MoreEqual { loc, left, right } => Expression::MoreEqual { loc: *loc, signed: left.ty().is_signed_int(ns), - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::Less { loc, left, right } => { - let l = expression(left, cfg, contract_no, func, ns, vartab, opt); - let r = expression(right, cfg, contract_no, func, ns, vartab, opt); + let l = expression(left, cfg, contract_no, func, ns, vartab, opt, target); + let r = expression(right, cfg, contract_no, func, ns, vartab, opt, target); Expression::Less { loc: *loc, signed: l.ty().is_signed_int(ns), @@ -287,8 +471,26 @@ pub fn expression( ast::Expression::LessEqual { loc, left, right } => Expression::LessEqual { loc: *loc, signed: left.ty().is_signed_int(ns), - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::ConstantVariable { contract_no: Some(var_contract_no), @@ -305,6 +507,7 @@ pub fn expression( ns, vartab, opt, + target, ), ast::Expression::ConstantVariable { contract_no: None, @@ -318,15 +521,34 @@ pub fn expression( ns, vartab, opt, + target, ), ast::Expression::Not { loc, expr } => Expression::Not { loc: *loc, - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::BitwiseNot { loc, ty, expr } => Expression::BitwiseNot { loc: *loc, ty: ty.clone(), - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::Negate { loc, @@ -337,7 +559,16 @@ pub fn expression( loc: *loc, ty: ty.clone(), overflowing: *unchecked, - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::StructLiteral { loc, ty, values, .. @@ -346,7 +577,7 @@ pub fn expression( ty: ty.clone(), values: values .iter() - .map(|(_, e)| expression(e, cfg, contract_no, func, ns, vartab, opt)) + .map(|(_, e)| expression(e, cfg, contract_no, func, ns, vartab, opt, target)) .collect(), }, ast::Expression::ArrayLiteral { @@ -360,7 +591,7 @@ pub fn expression( dimensions: dimensions.clone(), values: values .iter() - .map(|e| expression(e, cfg, contract_no, func, ns, vartab, opt)) + .map(|e| expression(e, cfg, contract_no, func, ns, vartab, opt, target)) .collect(), }, ast::Expression::ConstArrayLiteral { @@ -374,7 +605,7 @@ pub fn expression( dimensions: dimensions.clone(), values: values .iter() - .map(|e| expression(e, cfg, contract_no, func, ns, vartab, opt)) + .map(|e| expression(e, cfg, contract_no, func, ns, vartab, opt, target)) .collect(), }, ast::Expression::Assign { left, right, .. } => { @@ -382,11 +613,11 @@ pub fn expression( if let Some(function) = func { if should_remove_assignment(left, function, opt, ns) { - return expression(right, cfg, contract_no, func, ns, vartab, opt); + return expression(right, cfg, contract_no, func, ns, vartab, opt, target); } } - let mut cfg_right = expression(right, cfg, contract_no, func, ns, vartab, opt); + let mut cfg_right = expression(right, cfg, contract_no, func, ns, vartab, opt, target); // If an assignment where the left hand side is an array, call a helper function that updates the temp variable. if let ast::Expression::Variable { @@ -399,7 +630,17 @@ pub fn expression( cfg_right = handle_array_assign(cfg_right, cfg, vartab, *var_no); } - assign_single(left, cfg_right, cfg, contract_no, func, ns, vartab, opt) + assign_single( + left, + cfg_right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ) } ast::Expression::PreDecrement { loc, @@ -424,6 +665,7 @@ pub fn expression( expr, *unchecked, opt, + target, ), ast::Expression::PostDecrement { loc, @@ -448,6 +690,7 @@ pub fn expression( expr, *unchecked, opt, + target, ), ast::Expression::Constructor { loc, @@ -475,6 +718,7 @@ pub fn expression( vartab, cfg, opt, + target, ); if ns.target.is_polkadot() { polkadot::RetCodeCheckBuilder::default() @@ -516,7 +760,7 @@ pub fn expression( elem_ty, } => { let array_ty = array.ty().deref_into(); - let array = expression(array, cfg, contract_no, func, ns, vartab, opt); + let array = expression(array, cfg, contract_no, func, ns, vartab, opt, target); match array_ty { Type::Bytes(length) => { @@ -529,7 +773,7 @@ pub fn expression( None, ) .unwrap(); - expression(&ast_expr, cfg, contract_no, func, ns, vartab, opt) + expression(&ast_expr, cfg, contract_no, func, ns, vartab, opt, target) } Type::DynamicBytes | Type::String => Expression::StorageArrayLength { loc: *loc, @@ -560,7 +804,7 @@ pub fn expression( None, ) .unwrap(); - expression(&ast_expr, cfg, contract_no, func, ns, vartab, opt) + expression(&ast_expr, cfg, contract_no, func, ns, vartab, opt, target) } _ => unreachable!(), }, @@ -573,9 +817,18 @@ pub fn expression( .. } => { if let ast::Expression::ExternalFunction { address, .. } = &func_expr[0] { - expression(address, cfg, contract_no, func, ns, vartab, opt) + expression(address, cfg, contract_no, func, ns, vartab, opt, target) } else { - let func_expr = expression(&func_expr[0], cfg, contract_no, func, ns, vartab, opt); + let func_expr = expression( + &func_expr[0], + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ); func_expr.external_function_address() } @@ -596,7 +849,16 @@ pub fn expression( } } _ => { - let func_expr = expression(&func_expr[0], cfg, contract_no, func, ns, vartab, opt); + let func_expr = expression( + &func_expr[0], + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ); func_expr.external_function_selector() } @@ -617,7 +879,8 @@ pub fn expression( kind: ast::Builtin::AbiDecode, .. } => { - let mut returns = emit_function_call(expr, contract_no, cfg, func, ns, vartab, opt); + let mut returns = + emit_function_call(expr, contract_no, cfg, func, ns, vartab, opt, target); returns.remove(0) } @@ -627,7 +890,7 @@ pub fn expression( address, function_no, } => { - let address = expression(address, cfg, contract_no, func, ns, vartab, opt); + let address = expression(address, cfg, contract_no, func, ns, vartab, opt, target); let selector = Expression::BytesLiteral { loc: *loc, ty: Type::Uint(32), @@ -662,6 +925,7 @@ pub fn expression( ns, vartab, opt, + target, ), ast::Expression::StructMember { loc, @@ -684,7 +948,8 @@ pub fn expression( // In Soroban, storage struct members are accessed via a key whose representation is a Soroban Vec. // Therefore instead of adding the offset we insert it as a separate argument. - let soroban_key = expression(var, cfg, contract_no, func, ns, vartab, opt); + let soroban_key = + expression(var, cfg, contract_no, func, ns, vartab, opt, target); let offset = Expression::NumberLiteral { loc: *loc, @@ -718,7 +983,16 @@ pub fn expression( loc: *loc, ty: ns.storage_type(), overflowing: true, - left: Box::new(expression(var, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + var, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), right: Box::new(Expression::NumberLiteral { loc: *loc, ty: ns.storage_type(), @@ -752,7 +1026,16 @@ pub fn expression( let member_ptr = Expression::StructMember { loc: *loc, ty: member_ty, - expr: Box::new(expression(var, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + var, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), member: *member, }; @@ -768,32 +1051,86 @@ pub fn expression( } ast::Expression::StringCompare { loc, left, right } => Expression::StringCompare { loc: *loc, - left: string_location(left, cfg, contract_no, func, ns, vartab, opt), - right: string_location(right, cfg, contract_no, func, ns, vartab, opt), + left: string_location(left, cfg, contract_no, func, ns, vartab, opt, target), + right: string_location(right, cfg, contract_no, func, ns, vartab, opt, target), }, - ast::Expression::Or { loc, left, right } => { - expr_or(left, cfg, contract_no, func, ns, vartab, loc, right, opt) - } - ast::Expression::And { loc, left, right } => { - and(left, cfg, contract_no, func, ns, vartab, loc, right, opt) - } - ast::Expression::CheckingTrunc { loc, to, expr } => { - checking_trunc(loc, expr, to, cfg, contract_no, func, ns, vartab, opt) - } + ast::Expression::Or { loc, left, right } => expr_or( + left, + cfg, + contract_no, + func, + ns, + vartab, + loc, + right, + opt, + target, + ), + ast::Expression::And { loc, left, right } => and( + left, + cfg, + contract_no, + func, + ns, + vartab, + loc, + right, + opt, + target, + ), + ast::Expression::CheckingTrunc { loc, to, expr } => checking_trunc( + loc, + expr, + to, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ), ast::Expression::Trunc { loc, to, expr } => Expression::Trunc { loc: *loc, ty: to.clone(), - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::ZeroExt { loc, to, expr } => Expression::ZeroExt { loc: *loc, ty: to.clone(), - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::SignExt { loc, to, expr } => Expression::SignExt { loc: *loc, ty: to.clone(), - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::Cast { loc, to, expr } if matches!(to, Type::Address(_)) => { let mut diagnostics = Diagnostics::default(); @@ -807,7 +1144,16 @@ pub fn expression( Expression::Cast { loc: *loc, ty: to.clone(), - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), } } } @@ -817,13 +1163,13 @@ pub fn expression( // Address and Contract have the same underlying type. CSE will create // a temporary to replace multiple casts from address to Contract, which have no // real purpose. - expression(expr, cfg, contract_no, func, ns, vartab, opt) + expression(expr, cfg, contract_no, func, ns, vartab, opt, target) } ast::Expression::Cast { loc, to, expr } if matches!(to, Type::Array(..)) && matches!(**expr, ast::Expression::ArrayLiteral { .. }) => { - let codegen_expr = expression(expr, cfg, contract_no, func, ns, vartab, opt); + let codegen_expr = expression(expr, cfg, contract_no, func, ns, vartab, opt, target); array_literal_to_memory_array(loc, &codegen_expr, to, cfg, vartab) } ast::Expression::Cast { loc, to, expr } => { @@ -838,12 +1184,21 @@ pub fn expression( } else if matches!(to, Type::String | Type::DynamicBytes) && matches!(expr.ty(), Type::String | Type::DynamicBytes) { - expression(expr, cfg, contract_no, func, ns, vartab, opt) + expression(expr, cfg, contract_no, func, ns, vartab, opt, target) } else { Expression::Cast { loc: *loc, ty: to.clone(), - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), } } } @@ -856,10 +1211,28 @@ pub fn expression( loc: *loc, ty: to.clone(), from: from.clone(), - expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::Load { loc, ty, expr: e } => { - let expr = Box::new(expression(e, cfg, contract_no, func, ns, vartab, opt)); + let expr = Box::new(expression( + e, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )); // Soroban lazy decode path: if memory contains encoded handles, decode on demand. if ns.target == Target::Soroban { @@ -892,7 +1265,7 @@ pub fn expression( kind: ast::Builtin::UserTypeUnwrap, args, .. - } => expression(&args[0], cfg, contract_no, func, ns, vartab, opt), + } => expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target), ast::Expression::Builtin { loc, tys: ty, @@ -901,13 +1274,23 @@ pub fn expression( } => { if args[0].ty().is_contract_storage() { if ns.target == Target::Solana || args[0].ty().is_storage_bytes() { - array_push(loc, args, cfg, contract_no, func, ns, vartab, opt) + array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, target) } else { - storage_slots_array_push(loc, args, cfg, contract_no, func, ns, vartab, opt) + storage_slots_array_push( + loc, + args, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ) } } else { let second_arg = if args.len() > 1 { - expression(&args[1], cfg, contract_no, func, ns, vartab, opt) + expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target) } else { ty[0].default(ns).unwrap() }; @@ -922,6 +1305,7 @@ pub fn expression( second_arg, loc, opt, + target, ) } } @@ -933,7 +1317,18 @@ pub fn expression( } => { if args[0].ty().is_contract_storage() { if ns.target == Target::Solana || args[0].ty().is_storage_bytes() { - array_pop(loc, args, &ty[0], cfg, contract_no, func, ns, vartab, opt) + array_pop( + loc, + args, + &ty[0], + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ) } else { storage_slots_array_pop( loc, @@ -945,20 +1340,21 @@ pub fn expression( ns, vartab, opt, + target, ) } } else { let address_res = vartab.temp_anonymous(&ty[0]); - let array_pos = match expression(&args[0], cfg, contract_no, func, ns, vartab, opt) - { - Expression::Variable { var_no, .. } => { - vartab.set_dirty(var_no); + let array_pos = + match expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target) { + Expression::Variable { var_no, .. } => { + vartab.set_dirty(var_no); - var_no - } - _ => unreachable!(), - }; + var_no + } + _ => unreachable!(), + }; cfg.add( vartab, @@ -982,14 +1378,14 @@ pub fn expression( kind: ast::Builtin::Assert, args, .. - } => expr_assert(cfg, &args[0], contract_no, func, ns, vartab, opt), + } => expr_assert(cfg, &args[0], contract_no, func, ns, vartab, opt, target), ast::Expression::Builtin { kind: ast::Builtin::Print, args, .. } => { if opt.log_prints { - let expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); let to_print = if ns.target.is_polkadot() { add_prefix_and_delimiter_to_print(expr) @@ -1022,54 +1418,64 @@ pub fn expression( kind: ast::Builtin::Require, args, .. - } => require(cfg, args, contract_no, func, ns, vartab, opt, expr.loc()), + } => require( + cfg, + args, + contract_no, + func, + ns, + vartab, + opt, + expr.loc(), + target, + ), ast::Expression::Builtin { kind: ast::Builtin::SelfDestruct, args, .. - } => self_destruct(args, cfg, contract_no, func, ns, vartab, opt), + } => self_destruct(args, cfg, contract_no, func, ns, vartab, opt, target), ast::Expression::Builtin { loc, kind: ast::Builtin::PayableSend, args, .. - } => payable_send(args, cfg, contract_no, func, ns, vartab, loc, opt), + } => payable_send(args, cfg, contract_no, func, ns, vartab, loc, opt, target), ast::Expression::Builtin { loc, kind: ast::Builtin::PayableTransfer, args, .. - } => payable_transfer(args, cfg, contract_no, func, ns, vartab, loc, opt), + } => payable_transfer(args, cfg, contract_no, func, ns, vartab, loc, opt, target), ast::Expression::Builtin { loc, kind: ast::Builtin::AbiEncode, args, .. - } => abi_encode_many(args, cfg, contract_no, func, ns, vartab, loc, opt), + } => abi_encode_many(args, cfg, contract_no, func, ns, vartab, loc, opt, target), ast::Expression::Builtin { loc, kind: ast::Builtin::AbiEncodePacked, args, .. - } => abi_encode_packed(args, cfg, contract_no, func, ns, vartab, loc, opt), + } => abi_encode_packed(args, cfg, contract_no, func, ns, vartab, loc, opt, target), ast::Expression::Builtin { loc, kind: ast::Builtin::AbiEncodeWithSelector, args, .. - } => abi_encode_with_selector(args, cfg, contract_no, func, ns, vartab, loc, opt), + } => abi_encode_with_selector(args, cfg, contract_no, func, ns, vartab, loc, opt, target), ast::Expression::Builtin { loc, kind: ast::Builtin::AbiEncodeWithSignature, args, .. - } => abi_encode_with_signature(args, loc, cfg, contract_no, func, ns, vartab, opt), + } => abi_encode_with_signature(args, loc, cfg, contract_no, func, ns, vartab, opt, target), ast::Expression::Builtin { loc, kind: ast::Builtin::AbiEncodeCall, args, .. - } => abi_encode_call(args, cfg, contract_no, func, ns, vartab, loc, opt), + } => abi_encode_call(args, cfg, contract_no, func, ns, vartab, loc, opt, target), // The Polkadot gas price builtin takes an argument; the others do not ast::Expression::Builtin { loc, @@ -1077,7 +1483,7 @@ pub fn expression( args: expr, .. } if expr.len() == 1 && ns.target == Target::EVM => { - builtin_evm_gasprice(loc, expr, cfg, contract_no, func, ns, vartab, opt) + builtin_evm_gasprice(loc, expr, cfg, contract_no, func, ns, vartab, opt, target) } ast::Expression::Builtin { loc, @@ -1111,16 +1517,29 @@ pub fn expression( tys, *kind, opt, + target, ), ast::Expression::FormatString { loc, format: args } => { - format_string(args, cfg, contract_no, func, ns, vartab, loc, opt) + format_string(args, cfg, contract_no, func, ns, vartab, loc, opt, target) } ast::Expression::AllocDynamicBytes { loc, ty, length: size, init, - } => alloc_dynamic_array(size, cfg, contract_no, func, ns, vartab, loc, ty, init, opt), + } => alloc_dynamic_array( + size, + cfg, + contract_no, + func, + ns, + vartab, + loc, + ty, + init, + opt, + target, + ), ast::Expression::ConditionalOperator { loc, ty, @@ -1139,6 +1558,7 @@ pub fn expression( left, right, opt, + target, ), ast::Expression::BoolLiteral { loc, value } => Expression::BoolLiteral { loc: *loc, @@ -1173,7 +1593,16 @@ pub fn expression( ast::Expression::GetRef { loc, ty, expr: exp } => Expression::GetRef { loc: *loc, ty: ty.clone(), - expr: Box::new(expression(exp, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + exp, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }, ast::Expression::UserDefinedOperator { loc, @@ -1186,7 +1615,7 @@ pub fn expression( let cfg_no = ns.contracts[contract_no].all_functions[function_no]; let args = args .iter() - .map(|a| expression(a, cfg, contract_no, func, ns, vartab, opt)) + .map(|a| expression(a, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); cfg.add( @@ -1251,9 +1680,10 @@ fn memory_array_push( value: Expression, loc: &pt::Loc, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let address_res = vartab.temp_anonymous(ty); - let array_pos = match expression(array, cfg, contract_no, func, ns, vartab, opt) { + let array_pos = match expression(array, cfg, contract_no, func, ns, vartab, opt, target) { Expression::Variable { var_no, .. } => { vartab.set_dirty(var_no); @@ -1291,9 +1721,10 @@ fn post_incdec( expr: &ast::Expression, overflowing: bool, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let res = vartab.temp_anonymous(ty); - let v = expression(var, cfg, contract_no, func, ns, vartab, opt); + let v = expression(var, cfg, contract_no, func, ns, vartab, opt, target); let storage_type = storage_type(var, ns); @@ -1364,7 +1795,7 @@ fn post_incdec( ); } _ => { - let dest = expression(var, cfg, contract_no, func, ns, vartab, opt); + let dest = expression(var, cfg, contract_no, func, ns, vartab, opt, target); let res = vartab.temp_anonymous(ty); cfg.add( vartab, @@ -1433,9 +1864,10 @@ fn pre_incdec( expr: &ast::Expression, overflowing: bool, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let res = vartab.temp_anonymous(ty); - let v = expression(var, cfg, contract_no, func, ns, vartab, opt); + let v = expression(var, cfg, contract_no, func, ns, vartab, opt, target); let storage_type = storage_type(var, ns); let v = match var.ty() { Type::Ref(ty) => Expression::Load { @@ -1501,7 +1933,7 @@ fn pre_incdec( ); } _ => { - let dest = expression(var, cfg, contract_no, func, ns, vartab, opt); + let dest = expression(var, cfg, contract_no, func, ns, vartab, opt, target); match var.ty() { Type::StorageRef(..) => { @@ -1559,8 +1991,9 @@ fn expr_or( loc: &pt::Loc, right: &ast::Expression, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let l = expression(left, cfg, contract_no, func, ns, vartab, opt); + let l = expression(left, cfg, contract_no, func, ns, vartab, opt, target); let pos = vartab.temp( &pt::Identifier { name: "or".to_owned(), @@ -1591,7 +2024,7 @@ fn expr_or( }, ); cfg.set_basic_block(right_side); - let r = expression(right, cfg, contract_no, func, ns, vartab, opt); + let r = expression(right, cfg, contract_no, func, ns, vartab, opt, target); cfg.add( vartab, Instr::Set { @@ -1620,8 +2053,9 @@ fn and( loc: &pt::Loc, right: &ast::Expression, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let l = expression(left, cfg, contract_no, func, ns, vartab, opt); + let l = expression(left, cfg, contract_no, func, ns, vartab, opt, target); let pos = vartab.temp( &pt::Identifier { name: "and".to_owned(), @@ -1652,7 +2086,7 @@ fn and( }, ); cfg.set_basic_block(right_side); - let r = expression(right, cfg, contract_no, func, ns, vartab, opt); + let r = expression(right, cfg, contract_no, func, ns, vartab, opt, target); cfg.add( vartab, Instr::Set { @@ -1679,8 +2113,9 @@ fn self_destruct( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let recipient = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let recipient = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); cfg.add(vartab, Instr::SelfDestruct { recipient }); Expression::Poison } @@ -1694,9 +2129,10 @@ fn payable_send( vartab: &mut Vartable, loc: &pt::Loc, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); - let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt); + let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); let success = vartab.temp( &pt::Identifier { loc: *loc, @@ -1768,9 +2204,10 @@ fn payable_transfer( vartab: &mut Vartable, loc: &pt::Loc, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); - let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt); + let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); if ns.target == Target::EVM { // Ethereum can only transfer via external call cfg.add( @@ -1832,10 +2269,11 @@ fn abi_encode_many( vartab: &mut Vartable, loc: &pt::Loc, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let args = args .iter() - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt)) + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); abi_encode(loc, args, ns, vartab, cfg, false).0 @@ -1850,10 +2288,11 @@ fn abi_encode_packed( vartab: &mut Vartable, loc: &pt::Loc, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let packed = args .iter() - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt)) + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); let (encoded, _) = abi_encode(loc, packed, ns, vartab, cfg, true); @@ -1883,6 +2322,7 @@ fn abi_encode_with_selector( vartab: &mut Vartable, loc: &pt::Loc, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let mut args_iter = args.iter(); let selector = expression( @@ -1893,9 +2333,10 @@ fn abi_encode_with_selector( ns, vartab, opt, + target, ); let args = args_iter - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt)) + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); encode_many_with_selector(loc, selector, args, ns, vartab, cfg) } @@ -1909,6 +2350,7 @@ fn abi_encode_with_signature( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let mut args_iter = args.iter(); let hash_algorithm = if ns.target == Target::Solana { @@ -1923,10 +2365,10 @@ fn abi_encode_with_signature( kind: hash_algorithm, args: vec![args_iter.next().unwrap().clone()], }; - let hash = expression(&hash, cfg, contract_no, func, ns, vartab, opt); + let hash = expression(&hash, cfg, contract_no, func, ns, vartab, opt, target); let selector = hash.cast(&Type::FunctionSelector, ns); let args = args_iter - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt)) + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); encode_many_with_selector(loc, selector, args, ns, vartab, cfg) } @@ -1940,6 +2382,7 @@ fn abi_encode_call( vartab: &mut Vartable, loc: &pt::Loc, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let mut args_iter = args.iter(); let selector = expression( @@ -1955,9 +2398,10 @@ fn abi_encode_call( ns, vartab, opt, + target, ); let args = args_iter - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt)) + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); encode_many_with_selector(loc, selector, args, ns, vartab, cfg) } @@ -1971,6 +2415,7 @@ fn builtin_evm_gasprice( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let ty = Type::Value; let gasprice = Expression::Builtin { @@ -1979,7 +2424,7 @@ fn builtin_evm_gasprice( kind: Builtin::Gasprice, args: vec![], }; - let units = expression(&expr[0], cfg, contract_no, func, ns, vartab, opt); + let units = expression(&expr[0], cfg, contract_no, func, ns, vartab, opt, target); Expression::Multiply { loc: *loc, ty, @@ -2000,6 +2445,7 @@ fn expr_builtin( tys: &[Type], builtin: ast::Builtin, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { match builtin { ast::Builtin::WriteInt8 @@ -2014,8 +2460,8 @@ fn expr_builtin( | ast::Builtin::WriteUint64LE | ast::Builtin::WriteUint128LE | ast::Builtin::WriteUint256LE => { - let buf = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); - let offset = expression(&args[2], cfg, contract_no, func, ns, vartab, opt); + let buf = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); + let offset = expression(&args[2], cfg, contract_no, func, ns, vartab, opt, target); // range check let cond = Expression::LessEqual { @@ -2066,15 +2512,15 @@ fn expr_builtin( cfg.set_basic_block(in_bounds); - let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); cfg.add(vartab, Instr::WriteBuffer { buf, value, offset }); Expression::Undefined { ty: tys[0].clone() } } ast::Builtin::WriteBytes | ast::Builtin::WriteString => { - let buffer = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); - let data = expression(&args[1], cfg, contract_no, func, ns, vartab, opt); - let offset = expression(&args[2], cfg, contract_no, func, ns, vartab, opt); + let buffer = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); + let data = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); + let offset = expression(&args[2], cfg, contract_no, func, ns, vartab, opt, target); let size = Expression::Builtin { loc: *loc, @@ -2153,8 +2599,8 @@ fn expr_builtin( | ast::Builtin::ReadUint64LE | ast::Builtin::ReadUint128LE | ast::Builtin::ReadUint256LE => { - let buf = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); - let offset = expression(&args[1], cfg, contract_no, func, ns, vartab, opt); + let buf = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); + let offset = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); // range check let cond = Expression::LessEqual { @@ -2215,7 +2661,7 @@ fn expr_builtin( ast::Builtin::AddMod | ast::Builtin::MulMod => { let arguments: Vec = args .iter() - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt)) + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect(); let temp = vartab.temp_anonymous(&tys[0]); @@ -2439,7 +2885,7 @@ fn expr_builtin( ty: Type::Address(false), var_no: var_temp, }; - let expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); let expr = if let Type::StorageRef(_, _) = args[0].ty() { let expr_no = vartab.temp_anonymous(&Type::Address(false)); @@ -2518,8 +2964,10 @@ fn expr_builtin( ) .2; - let contract_value = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); - let fn_name_symbol = expression(&args[1], cfg, contract_no, func, ns, vartab, opt); + let contract_value = + expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); + let fn_name_symbol = + expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); let symbol_string = if let Expression::BytesLiteral { loc, ty: _, value } = fn_name_symbol { @@ -2538,7 +2986,7 @@ fn expr_builtin( let mut args_vec = Vec::new(); for arg in args.iter().skip(2) { - let arg = expression(arg, cfg, contract_no, func, ns, vartab, opt); + let arg = expression(arg, cfg, contract_no, func, ns, vartab, opt, target); args_vec.push(arg); } @@ -2880,7 +3328,7 @@ fn expr_builtin( ast::Builtin::ExtendTtl => { let mut arguments: Vec = args .iter() - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt)) + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect(); // var_no is the first argument of the builtin @@ -2917,7 +3365,7 @@ fn expr_builtin( _ => { let arguments: Vec = args .iter() - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt)) + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect(); if !arguments.is_empty() && builtin == ast::Builtin::ArrayLength { @@ -2956,8 +3404,9 @@ fn alloc_dynamic_array( ty: &Type, init: &Option>, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let size = expression(size, cfg, contract_no, func, ns, vartab, opt); + let size = expression(size, cfg, contract_no, func, ns, vartab, opt, target); Expression::AllocDynamicBytes { loc: *loc, ty: ty.clone(), @@ -2978,13 +3427,32 @@ fn add( vartab: &mut Vartable, right: &ast::Expression, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { Expression::Add { loc: *loc, ty: ty.clone(), overflowing, - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), } } @@ -3000,13 +3468,32 @@ fn subtract( vartab: &mut Vartable, right: &ast::Expression, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { Expression::Subtract { loc: *loc, ty: ty.clone(), overflowing, - left: Box::new(expression(left, cfg, contract_no, func, ns, vartab, opt)), - right: Box::new(expression(right, cfg, contract_no, func, ns, vartab, opt)), + left: Box::new(expression( + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + right: Box::new(expression( + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), } } @@ -3020,6 +3507,7 @@ fn checking_trunc( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let bits = match ty { Type::Uint(bits) => *bits as u32, @@ -3043,7 +3531,7 @@ fn checking_trunc( &source_ty, ); - let expr = expression(expr, cfg, contract_no, func, ns, vartab, opt); + let expr = expression(expr, cfg, contract_no, func, ns, vartab, opt, target); cfg.add( vartab, @@ -3109,13 +3597,14 @@ fn format_string( vartab: &mut Vartable, loc: &pt::Loc, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let args = args .iter() .map(|(spec, arg)| { ( *spec, - expression(arg, cfg, contract_no, func, ns, vartab, opt), + expression(arg, cfg, contract_no, func, ns, vartab, opt, target), ) }) .collect(); @@ -3134,8 +3623,9 @@ fn conditional_operator( left: &ast::Expression, right: &ast::Expression, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let cond = expression(cond, cfg, contract_no, func, ns, vartab, opt); + let cond = expression(cond, cfg, contract_no, func, ns, vartab, opt, target); let pos = vartab.temp( &pt::Identifier { @@ -3162,7 +3652,7 @@ fn conditional_operator( cfg.set_basic_block(left_block); - let expr = expression(left, cfg, contract_no, func, ns, vartab, opt); + let expr = expression(left, cfg, contract_no, func, ns, vartab, opt, target); cfg.add( vartab, @@ -3177,7 +3667,7 @@ fn conditional_operator( cfg.set_basic_block(right_block); - let expr = expression(right, cfg, contract_no, func, ns, vartab, opt); + let expr = expression(right, cfg, contract_no, func, ns, vartab, opt, target); cfg.add( vartab, @@ -3232,6 +3722,7 @@ pub fn assign_single( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { match left { ast::Expression::Variable { loc, ty, var_no } => { @@ -3263,7 +3754,7 @@ pub fn assign_single( false }; - let dest = expression(left, cfg, contract_no, func, ns, vartab, opt); + let dest = expression(left, cfg, contract_no, func, ns, vartab, opt, target); let cfg_right = if !left_ty.is_contract_storage() && cfg_right.ty().is_fixed_reference_type(ns) { @@ -3378,6 +3869,7 @@ pub fn emit_function_call( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Vec { match expr { ast::Expression::InternalFunctionCall { function, args, .. } => { @@ -3389,7 +3881,7 @@ pub fn emit_function_call( { let args = args .iter() - .map(|a| expression(a, cfg, caller_contract_no, func, ns, vartab, opt)) + .map(|a| expression(a, cfg, caller_contract_no, func, ns, vartab, opt, target)) .collect(); let function_no = if let Some(signature) = signature { @@ -3459,11 +3951,20 @@ pub fn emit_function_call( vec![Expression::Poison] } } else if let Type::InternalFunction { returns, .. } = function.ty().deref_any() { - let cfg_expr = expression(function, cfg, caller_contract_no, func, ns, vartab, opt); + let cfg_expr = expression( + function, + cfg, + caller_contract_no, + func, + ns, + vartab, + opt, + target, + ); let args = args .iter() - .map(|a| expression(a, cfg, caller_contract_no, func, ns, vartab, opt)) + .map(|a| expression(a, cfg, caller_contract_no, func, ns, vartab, opt, target)) .collect(); if !returns.is_empty() { @@ -3522,15 +4023,33 @@ pub fn emit_function_call( call_args, ty, } => { - let args = expression(args, cfg, caller_contract_no, func, ns, vartab, opt); - let address = expression(address, cfg, caller_contract_no, func, ns, vartab, opt); + let args = expression(args, cfg, caller_contract_no, func, ns, vartab, opt, target); + let address = expression( + address, + cfg, + caller_contract_no, + func, + ns, + vartab, + opt, + target, + ); let gas = if let Some(gas) = &call_args.gas { - expression(gas, cfg, caller_contract_no, func, ns, vartab, opt) + expression(gas, cfg, caller_contract_no, func, ns, vartab, opt, target) } else { default_gas(ns) }; let value = if let Some(value) = &call_args.value { - expression(value, cfg, caller_contract_no, func, ns, vartab, opt) + expression( + value, + cfg, + caller_contract_no, + func, + ns, + vartab, + opt, + target, + ) } else { Expression::NumberLiteral { loc: pt::Loc::Codegen, @@ -3538,20 +4057,18 @@ pub fn emit_function_call( value: BigInt::zero(), } }; - let accounts = call_args - .accounts - .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); - let seeds = call_args - .seeds - .as_ref() - .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); + let accounts = call_args.accounts.map(|expr| { + expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) + }); + let seeds = call_args.seeds.as_ref().map(|expr| { + expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) + }); let success = vartab.temp_name("success", &Type::Uint(32)); - let flags = call_args - .flags - .as_ref() - .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); + let flags = call_args.flags.as_ref().map(|expr| { + expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) + }); cfg.add( vartab, @@ -3617,24 +4134,41 @@ pub fn emit_function_call( let mut tys: Vec = args.iter().map(|a| a.ty()).collect(); let mut args: Vec = args .iter() - .map(|a| expression(a, cfg, caller_contract_no, func, ns, vartab, opt)) + .map(|a| expression(a, cfg, caller_contract_no, func, ns, vartab, opt, target)) .collect(); - let address = expression(address, cfg, caller_contract_no, func, ns, vartab, opt); + let address = expression( + address, + cfg, + caller_contract_no, + func, + ns, + vartab, + opt, + target, + ); let gas = if let Some(gas) = &call_args.gas { - expression(gas, cfg, caller_contract_no, func, ns, vartab, opt) + expression(gas, cfg, caller_contract_no, func, ns, vartab, opt, target) } else { default_gas(ns) }; - let accounts = call_args - .accounts - .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); - let seeds = call_args - .seeds - .as_ref() - .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); + let accounts = call_args.accounts.map(|expr| { + expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) + }); + let seeds = call_args.seeds.as_ref().map(|expr| { + expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) + }); let value = if let Some(value) = &call_args.value { - expression(value, cfg, caller_contract_no, func, ns, vartab, opt) + expression( + value, + cfg, + caller_contract_no, + func, + ns, + vartab, + opt, + target, + ) } else { Expression::NumberLiteral { loc: pt::Loc::Codegen, @@ -3658,10 +4192,9 @@ pub fn emit_function_call( let (payload, _) = abi_encode(loc, args, ns, vartab, cfg, false); - let flags = call_args - .flags - .as_ref() - .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); + let flags = call_args.flags.as_ref().map(|expr| { + expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) + }); let success = ns .target @@ -3720,16 +4253,34 @@ pub fn emit_function_call( let mut tys: Vec = args.iter().map(|a| a.ty()).collect(); let mut args = args .iter() - .map(|a| expression(a, cfg, caller_contract_no, func, ns, vartab, opt)) + .map(|a| expression(a, cfg, caller_contract_no, func, ns, vartab, opt, target)) .collect::>(); - let function = expression(function, cfg, caller_contract_no, func, ns, vartab, opt); + let function = expression( + function, + cfg, + caller_contract_no, + func, + ns, + vartab, + opt, + target, + ); let gas = if let Some(gas) = &call_args.gas { - expression(gas, cfg, caller_contract_no, func, ns, vartab, opt) + expression(gas, cfg, caller_contract_no, func, ns, vartab, opt, target) } else { default_gas(ns) }; let value = if let Some(value) = &call_args.value { - expression(value, cfg, caller_contract_no, func, ns, vartab, opt) + expression( + value, + cfg, + caller_contract_no, + func, + ns, + vartab, + opt, + target, + ) } else { Expression::NumberLiteral { loc: pt::Loc::Codegen, @@ -3746,10 +4297,9 @@ pub fn emit_function_call( let (payload, _) = abi_encode(loc, args, ns, vartab, cfg, false); - let flags = call_args - .flags - .as_ref() - .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); + let flags = call_args.flags.as_ref().map(|expr| { + expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) + }); let success = ns .target .is_polkadot() @@ -3803,7 +4353,16 @@ pub fn emit_function_call( kind: ast::Builtin::AbiDecode, args, } => { - let data = expression(&args[0], cfg, caller_contract_no, func, ns, vartab, opt); + let data = expression( + &args[0], + cfg, + caller_contract_no, + func, + ns, + vartab, + opt, + target, + ); if tys.len() == 1 && tys[0] == Type::Void { vec![Expression::Poison] @@ -3841,20 +4400,39 @@ fn array_subscript( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { if array_ty.is_storage_bytes() { return Expression::Subscript { loc: *loc, ty: elem_ty.clone(), array_ty: array_ty.clone(), - expr: Box::new(expression(array, cfg, contract_no, func, ns, vartab, opt)), - index: Box::new(expression(index, cfg, contract_no, func, ns, vartab, opt)), + expr: Box::new(expression( + array, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), + index: Box::new(expression( + index, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + )), }; } if array_ty.is_mapping() { - let array = expression(array, cfg, contract_no, func, ns, vartab, opt); - let index = expression(index, cfg, contract_no, func, ns, vartab, opt); + let array = expression(array, cfg, contract_no, func, ns, vartab, opt, target); + let index = expression(index, cfg, contract_no, func, ns, vartab, opt, target); return match ns.target { Target::Solana | Target::Soroban | Target::EVM => Expression::Subscript { @@ -3872,9 +4450,9 @@ fn array_subscript( }; } - let mut array = expression(array, cfg, contract_no, func, ns, vartab, opt); + let mut array = expression(array, cfg, contract_no, func, ns, vartab, opt, target); let index_ty = index.ty(); - let index = expression(index, cfg, contract_no, func, ns, vartab, opt); + let index = expression(index, cfg, contract_no, func, ns, vartab, opt, target); let index_loc = index.loc(); let index_width = index_ty.bits(ns); @@ -3890,7 +4468,7 @@ fn array_subscript( None, ) .unwrap(); - expression(&ast_bigint, cfg, contract_no, func, ns, vartab, opt) + expression(&ast_bigint, cfg, contract_no, func, ns, vartab, opt, target) } Type::Array(..) => match array_ty.array_length() { None => { @@ -3959,7 +4537,16 @@ fn array_subscript( None, ) .unwrap(); - expression(&ast_big_int, cfg, contract_no, func, ns, vartab, opt) + expression( + &ast_big_int, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ) } }, Type::DynamicBytes | Type::Slice(_) => Expression::Builtin { @@ -4273,6 +4860,7 @@ fn string_location( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> StringLocation { match loc { StringLocation::RunTime(s) => StringLocation::RunTime(Box::new(expression( @@ -4283,6 +4871,7 @@ fn string_location( ns, vartab, opt, + target, ))), StringLocation::CompileTime(vec) => StringLocation::CompileTime(vec.clone()), } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index bca688485..6b40473ee 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -329,16 +329,17 @@ fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options, target: &dyn &mut all_cfg, ns, opt, + target, ) } // generate the cfg for yul functions for yul_func_no in ns.contracts[contract_no].yul_functions.clone() { - generate_yul_function_cfg(contract_no, yul_func_no, &mut all_cfg, ns, opt); + generate_yul_function_cfg(contract_no, yul_func_no, &mut all_cfg, ns, opt, target); } // Generate cfg for storage initializers - let cfg = storage_initializer(contract_no, ns, opt); + let cfg = storage_initializer(contract_no, ns, opt, target); let pos = all_cfg.len(); all_cfg.push(cfg); ns.contracts[contract_no].initializer = Some(pos); @@ -349,7 +350,7 @@ fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options, target: &dyn let cfg_no = all_cfg.len(); all_cfg.push(ControlFlowGraph::placeholder()); - cfg::generate_cfg(contract_no, None, cfg_no, &mut all_cfg, ns, opt); + cfg::generate_cfg(contract_no, None, cfg_no, &mut all_cfg, ns, opt, target); ns.contracts[contract_no].default_constructor = Some((func, cfg_no)); } @@ -369,7 +370,12 @@ fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options, target: &dyn } /// This function will set all contract storage initializers and should be called from the constructor -fn storage_initializer(contract_no: usize, ns: &mut Namespace, opt: &Options) -> ControlFlowGraph { +fn storage_initializer( + contract_no: usize, + ns: &mut Namespace, + opt: &Options, + target: &dyn TargetCodegen, +) -> ControlFlowGraph { // note the single `:` to prevent a name clash with user-declared functions let mut cfg = ControlFlowGraph::new(STORAGE_INITIALIZER.to_string(), ASTFunction::None); let mut vartab = Vartable::new(ns.next_id); @@ -387,7 +393,16 @@ fn storage_initializer(contract_no: usize, ns: &mut Namespace, opt: &Options) -> }; let mut value = if let Some(init) = &var.initializer { - expression(init, &mut cfg, contract_no, None, ns, &mut vartab, opt) + expression( + init, + &mut cfg, + contract_no, + None, + ns, + &mut vartab, + opt, + target, + ) } else if soroban_init_with_vec { targets::soroban::soroban_vec_new(&var.loc, &var.ty, &mut cfg, &mut vartab) } else { diff --git a/src/codegen/revert.rs b/src/codegen/revert.rs index 9ff265268..c304ec47d 100644 --- a/src/codegen/revert.rs +++ b/src/codegen/revert.rs @@ -11,6 +11,7 @@ use super::{ vartable::Vartable, }; +use crate::codegen::interface::TargetCodegen; use crate::codegen::Expression; use crate::sema::{ ast, @@ -205,10 +206,11 @@ pub(super) fn expr_assert( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let true_ = cfg.new_basic_block("noassert".to_owned()); let false_ = cfg.new_basic_block("doassert".to_owned()); - let cond = expression(args, cfg, contract_no, func, ns, vartab, opt); + let cond = expression(args, cfg, contract_no, func, ns, vartab, opt, target); cfg.add( vartab, Instr::BranchCond { @@ -241,10 +243,11 @@ pub(super) fn require( vartab: &mut Vartable, opt: &Options, loc: Loc, + target: &dyn TargetCodegen, ) -> Expression { let true_ = cfg.new_basic_block("noassert".to_owned()); let false_ = cfg.new_basic_block("doassert".to_owned()); - let cond = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let cond = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); cfg.add( vartab, Instr::BranchCond { @@ -256,7 +259,7 @@ pub(super) fn require( cfg.set_basic_block(false_); let expr = args .get(1) - .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt)); + .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt, target)); // On Solana and Polkadot, print the reason if opt.log_runtime_errors { @@ -338,10 +341,11 @@ pub(super) fn revert( vartab: &mut Vartable, opt: &Options, loc: &Loc, + target: &dyn TargetCodegen, ) { let exprs = args .iter() - .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt)) + .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); if opt.log_runtime_errors { match (error_no, exprs.first()) { diff --git a/src/codegen/solana_deploy.rs b/src/codegen/solana_deploy.rs index 2772bf523..ada4bbefb 100644 --- a/src/codegen/solana_deploy.rs +++ b/src/codegen/solana_deploy.rs @@ -4,6 +4,7 @@ use super::{ cfg::ReturnCode, expression, Builtin, ControlFlowGraph, Expression, Instr, Options, Type, Vartable, }; +use crate::codegen::interface::TargetCodegen; use crate::codegen::revert::string_to_expr; use crate::codegen::solana_accounts::account_management::{ account_meta_literal, retrieve_key_from_account_info, @@ -39,6 +40,7 @@ pub(super) fn solana_deploy( cfg: &mut ControlFlowGraph, ns: &Namespace, opt: &Options, + target: &dyn TargetCodegen, ) { let contract = &ns.contracts[contract_no]; @@ -306,7 +308,7 @@ pub(super) fn solana_deploy( // Calculate minimum balance for rent-exempt let (space, lamports) = if let Some((_, space_expr)) = &func.annotations.space { - let expr = expression(space_expr, cfg, contract_no, None, ns, vartab, opt); + let expr = expression(space_expr, cfg, contract_no, None, ns, vartab, opt, target); // If the space is not a literal or a constant expression, // we must verify if we are allocating enough space during runtime. if eval_const_number(space_expr, ns, &mut Diagnostics::default()).is_err() { @@ -533,7 +535,7 @@ pub(super) fn solana_deploy( .annotations .seeds .iter() - .map(|seed| expression(&seed.1, cfg, contract_no, None, ns, vartab, opt)) + .map(|seed| expression(&seed.1, cfg, contract_no, None, ns, vartab, opt, target)) .collect::>(); if let Some((_, bump)) = &func.annotations.bump { @@ -548,7 +550,16 @@ pub(super) fn solana_deploy( } .into(), }; - seeds.push(expression(&expr, cfg, contract_no, None, ns, vartab, opt)); + seeds.push(expression( + &expr, + cfg, + contract_no, + None, + ns, + vartab, + opt, + target, + )); } let seeds = if !seeds.is_empty() { diff --git a/src/codegen/statements/mod.rs b/src/codegen/statements/mod.rs index cd3a67ada..7ad4665c9 100644 --- a/src/codegen/statements/mod.rs +++ b/src/codegen/statements/mod.rs @@ -12,6 +12,7 @@ use super::{ yul::inline_assembly_cfg, Builtin, Expression, Options, }; +use crate::codegen::interface::TargetCodegen; use crate::sema::ast::{ self, ArrayLength, DestructureField, Function, Namespace, RetrieveType, SolanaAccount, Statement, Type, Type::Uint, @@ -36,6 +37,7 @@ pub(crate) fn statement( placeholder: Option<&Instr>, return_override: Option<&Instr>, opt: &Options, + target: &dyn TargetCodegen, ) { match stmt { Statement::Block { statements, .. } => { @@ -51,6 +53,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ); if !stmt.reachable() { @@ -67,13 +70,15 @@ pub(crate) fn statement( ns, vartab, opt, + target, }; //If we remove the assignment, we must keep expressions that have side effects init.recurse(&mut params, process_side_effects_expressions); return; } - let mut expression = expression(init, cfg, contract_no, Some(func), ns, vartab, opt); + let mut expression = + expression(init, cfg, contract_no, Some(func), ns, vartab, opt, target); // Let's check if the declaration is a declaration of a dynamic array if let Expression::AllocDynamicBytes { @@ -165,7 +170,7 @@ pub(crate) fn statement( } else { match expr { None => cfg.add(vartab, Instr::Return { value: Vec::new() }), - Some(expr) => returns(expr, cfg, contract_no, func, ns, vartab, opt), + Some(expr) => returns(expr, cfg, contract_no, func, ns, vartab, opt, target), } } } @@ -180,6 +185,7 @@ pub(crate) fn statement( ns, vartab, opt, + target, }; right.recurse(&mut params, process_side_effects_expressions); @@ -196,6 +202,7 @@ pub(crate) fn statement( ns, vartab, opt, + target, }; for arg in args { arg.recurse(&mut params, process_side_effects_expressions); @@ -211,10 +218,10 @@ pub(crate) fn statement( _ => (), } - let _ = expression(expr, cfg, contract_no, Some(func), ns, vartab, opt); + let _ = expression(expr, cfg, contract_no, Some(func), ns, vartab, opt, target); } Statement::Delete(_, ty, expr) => { - let var_expr = expression(expr, cfg, contract_no, Some(func), ns, vartab, opt); + let var_expr = expression(expr, cfg, contract_no, Some(func), ns, vartab, opt, target); cfg.add( vartab, @@ -253,6 +260,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ); } Statement::If(_, _, cond, then_stmt, else_stmt) => if_then_else( @@ -268,6 +276,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ), Statement::DoWhile(_, _, body_stmt, cond_expr) => { let body = cfg.new_basic_block("body".to_string()); @@ -295,6 +304,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ); body_reachable = stmt.reachable(); @@ -306,7 +316,16 @@ pub(crate) fn statement( cfg.set_basic_block(cond); - let cond_expr = expression(cond_expr, cfg, contract_no, Some(func), ns, vartab, opt); + let cond_expr = expression( + cond_expr, + cfg, + contract_no, + Some(func), + ns, + vartab, + opt, + target, + ); cfg.add( vartab, @@ -333,7 +352,16 @@ pub(crate) fn statement( cfg.set_basic_block(cond); - let cond_expr = expression(cond_expr, cfg, contract_no, Some(func), ns, vartab, opt); + let cond_expr = expression( + cond_expr, + cfg, + contract_no, + Some(func), + ns, + vartab, + opt, + target, + ); cfg.add( vartab, @@ -363,6 +391,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ); body_reachable = stmt.reachable(); @@ -402,6 +431,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ); } @@ -434,6 +464,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ); body_reachable = stmt.reachable(); @@ -449,7 +480,7 @@ pub(crate) fn statement( cfg.set_basic_block(next_block); if let Some(next) = next { - expression(next, cfg, contract_no, Some(func), ns, vartab, opt); + expression(next, cfg, contract_no, Some(func), ns, vartab, opt, target); body_reachable = next.ty() != Type::Unreachable; } @@ -491,6 +522,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ); } @@ -498,7 +530,16 @@ pub(crate) fn statement( cfg.set_basic_block(cond_block); - let cond_expr = expression(cond_expr, cfg, contract_no, Some(func), ns, vartab, opt); + let cond_expr = expression( + cond_expr, + cfg, + contract_no, + Some(func), + ns, + vartab, + opt, + target, + ); cfg.add( vartab, @@ -530,6 +571,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ); body_reachable = stmt.reachable(); @@ -546,7 +588,7 @@ pub(crate) fn statement( let mut next_reachable = true; if let Some(next) = next { - expression(next, cfg, contract_no, Some(func), ns, vartab, opt); + expression(next, cfg, contract_no, Some(func), ns, vartab, opt, target); next_reachable = next.ty() != Type::Unreachable; } @@ -562,9 +604,17 @@ pub(crate) fn statement( cfg.set_phis(end_block, set.clone()); cfg.set_phis(cond_block, set); } - Statement::Destructure(_, fields, expr) => { - destructure(fields, expr, cfg, contract_no, func, ns, vartab, opt) - } + Statement::Destructure(_, fields, expr) => destructure( + fields, + expr, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ), Statement::TryCatch(_, _, try_stmt) => self::try_catch::try_catch( try_stmt, func, @@ -576,6 +626,7 @@ pub(crate) fn statement( placeholder, return_override, opt, + target, ), Statement::Emit { loc, @@ -584,7 +635,7 @@ pub(crate) fn statement( .. } => { let emitter = new_event_emitter(loc, *event_no, args, ns); - emitter.emit(contract_no, func, cfg, vartab, opt); + emitter.emit(contract_no, func, cfg, vartab, opt, target); } Statement::Revert { loc, @@ -601,6 +652,7 @@ pub(crate) fn statement( vartab, opt, loc, + target, ); } Statement::Underscore(_) => { @@ -617,7 +669,7 @@ pub(crate) fn statement( } Statement::Assembly(inline_assembly, ..) => { - inline_assembly_cfg(inline_assembly, contract_no, ns, cfg, vartab, opt); + inline_assembly_cfg(inline_assembly, contract_no, ns, cfg, vartab, opt, target); } } } @@ -635,8 +687,9 @@ fn if_then( placeholder: Option<&Instr>, return_override: Option<&Instr>, opt: &Options, + target: &dyn TargetCodegen, ) { - let cond = expression(cond, cfg, contract_no, Some(func), ns, vartab, opt); + let cond = expression(cond, cfg, contract_no, Some(func), ns, vartab, opt, target); let then = cfg.new_basic_block("then".to_string()); let endif = cfg.new_basic_block("endif".to_string()); @@ -668,6 +721,7 @@ fn if_then( placeholder, return_override, opt, + target, ); reachable = stmt.reachable(); @@ -696,8 +750,9 @@ fn if_then_else( placeholder: Option<&Instr>, return_override: Option<&Instr>, opt: &Options, + target: &dyn TargetCodegen, ) { - let cond = expression(cond, cfg, contract_no, Some(func), ns, vartab, opt); + let cond = expression(cond, cfg, contract_no, Some(func), ns, vartab, opt, target); let then = cfg.new_basic_block("then".to_string()); let else_ = cfg.new_basic_block("else".to_string()); @@ -731,6 +786,7 @@ fn if_then_else( placeholder, return_override, opt, + target, ); then_reachable = stmt.reachable(); @@ -757,6 +813,7 @@ fn if_then_else( placeholder, return_override, opt, + target, ); else_reachable = stmt.reachable(); @@ -779,6 +836,7 @@ fn returns( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) { // Can only be another function call without returns let uncast_values = match expr { @@ -790,7 +848,7 @@ fn returns( false_option: right, .. } => { - let cond = expression(cond, cfg, contract_no, Some(func), ns, vartab, opt); + let cond = expression(cond, cfg, contract_no, Some(func), ns, vartab, opt, target); let left_block = cfg.new_basic_block("left".to_string()); let right_block = cfg.new_basic_block("right".to_string()); @@ -807,10 +865,10 @@ fn returns( vartab.new_dirty_tracker(); cfg.set_basic_block(left_block); - returns(left, cfg, contract_no, func, ns, vartab, opt); + returns(left, cfg, contract_no, func, ns, vartab, opt, target); cfg.set_basic_block(right_block); - returns(right, cfg, contract_no, func, ns, vartab, opt); + returns(right, cfg, contract_no, func, ns, vartab, opt, target); return; } @@ -822,12 +880,12 @@ fn returns( | ast::Expression::InternalFunctionCall { .. } | ast::Expression::ExternalFunctionCall { .. } | ast::Expression::ExternalFunctionCallRaw { .. } => { - emit_function_call(expr, contract_no, cfg, Some(func), ns, vartab, opt) + emit_function_call(expr, contract_no, cfg, Some(func), ns, vartab, opt, target) } ast::Expression::List { list, .. } => list .iter() - .map(|e| expression(e, cfg, contract_no, Some(func), ns, vartab, opt)) + .map(|e| expression(e, cfg, contract_no, Some(func), ns, vartab, opt, target)) .collect::>(), // Can be any other expression @@ -840,6 +898,7 @@ fn returns( ns, vartab, opt, + target, )] } }; @@ -863,6 +922,7 @@ fn destructure( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) { if let ast::Expression::ConditionalOperator { cond, @@ -871,7 +931,7 @@ fn destructure( .. } = expr { - let cond = expression(cond, cfg, contract_no, Some(func), ns, vartab, opt); + let cond = expression(cond, cfg, contract_no, Some(func), ns, vartab, opt, target); let left_block = cfg.new_basic_block("left".to_string()); let right_block = cfg.new_basic_block("right".to_string()); @@ -890,13 +950,33 @@ fn destructure( cfg.set_basic_block(left_block); - destructure(fields, left, cfg, contract_no, func, ns, vartab, opt); + destructure( + fields, + left, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ); cfg.add(vartab, Instr::Branch { block: done_block }); cfg.set_basic_block(right_block); - destructure(fields, right, cfg, contract_no, func, ns, vartab, opt); + destructure( + fields, + right, + cfg, + contract_no, + func, + ns, + vartab, + opt, + target, + ); cfg.add(vartab, Instr::Branch { block: done_block }); @@ -913,7 +993,7 @@ fn destructure( for expr in list { let loc = expr.loc(); - let expr = expression(expr, cfg, contract_no, Some(func), ns, vartab, opt); + let expr = expression(expr, cfg, contract_no, Some(func), ns, vartab, opt, target); let ty = expr.ty(); let res = vartab.temp_anonymous(&ty); @@ -931,7 +1011,7 @@ fn destructure( } _ => { // must be function call, either internal or external - emit_function_call(expr, contract_no, cfg, Some(func), ns, vartab, opt) + emit_function_call(expr, contract_no, cfg, Some(func), ns, vartab, opt, target) } }; @@ -965,7 +1045,17 @@ fn destructure( continue; } - assign_single(left, expr, cfg, contract_no, Some(func), ns, vartab, opt); + assign_single( + left, + expr, + cfg, + contract_no, + Some(func), + ns, + vartab, + opt, + target, + ); } } } @@ -1204,6 +1294,7 @@ pub fn process_side_effects_expressions( ctx.ns, ctx.vartab, ctx.opt, + ctx.target, ); false } @@ -1230,7 +1321,7 @@ pub fn process_side_effects_expressions( | ast::Builtin::WriteUint256LE | ast::Builtin::WriteAddress, .. } => { - let _ = expression(exp, ctx.cfg, ctx.contract_no, ctx.func, ctx.ns, ctx.vartab, ctx.opt); + let _ = expression(exp, ctx.cfg, ctx.contract_no, ctx.func, ctx.ns, ctx.vartab, ctx.opt, ctx.target); false }, diff --git a/src/codegen/statements/try_catch.rs b/src/codegen/statements/try_catch.rs index 316b91e24..5d093a545 100644 --- a/src/codegen/statements/try_catch.rs +++ b/src/codegen/statements/try_catch.rs @@ -6,6 +6,7 @@ use crate::codegen::{ constructor::call_constructor, encoding::{abi_decode, abi_encode}, expression::{default_gas, expression}, + interface::TargetCodegen, polkadot, revert::{ERROR_SELECTOR, PANIC_SELECTOR}, vartable::Vartable, @@ -31,6 +32,7 @@ pub(super) fn try_catch( placeholder: Option<&Instr>, return_override: Option<&Instr>, opt: &Options, + target: &dyn TargetCodegen, ) { if !ns.target.is_polkadot() { unimplemented!() @@ -50,6 +52,7 @@ pub(super) fn try_catch( opt, ok_block, catch_block, + target, ); vartab.new_dirty_tracker(); @@ -67,6 +70,7 @@ pub(super) fn try_catch( opt, ok_block, finally_block, + target, ); insert_catch_clauses( @@ -83,6 +87,7 @@ pub(super) fn try_catch( catch_block, finally_block, error_ret_data_var, + target, ); // Remove the variables only in scope inside the catch clauses block from the phi set for the finally block @@ -116,8 +121,18 @@ fn insert_try_expression( opt: &Options, ok_block: usize, catch_block: usize, + target: &dyn TargetCodegen, ) -> usize { - let (cases, return_types) = exec_try(try_stmt, func, cfg, callee_contract_no, ns, vartab, opt); + let (cases, return_types) = exec_try( + try_stmt, + func, + cfg, + callee_contract_no, + ns, + vartab, + opt, + target, + ); let error_ret_data_var = vartab.temp_name("error_ret_data", &Type::DynamicBytes); @@ -194,6 +209,7 @@ fn exec_try( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> (polkadot::RetCodeCheck, Vec) { let success = vartab.temp( &pt::Identifier { @@ -216,7 +232,16 @@ fn exec_try( } = function.ty() { let value = if let Some(value) = &call_args.value { - expression(value, cfg, callee_contract_no, Some(func), ns, vartab, opt) + expression( + value, + cfg, + callee_contract_no, + Some(func), + ns, + vartab, + opt, + target, + ) } else { Expression::NumberLiteral { loc: Codegen, @@ -225,7 +250,16 @@ fn exec_try( } }; let gas = if let Some(gas) = &call_args.gas { - expression(gas, cfg, callee_contract_no, Some(func), ns, vartab, opt) + expression( + gas, + cfg, + callee_contract_no, + Some(func), + ns, + vartab, + opt, + target, + ) } else { default_gas(ns) }; @@ -237,11 +271,23 @@ fn exec_try( ns, vartab, opt, + target, ); let mut args = args .iter() - .map(|a| expression(a, cfg, callee_contract_no, Some(func), ns, vartab, opt)) + .map(|a| { + expression( + a, + cfg, + callee_contract_no, + Some(func), + ns, + vartab, + opt, + target, + ) + }) .collect::>(); let selector = function.external_function_selector(); @@ -252,7 +298,16 @@ fn exec_try( let (payload, _) = abi_encode(loc, args, ns, vartab, cfg, false); let flags = call_args.flags.as_ref().map(|expr| { - expression(expr, cfg, callee_contract_no, Some(func), ns, vartab, opt) + expression( + expr, + cfg, + callee_contract_no, + Some(func), + ns, + vartab, + opt, + target, + ) }); cfg.add( @@ -309,6 +364,7 @@ fn exec_try( vartab, cfg, opt, + target, ); let cases = polkadot::RetCodeCheckBuilder::default() @@ -335,6 +391,7 @@ fn insert_success_code_block( opt: &Options, ok_block: usize, finally_block: usize, + target: &dyn TargetCodegen, ) { cfg.set_basic_block(ok_block); @@ -352,6 +409,7 @@ fn insert_success_code_block( placeholder, return_override, opt, + target, ); finally_reachable = stmt.reachable(); @@ -384,6 +442,7 @@ fn insert_catch_clauses( catch_block: usize, finally_block: usize, error_ret_data_var: usize, + target: &dyn TargetCodegen, ) { cfg.set_basic_block(catch_block); @@ -409,6 +468,7 @@ fn insert_catch_clauses( opt, finally_block, buffer, + target, ); return; } @@ -472,6 +532,7 @@ fn insert_catch_clauses( placeholder, return_override, opt, + target, ); reachable = stmt.reachable(); @@ -538,6 +599,7 @@ fn insert_catch_clauses( opt, finally_block, buffer, + target, ); } } @@ -556,6 +618,7 @@ fn insert_catchall_clause_code_block( opt: &Options, finally_block: usize, error_data_buf: Expression, + target: &dyn TargetCodegen, ) { if let Some(res) = try_stmt.catch_all.as_ref().unwrap().param_pos { let instruction = Instr::Set { @@ -580,6 +643,7 @@ fn insert_catchall_clause_code_block( placeholder, return_override, opt, + target, ); reachable = stmt.reachable(); diff --git a/src/codegen/storage.rs b/src/codegen/storage.rs index 722a89843..ae1544258 100644 --- a/src/codegen/storage.rs +++ b/src/codegen/storage.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 +use crate::codegen::interface::TargetCodegen; use crate::codegen::targets::soroban::encoding::soroban_encode_arg; use crate::codegen::Expression; use crate::sema::ast; @@ -94,6 +95,7 @@ pub fn storage_slots_array_push( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { let inner_ty = if let Type::StorageRef(_, inner) = args[0].ty() { if let Type::Array(elem_ty, _) = inner.deref_any() { @@ -106,14 +108,14 @@ pub fn storage_slots_array_push( }; if ns.target == Target::Soroban && !inner_ty.is_reference_type(ns) { - return soroban_storage_push(loc, args, cfg, contract_no, func, ns, vartab, opt); + return soroban_storage_push(loc, args, cfg, contract_no, func, ns, vartab, opt, target); } // set array+length to val_expr let slot_ty = ns.storage_type(); let length_pos = vartab.temp_anonymous(&slot_ty); - let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); // TODO(Soroban): Storage type here is None, since arrays are not yet supported in Soroban let expr = load_storage(loc, &slot_ty, var_expr.clone(), cfg, vartab, None, ns); @@ -175,7 +177,7 @@ pub fn storage_slots_array_push( ); if args.len() == 2 { - let mut value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt); + let mut value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); if ns.target == Target::Soroban { value = soroban_encode_arg(value, cfg, vartab, ns); @@ -249,6 +251,7 @@ pub fn storage_slots_array_pop( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { if ns.target == Target::Soroban { return soroban_storage_pop( @@ -261,6 +264,7 @@ pub fn storage_slots_array_pop( ns, vartab, opt, + target, ); } @@ -270,7 +274,7 @@ pub fn storage_slots_array_pop( let length_pos = vartab.temp_anonymous(&slot_ty); let ty = args[0].ty(); - let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); // TODO(Soroban): Storage type here is None, since arrays are not yet supported in Soroban let expr = load_storage(loc, &length_ty, var_expr.clone(), cfg, vartab, None, ns); @@ -475,12 +479,13 @@ pub fn array_push( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { if ns.target == Target::Soroban { - return soroban_storage_push(loc, args, cfg, contract_no, func, ns, vartab, opt); + return soroban_storage_push(loc, args, cfg, contract_no, func, ns, vartab, opt, target); } - let storage = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let storage = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); let mut ty = args[0].ty().storage_array_elem(); @@ -493,6 +498,7 @@ pub fn array_push( ns, vartab, opt, + target, )) } else { ty.deref_any().default(ns) @@ -532,8 +538,9 @@ pub fn array_pop( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let storage = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let storage = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); let ty = args[0].ty().storage_array_elem().deref_into(); diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index 114c68c95..c207eb5b6 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -37,6 +37,7 @@ impl TargetCodegen for SolanaTarget { all_cfg, ns, opt, + self, )] } diff --git a/src/codegen/targets/soroban/events.rs b/src/codegen/targets/soroban/events.rs index 06ef448bc..be8887999 100644 --- a/src/codegen/targets/soroban/events.rs +++ b/src/codegen/targets/soroban/events.rs @@ -3,6 +3,7 @@ use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::events::EventEmitter; use crate::codegen::expression::expression; +use crate::codegen::interface::TargetCodegen; use crate::codegen::targets::soroban::encoding::soroban_encode_arg; use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; @@ -34,13 +35,23 @@ impl EventEmitter for SorobanEventEmitter<'_> { cfg: &mut ControlFlowGraph, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) { let event = &self.ns.events[self.event_no]; let mut topics: Vec = Vec::new(); let mut data_args: Vec = Vec::new(); for (ast_exp, field) in self.args.iter().zip(event.fields.iter()) { - let value = expression(ast_exp, cfg, contract_no, Some(func), self.ns, vartab, opt); + let value = expression( + ast_exp, + cfg, + contract_no, + Some(func), + self.ns, + vartab, + opt, + target, + ); let encoded = soroban_encode_arg(value, cfg, vartab, self.ns); if field.indexed { topics.push(encoded); diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index 2f501d798..68b425615 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -667,10 +667,11 @@ pub(crate) fn soroban_storage_push( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { // Storage wrapper: evaluate storage key/value and load vec object from storage. - let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); - let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt); + let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); let vec_ty = args[0].ty(); let old_vec_obj = load_storage(loc, &vec_ty, var_expr.clone(), cfg, vartab, None, ns); @@ -699,9 +700,10 @@ pub(crate) fn soroban_storage_pop( ns: &Namespace, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { // Storage wrapper: evaluate storage key and load vec object from storage. - let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt); + let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); let vec_ty = args[0].ty(); let old_vec_obj = load_storage(loc, &vec_ty, var_expr.clone(), cfg, vartab, None, ns); diff --git a/src/codegen/unused_variable.rs b/src/codegen/unused_variable.rs index 7e4fc9588..150e35c6c 100644 --- a/src/codegen/unused_variable.rs +++ b/src/codegen/unused_variable.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 use super::Options; +use crate::codegen::interface::TargetCodegen; use crate::codegen::{cfg::ControlFlowGraph, vartable::Vartable, OptimizationLevel}; use crate::sema::ast::RetrieveType; use crate::sema::ast::{Builtin, Expression, Function, Namespace}; @@ -15,6 +16,7 @@ pub struct SideEffectsCheckParameters<'a> { pub ns: &'a Namespace, pub vartab: &'a mut Vartable, pub opt: &'a Options, + pub target: &'a dyn TargetCodegen, } /// Check if we should remove an assignment. The expression in the argument is the left-hand side diff --git a/src/codegen/yul/builtin.rs b/src/codegen/yul/builtin.rs index 2f3137846..52df2bb84 100644 --- a/src/codegen/yul/builtin.rs +++ b/src/codegen/yul/builtin.rs @@ -3,6 +3,7 @@ use crate::{ codegen::{ cfg::{ControlFlowGraph, Instr}, + interface::TargetCodegen, revert::{assert_failure, log_runtime_error, PanicCode, SolidityError}, vartable::Vartable, yul::expression::expression, @@ -50,15 +51,16 @@ pub(crate) fn process_builtin( vartab: &mut Vartable, cfg: &mut ControlFlowGraph, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { match builtin_ty { YulBuiltInFunction::Not => { - let exp = expression(&args[0], contract_no, ns, vartab, cfg, opt); + let exp = expression(&args[0], contract_no, ns, vartab, cfg, opt, target); Expression::BitwiseNot { loc: *loc, ty: exp.ty(), expr: Box::new(exp) } } YulBuiltInFunction::IsZero => { - let left = expression(&args[0], contract_no, ns, vartab, cfg, opt); + let left = expression(&args[0], contract_no, ns, vartab, cfg, opt, target); let right = Expression::NumberLiteral { loc: pt::Loc::Codegen, ty: left.ty(), value: BigInt::from(0) }; Expression::Equal { loc: *loc, left: Box::new(left), right: Box::new(right) } @@ -85,11 +87,11 @@ pub(crate) fn process_builtin( | YulBuiltInFunction::Exp | YulBuiltInFunction::AddMod | YulBuiltInFunction::MulMod => { - process_arithmetic(loc, builtin_ty, args, contract_no, ns, vartab, cfg, opt) + process_arithmetic(loc, builtin_ty, args, contract_no, ns, vartab, cfg, opt, target) } YulBuiltInFunction::Byte => { - byte_builtin(loc, args, contract_no, ns, cfg, vartab, opt) + byte_builtin(loc, args, contract_no, ns, cfg, vartab, opt, target) } YulBuiltInFunction::SignExtend @@ -164,7 +166,7 @@ pub(crate) fn process_builtin( } YulBuiltInFunction::Balance => { - let addr = expression(&args[0], contract_no, ns, vartab, cfg, opt).cast(&Type::Address(false), ns); + let addr = expression(&args[0], contract_no, ns, vartab, cfg, opt, target).cast(&Type::Address(false), ns); Expression::Builtin { loc: *loc, tys: vec![Type::Value], kind: Builtin::Balance, args: vec![addr] } } @@ -189,7 +191,7 @@ pub(crate) fn process_builtin( } YulBuiltInFunction::SelfDestruct => { - let recipient = expression(&args[0], contract_no, ns, vartab, cfg, opt).cast(&Type::Address(true), ns); + let recipient = expression(&args[0], contract_no, ns, vartab, cfg, opt, target).cast(&Type::Address(true), ns); cfg.add(vartab, Instr::SelfDestruct { recipient }); Expression::Poison } @@ -207,12 +209,12 @@ pub(crate) fn process_builtin( } YulBuiltInFunction::ExtCodeSize => { - let address = expression(&args[0], contract_no, ns, vartab, cfg, opt).cast(&Type::Address(false), ns); + let address = expression(&args[0], contract_no, ns, vartab, cfg, opt, target).cast(&Type::Address(false), ns); Expression::Builtin { loc: *loc, tys: vec![Type::Uint(32)], kind: Builtin::ExtCodeSize, args: vec![address] } } YulBuiltInFunction::BlockHash => { - let arg = expression(&args[0], contract_no, ns, vartab, cfg, opt).cast(&Type::Uint(64), ns); + let arg = expression(&args[0], contract_no, ns, vartab, cfg, opt, target).cast(&Type::Uint(64), ns); Expression::Builtin { loc: *loc, tys: vec![Type::Uint(256)], kind: Builtin::BlockHash, args: vec![arg] } } @@ -248,9 +250,10 @@ fn process_arithmetic( vartab: &mut Vartable, cfg: &mut ControlFlowGraph, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let left = expression(&args[0], contract_no, ns, vartab, cfg, opt); - let right = expression(&args[1], contract_no, ns, vartab, cfg, opt); + let left = expression(&args[0], contract_no, ns, vartab, cfg, opt, target); + let right = expression(&args[1], contract_no, ns, vartab, cfg, opt, target); let left = cast_to_number(left, ns); let right = cast_to_number(right, ns); @@ -392,7 +395,7 @@ fn process_arithmetic( }, YulBuiltInFunction::AddMod | YulBuiltInFunction::MulMod => { - let modulo_operand = expression(&args[2], contract_no, ns, vartab, cfg, opt); + let modulo_operand = expression(&args[2], contract_no, ns, vartab, cfg, opt, target); let (_, equalized_modulo) = equalize_types(left.clone(), modulo_operand.clone(), ns); let builtin = if builtin_ty == YulBuiltInFunction::AddMod { Builtin::AddMod @@ -553,8 +556,10 @@ fn byte_builtin( cfg: &mut ControlFlowGraph, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { - let offset = expression(&args[0], contract_no, ns, vartab, cfg, opt).cast(&Type::Uint(256), ns); + let offset = + expression(&args[0], contract_no, ns, vartab, cfg, opt, target).cast(&Type::Uint(256), ns); let cond = Expression::MoreEqual { loc: *loc, signed: false, @@ -625,7 +630,8 @@ fn byte_builtin( loc: *loc, ty: Type::Uint(256), left: Box::new( - expression(&args[1], contract_no, ns, vartab, cfg, opt).cast(&Type::Uint(256), ns), + expression(&args[1], contract_no, ns, vartab, cfg, opt, target) + .cast(&Type::Uint(256), ns), ), right: Box::new(op_eight_times), signed: false, diff --git a/src/codegen/yul/expression.rs b/src/codegen/yul/expression.rs index 43f556f8e..f42ae9a1b 100644 --- a/src/codegen/yul/expression.rs +++ b/src/codegen/yul/expression.rs @@ -2,6 +2,7 @@ use crate::codegen; use crate::codegen::cfg::{ControlFlowGraph, Instr, InternalCallTy}; +use crate::codegen::interface::TargetCodegen; use crate::codegen::vartable::Vartable; use crate::codegen::yul::builtin::process_builtin; use crate::codegen::{Builtin, Expression, Options}; @@ -20,6 +21,7 @@ pub(crate) fn expression( vartab: &mut Vartable, cfg: &mut ControlFlowGraph, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { match expr { ast::YulExpression::BoolLiteral(loc, value, ty) => { @@ -70,6 +72,7 @@ pub(crate) fn expression( ns, vartab, opt, + target, ) } ast::YulExpression::ConstantVariable(_, _, None, var_no) => codegen::expression( @@ -80,6 +83,7 @@ pub(crate) fn expression( ns, vartab, opt, + target, ), ast::YulExpression::StorageVariable(..) | ast::YulExpression::SolidityLocalVariable(_, _, Some(StorageLocation::Storage(_)), ..) => { @@ -91,18 +95,34 @@ pub(crate) fn expression( var_no: *var_no, }, ast::YulExpression::SuffixAccess(loc, expr, suffix) => { - process_suffix_access(loc, expr, suffix, contract_no, vartab, cfg, ns, opt) + process_suffix_access(loc, expr, suffix, contract_no, vartab, cfg, ns, opt, target) } ast::YulExpression::FunctionCall(_, function_no, args, _) => { - let mut returns = - process_function_call(*function_no, args, contract_no, vartab, cfg, ns, opt); + let mut returns = process_function_call( + *function_no, + args, + contract_no, + vartab, + cfg, + ns, + opt, + target, + ); assert_eq!(returns.len(), 1); returns.remove(0) } - ast::YulExpression::BuiltInCall(loc, builtin_ty, args) => { - process_builtin(loc, *builtin_ty, args, contract_no, ns, vartab, cfg, opt) - } + ast::YulExpression::BuiltInCall(loc, builtin_ty, args) => process_builtin( + loc, + *builtin_ty, + args, + contract_no, + ns, + vartab, + cfg, + opt, + target, + ), } } @@ -116,6 +136,7 @@ fn process_suffix_access( cfg: &mut ControlFlowGraph, ns: &Namespace, opt: &Options, + target: &dyn TargetCodegen, ) -> Expression { match suffix { YulSuffix::Slot => match expr { @@ -193,7 +214,7 @@ fn process_suffix_access( loc: *loc, tys: vec![Type::Uint(32)], kind: Builtin::ArrayLength, - args: vec![expression(expr, contract_no, ns, vartab, cfg, opt)], + args: vec![expression(expr, contract_no, ns, vartab, cfg, opt, target)], }; } } @@ -203,7 +224,7 @@ fn process_suffix_access( if let ast::YulExpression::SolidityLocalVariable(_, Type::ExternalFunction { .. }, ..) = expr { - let func_expr = expression(expr, contract_no, ns, vartab, cfg, opt); + let func_expr = expression(expr, contract_no, ns, vartab, cfg, opt, target); return func_expr.external_function_address(); } } @@ -212,7 +233,7 @@ fn process_suffix_access( if let ast::YulExpression::SolidityLocalVariable(_, Type::ExternalFunction { .. }, ..) = expr { - let func_expr = expression(expr, contract_no, ns, vartab, cfg, opt); + let func_expr = expression(expr, contract_no, ns, vartab, cfg, opt, target); return func_expr.external_function_selector(); } } @@ -230,11 +251,13 @@ pub(crate) fn process_function_call( cfg: &mut ControlFlowGraph, ns: &Namespace, opt: &Options, + target: &dyn TargetCodegen, ) -> Vec { let mut codegen_args: Vec = Vec::with_capacity(args.len()); for (param_no, item) in ns.yul_functions[function_no].params.iter().enumerate() { codegen_args.push( - expression(&args[param_no], contract_no, ns, vartab, cfg, opt).cast(&item.ty, ns), + expression(&args[param_no], contract_no, ns, vartab, cfg, opt, target) + .cast(&item.ty, ns), ); } diff --git a/src/codegen/yul/mod.rs b/src/codegen/yul/mod.rs index dc4dacf04..bda0b1bc0 100644 --- a/src/codegen/yul/mod.rs +++ b/src/codegen/yul/mod.rs @@ -4,6 +4,7 @@ use crate::codegen::cfg::{ optimize_and_check_cfg, populate_arguments, populate_named_returns, ASTFunction, ControlFlowGraph, Instr, }; +use crate::codegen::interface::TargetCodegen; use crate::codegen::statements::LoopScopes; use crate::codegen::vartable::Vartable; use crate::codegen::yul::statements::statement; @@ -26,10 +27,21 @@ pub fn inline_assembly_cfg( cfg: &mut ControlFlowGraph, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) { let mut loops = LoopScopes::new(); for stmt in &inline_assembly.body { - statement(stmt, contract_no, &mut loops, ns, cfg, vartab, &None, opt); + statement( + stmt, + contract_no, + &mut loops, + ns, + cfg, + vartab, + &None, + opt, + target, + ); } } @@ -40,8 +52,9 @@ pub(crate) fn generate_yul_function_cfg( all_cfgs: &mut [ControlFlowGraph], ns: &mut Namespace, opt: &Options, + target: &dyn TargetCodegen, ) { - let mut cfg = yul_function_cfg(contract_no, function_no, ns, opt); + let mut cfg = yul_function_cfg(contract_no, function_no, ns, opt, target); optimize_and_check_cfg(&mut cfg, ns, ASTFunction::YulFunction(function_no), opt); all_cfgs[ns.yul_functions[function_no].cfg_no] = cfg; @@ -53,6 +66,7 @@ fn yul_function_cfg( function_no: usize, ns: &mut Namespace, opt: &Options, + target: &dyn TargetCodegen, ) -> ControlFlowGraph { let mut vartab = Vartable::from_symbol_table(&ns.yul_functions[function_no].symtable, ns.next_id); @@ -105,6 +119,7 @@ fn yul_function_cfg( &mut vartab, &Some(returns.clone()), opt, + target, ); } diff --git a/src/codegen/yul/statements.rs b/src/codegen/yul/statements.rs index b384b6b5e..37b662b4d 100644 --- a/src/codegen/yul/statements.rs +++ b/src/codegen/yul/statements.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::codegen::cfg::{ControlFlowGraph, Instr}; +use crate::codegen::interface::TargetCodegen; use crate::codegen::statements::LoopScopes; use crate::codegen::vartable::Vartable; use crate::codegen::yul::builtin::process_builtin; @@ -24,6 +25,7 @@ pub(crate) fn statement( vartab: &mut Vartable, early_return: &Option, opt: &Options, + target: &dyn TargetCodegen, ) { if !yul_statement.is_reachable() { return; @@ -31,28 +33,59 @@ pub(crate) fn statement( match yul_statement { YulStatement::FunctionCall(_, _, func_no, args) => { - let returns = process_function_call(*func_no, args, contract_no, vartab, cfg, ns, opt); + let returns = + process_function_call(*func_no, args, contract_no, vartab, cfg, ns, opt, target); assert_eq!(returns.len(), 1); assert_eq!(returns[0], Expression::Poison); } YulStatement::BuiltInCall(loc, _, builtin_ty, args) => { - let expr = process_builtin(loc, *builtin_ty, args, contract_no, ns, vartab, cfg, opt); + let expr = process_builtin( + loc, + *builtin_ty, + args, + contract_no, + ns, + vartab, + cfg, + opt, + target, + ); assert_eq!(expr, Expression::Poison); } YulStatement::Block(block) => { for item in &block.statements { - statement(item, contract_no, loops, ns, cfg, vartab, early_return, opt); + statement( + item, + contract_no, + loops, + ns, + cfg, + vartab, + early_return, + opt, + target, + ); } } YulStatement::VariableDeclaration(loc, _, vars, init) => { - process_variable_declaration(loc, vars, init, contract_no, ns, cfg, vartab, opt); + process_variable_declaration( + loc, + vars, + init, + contract_no, + ns, + cfg, + vartab, + opt, + target, + ); } YulStatement::Assignment(loc, _, lhs, rhs) => { - process_assignment(loc, lhs, rhs, contract_no, ns, cfg, vartab, opt) + process_assignment(loc, lhs, rhs, contract_no, ns, cfg, vartab, opt, target) } YulStatement::IfBlock(_, _, condition, block) => process_if_block( @@ -65,6 +98,7 @@ pub(crate) fn statement( vartab, early_return, opt, + target, ), YulStatement::Switch { @@ -83,6 +117,7 @@ pub(crate) fn statement( cfg, early_return, opt, + target, ), YulStatement::For { @@ -105,6 +140,7 @@ pub(crate) fn statement( vartab, early_return, opt, + target, ), YulStatement::Leave(..) => { @@ -145,12 +181,13 @@ fn process_variable_declaration( cfg: &mut ControlFlowGraph, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) { let initializer = if let Some(expr) = init { if let ast::YulExpression::FunctionCall(_, func_no, args, _) = expr { - process_function_call(*func_no, args, contract_no, vartab, cfg, ns, opt) + process_function_call(*func_no, args, contract_no, vartab, cfg, ns, opt, target) } else { - vec![expression(expr, contract_no, ns, vartab, cfg, opt)] + vec![expression(expr, contract_no, ns, vartab, cfg, opt, target)] } } else { let mut inits: Vec = Vec::with_capacity(vars.len()); @@ -183,11 +220,12 @@ fn process_assignment( cfg: &mut ControlFlowGraph, vartab: &mut Vartable, opt: &Options, + target: &dyn TargetCodegen, ) { if lhs.len() > 1 { // builtins with multiple returns are not implemented (yet) let returns = if let ast::YulExpression::FunctionCall(_, func_no, args, _) = rhs { - process_function_call(*func_no, args, contract_no, vartab, cfg, ns, opt) + process_function_call(*func_no, args, contract_no, vartab, cfg, ns, opt, target) } else { unreachable!("only function call return multiple values"); }; @@ -198,7 +236,7 @@ fn process_assignment( return; } - let codegen_rhs = expression(rhs, contract_no, ns, vartab, cfg, opt); + let codegen_rhs = expression(rhs, contract_no, ns, vartab, cfg, opt, target); cfg_single_assigment(loc, &lhs[0], codegen_rhs, ns, cfg, vartab); } @@ -355,8 +393,9 @@ fn process_if_block( vartab: &mut Vartable, early_return: &Option, opt: &Options, + target: &dyn TargetCodegen, ) { - let cond = expression(cond, contract_no, ns, vartab, cfg, opt); + let cond = expression(cond, contract_no, ns, vartab, cfg, opt, target); let bool_cond = if cond.ty() == Type::Bool { cond @@ -388,7 +427,17 @@ fn process_if_block( vartab.new_dirty_tracker(); for stmt in &block.statements { - statement(stmt, contract_no, loops, ns, cfg, vartab, early_return, opt); + statement( + stmt, + contract_no, + loops, + ns, + cfg, + vartab, + early_return, + opt, + target, + ); } if block.is_next_reachable() { @@ -414,9 +463,20 @@ fn process_for_block( vartab: &mut Vartable, early_return: &Option, opt: &Options, + target: &dyn TargetCodegen, ) { for stmt in &init_block.statements { - statement(stmt, contract_no, loops, ns, cfg, vartab, early_return, opt); + statement( + stmt, + contract_no, + loops, + ns, + cfg, + vartab, + early_return, + opt, + target, + ); } if !init_block.is_next_reachable() { @@ -431,7 +491,7 @@ fn process_for_block( cfg.add(vartab, Instr::Branch { block: cond_block }); cfg.set_basic_block(cond_block); - let cond_expr = expression(condition, contract_no, ns, vartab, cfg, opt); + let cond_expr = expression(condition, contract_no, ns, vartab, cfg, opt, target); let cond_expr = if cond_expr.ty() == Type::Bool { cond_expr @@ -461,7 +521,17 @@ fn process_for_block( vartab.new_dirty_tracker(); for stmt in &execution_block.statements { - statement(stmt, contract_no, loops, ns, cfg, vartab, early_return, opt); + statement( + stmt, + contract_no, + loops, + ns, + cfg, + vartab, + early_return, + opt, + target, + ); } if execution_block.is_next_reachable() { @@ -473,7 +543,17 @@ fn process_for_block( cfg.set_basic_block(next_block); for stmt in &post_block.statements { - statement(stmt, contract_no, loops, ns, cfg, vartab, early_return, opt); + statement( + stmt, + contract_no, + loops, + ns, + cfg, + vartab, + early_return, + opt, + target, + ); } if post_block.is_next_reachable() { @@ -499,8 +579,9 @@ fn switch( cfg: &mut ControlFlowGraph, early_return: &Option, opt: &Options, + target: &dyn TargetCodegen, ) { - let cond = expression(condition, contract_no, ns, vartab, cfg, opt); + let cond = expression(condition, contract_no, ns, vartab, cfg, opt, target); let end_switch = cfg.new_basic_block("end_switch".to_string()); let current_block = cfg.current_block(); @@ -508,12 +589,22 @@ fn switch( vartab.new_dirty_tracker(); let mut cases_cfg: Vec<(Expression, usize)> = Vec::with_capacity(cases.len()); for (item_no, item) in cases.iter().enumerate() { - let case_cond = - expression(&item.condition, contract_no, ns, vartab, cfg, opt).cast(&cond.ty(), ns); + let case_cond = expression(&item.condition, contract_no, ns, vartab, cfg, opt, target) + .cast(&cond.ty(), ns); let case_block = cfg.new_basic_block(format!("case_{item_no}")); cfg.set_basic_block(case_block); for stmt in &item.block.statements { - statement(stmt, contract_no, loops, ns, cfg, vartab, early_return, opt); + statement( + stmt, + contract_no, + loops, + ns, + cfg, + vartab, + early_return, + opt, + target, + ); } if item.block.is_next_reachable() { cfg.add(vartab, Instr::Branch { block: end_switch }); @@ -525,7 +616,17 @@ fn switch( let new_block = cfg.new_basic_block("default".to_string()); cfg.set_basic_block(new_block); for stmt in &default_block.statements { - statement(stmt, contract_no, loops, ns, cfg, vartab, early_return, opt); + statement( + stmt, + contract_no, + loops, + ns, + cfg, + vartab, + early_return, + opt, + target, + ); } if default_block.is_next_reachable() { cfg.add(vartab, Instr::Branch { block: end_switch }); diff --git a/src/codegen/yul/tests/expression.rs b/src/codegen/yul/tests/expression.rs index 2aa1d5280..5a65df855 100644 --- a/src/codegen/yul/tests/expression.rs +++ b/src/codegen/yul/tests/expression.rs @@ -3,6 +3,7 @@ #![cfg(test)] use crate::codegen::cfg::ControlFlowGraph; +use crate::codegen::targets::make_target; use crate::codegen::vartable::Vartable; use crate::codegen::yul::expression::expression; use crate::codegen::{Builtin, Expression, Options}; @@ -24,11 +25,27 @@ fn bool_literal() { let opt = Options::default(); let expr = ast::YulExpression::BoolLiteral(loc, true, Type::Bool); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!(res, Expression::BoolLiteral { loc, value: true }); let expr = ast::YulExpression::BoolLiteral(loc, true, Type::Uint(32)); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -39,7 +56,15 @@ fn bool_literal() { ); let expr = ast::YulExpression::BoolLiteral(loc, false, Type::Uint(32)); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -59,7 +84,15 @@ fn number_literal() { let opt = Options::default(); let expr = ast::YulExpression::NumberLiteral(loc, BigInt::from(32), Type::Uint(256)); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -79,7 +112,15 @@ fn string_literal() { let opt = Options::default(); let expr = ast::YulExpression::StringLiteral(loc, vec![0, 3, 255, 127], Type::Uint(128)); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -99,7 +140,15 @@ fn yul_local_variable() { let opt = Options::default(); let expr = ast::YulExpression::YulLocalVariable(loc, Type::Int(16), 5); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::Variable { @@ -165,7 +214,15 @@ fn contract_constant_variable() { ns.contracts.push(contract); let expr = ast::YulExpression::ConstantVariable(loc, Type::Uint(64), Some(0), 0); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -203,7 +260,15 @@ fn global_constant_variable() { }; ns.constants.push(var); let expr = ast::YulExpression::ConstantVariable(loc, Type::Uint(64), None, 0); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -224,7 +289,15 @@ fn storage_variable() { let opt = Options::default(); let expr = ast::YulExpression::StorageVariable(loc, Type::Bool, 0, 0); - let _ = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let _ = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); } #[test] @@ -242,7 +315,15 @@ fn storage_variable_reference() { Some(StorageLocation::Storage(loc)), 0, ); - let _ = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let _ = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); } #[test] @@ -254,7 +335,15 @@ fn solidity_local_variable() { let opt = Options::default(); let expr = ast::YulExpression::SolidityLocalVariable(loc, Type::Uint(32), None, 7); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::Variable { @@ -317,7 +406,15 @@ fn slot_suffix() { )), YulSuffix::Slot, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -337,7 +434,15 @@ fn slot_suffix() { )), YulSuffix::Slot, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::Variable { @@ -368,7 +473,15 @@ fn slot_suffix_panic() { YulSuffix::Slot, ); - let _res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let _res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); } #[test] @@ -390,7 +503,15 @@ fn offset_suffix() { YulSuffix::Offset, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -410,7 +531,15 @@ fn offset_suffix() { )), YulSuffix::Offset, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::NumberLiteral { @@ -430,7 +559,15 @@ fn offset_suffix() { )), YulSuffix::Offset, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::Cast { @@ -468,7 +605,15 @@ fn offset_suffix_panic_calldata() { YulSuffix::Offset, ); - let _res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let _res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); } #[test] @@ -486,7 +631,15 @@ fn offset_suffix_panic_other() { YulSuffix::Offset, ); - let _res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let _res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); } #[test] @@ -515,7 +668,15 @@ fn length_suffix() { YulSuffix::Length, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::Builtin { @@ -561,7 +722,15 @@ fn length_suffix_panic() { YulSuffix::Length, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, Expression::Builtin { @@ -606,7 +775,15 @@ fn selector_suffix() { )), YulSuffix::Selector, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, @@ -650,7 +827,15 @@ fn selector_suffix_panic() { )), YulSuffix::Selector, ); - let _res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let _res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); } #[test] @@ -675,7 +860,15 @@ fn address_suffix() { )), YulSuffix::Address, ); - let res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); assert_eq!( res, @@ -719,5 +912,13 @@ fn address_suffix_panic() { )), YulSuffix::Address, ); - let _res = expression(&expr, 0, &ns, &mut vartab, &mut cfg, &opt); + let _res = expression( + &expr, + 0, + &ns, + &mut vartab, + &mut cfg, + &opt, + &*make_target(&ns), + ); } From c3ae3d2403b8d2820a2715049e9c04deae2fff8e Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Thu, 18 Jun 2026 18:14:53 +0300 Subject: [PATCH 04/15] codegen: route load/storage hooks and target properties through TargetCodegen Convert five scattered target branches in the shared lowering to TargetCodegen methods, each with a behaviour-preserving default: - selector_hash_algorithm() (was Solana/else Sha256/Keccak256 ternary) - storage_array_length_is_inline() (was Solana||Soroban inline-length branch) - lower_load() (was Soroban handle lazy-decode in Load arm) - prepare_storage_value() (was Soroban encode before SetStorage/Store) - default_storage_value() (was Soroban soroban_init_with_vec block) Soroban overrides the three behavioural hooks; Solana overrides the two property methods; Polkadot inherits the defaults. Golden CFG (zero // CHECK: edits) and soroban suites pass; lower_load verified IR-identical via before/after emit-cfg diff on handle-loading contracts. Signed-off-by: Islam-Imad --- src/codegen/expression.rs | 118 +++++++++++------------------ src/codegen/interface.rs | 55 +++++++++++++- src/codegen/mod.rs | 24 ++---- src/codegen/targets/mod.rs | 9 +++ src/codegen/targets/soroban/mod.rs | 65 +++++++++++++++- 5 files changed, 178 insertions(+), 93 deletions(-) diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index e8eeac4bd..50c7330a5 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -783,7 +783,7 @@ pub fn expression( }, Type::Array(_, dim) => match dim.last().unwrap() { ArrayLength::Dynamic => { - if ns.target == Target::Solana || ns.target == Target::Soroban { + if target.storage_array_length_is_inline() { Expression::StorageArrayLength { loc: *loc, ty: ty.clone(), @@ -1234,26 +1234,13 @@ pub fn expression( target, )); - // Soroban lazy decode path: if memory contains encoded handles, decode on demand. - if ns.target == Target::Soroban { - if let Type::Ref(inner) = expr.ty() { - if matches!(inner.as_ref(), Type::SorobanHandle(_)) { - let load_handle = Expression::Load { - loc: *loc, - ty: inner.as_ref().clone(), - expr: expr.clone(), - }; - - return soroban_decode_arg(load_handle, cfg, vartab, ns, None); - } - } - } - - Expression::Load { + let load = Expression::Load { loc: *loc, ty: ty.clone(), expr, - } + }; + // Target gets to rewrite the Load (Soroban decodes handles; others return as-is). + target.lower_load(load, cfg, vartab, ns) } // for some built-ins, we have to inline special case code ast::Expression::Builtin { @@ -1808,16 +1795,17 @@ fn post_incdec( match var.ty() { Type::StorageRef(..) => { - let mut value = Expression::Variable { - loc: *loc, - ty: ty.clone(), - var_no: res, - }; - // If the target is Soroban, encode the value before storing it in storage. - if ns.target == Target::Soroban { - value = soroban_encode_arg(value, cfg, vartab, ns); - } - + let value = target.prepare_storage_value( + Expression::Variable { + loc: *loc, + ty: ty.clone(), + var_no: res, + }, + &dest, + cfg, + vartab, + ns, + ); cfg.add( vartab, Instr::SetStorage { @@ -1937,16 +1925,17 @@ fn pre_incdec( match var.ty() { Type::StorageRef(..) => { - let mut value = Expression::Variable { - loc: *loc, - ty: ty.clone(), - var_no: res, - }; - - if ns.target == Target::Soroban { - value = soroban_encode_arg(value, cfg, vartab, ns) - } - + let value = target.prepare_storage_value( + Expression::Variable { + loc: *loc, + ty: ty.clone(), + var_no: res, + }, + &dest, + cfg, + vartab, + ns, + ); cfg.add( vartab, Instr::SetStorage { @@ -2353,11 +2342,7 @@ fn abi_encode_with_signature( target: &dyn TargetCodegen, ) -> Expression { let mut args_iter = args.iter(); - let hash_algorithm = if ns.target == Target::Solana { - ast::Builtin::Sha256 - } else { - ast::Builtin::Keccak256 - }; + let hash_algorithm = target.selector_hash_algorithm(); let hash = ast::Expression::Builtin { loc: *loc, @@ -3802,16 +3787,17 @@ pub fn assign_single( } } Type::StorageRef(..) => { - let mut value = Expression::Variable { - loc: left.loc(), - ty: ty.clone(), - var_no: pos, - }; - - if ns.target == Target::Soroban { - value = soroban_encode_arg(value, cfg, vartab, ns); - } - + let value = target.prepare_storage_value( + Expression::Variable { + loc: left.loc(), + ty: ty.clone(), + var_no: pos, + }, + &dest, + cfg, + vartab, + ns, + ); cfg.add( vartab, Instr::SetStorage { @@ -3823,29 +3809,17 @@ pub fn assign_single( ); } Type::Ref(_) => { - let data = if ns.target == Target::Soroban - && matches!( - dest.ty(), - Type::Ref(inner) if matches!(inner.as_ref(), Type::SorobanHandle(_)) - ) { - soroban_encode_arg( - Expression::Variable { - loc: Loc::Codegen, - ty: ty.clone(), - var_no: pos, - }, - cfg, - vartab, - ns, - ) - } else { + let data = target.prepare_storage_value( Expression::Variable { loc: Loc::Codegen, ty: ty.clone(), var_no: pos, - } - }; - + }, + &dest, + cfg, + vartab, + ns, + ); cfg.add(vartab, Instr::Store { dest, data }); } _ => unreachable!(), diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index 326843b53..284648bb7 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::codegen::cfg::ControlFlowGraph; -use crate::codegen::Options; -use crate::sema::ast::Namespace; +use crate::codegen::vartable::Vartable; +use crate::codegen::{Expression, Options}; +use crate::sema::ast::{self, Namespace, Type}; +use solang_parser::pt::Loc; /// The per-event emission strategy, produced by a target. Defined in /// [`crate::codegen::events`]; re-exported here so both boundary traits have a @@ -29,4 +31,53 @@ pub(crate) trait TargetCodegen { /// Whole-program post-processing, called once after every contract's CFGs. fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {} + + /// Hash algorithm used for function selector computation. + /// Keccak256 everywhere except Solana (Sha256). + fn selector_hash_algorithm(&self) -> ast::Builtin { + ast::Builtin::Keccak256 + } + + /// Whether dynamic storage arrays store their length inline in the value (Solana/Soroban) + /// or in a separate storage slot (Polkadot). + fn storage_array_length_is_inline(&self) -> bool { + false + } + + /// Optionally rewrite a freshly-built `Load` expression. + /// Soroban decodes handles on load; other targets pass through unchanged. + fn lower_load( + &self, + load: Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + load + } + + /// Transform a value just before it is written to storage or a storage-backed ref. + /// Soroban encodes values to ScVal handles; other targets pass through unchanged. + fn prepare_storage_value( + &self, + value: Expression, + _dest: &Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + value + } + + /// Default value for an uninitialised storage variable; `None` means "skip the variable". + fn default_storage_value( + &self, + _loc: &Loc, + _ty: &Type, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Option { + None + } } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 6b40473ee..72b6db955 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -35,7 +35,7 @@ use self::{ vartable::Vartable, }; use crate::sema::ast::{ - ArrayLength, FormatArg, Function, Layout, Namespace, RetrieveType, StringLocation, Type, + FormatArg, Function, Layout, Namespace, RetrieveType, StringLocation, Type, }; use crate::{sema::ast, Target}; use std::cmp::Ordering; @@ -52,7 +52,6 @@ use num_rational::BigRational; use num_traits::{FromPrimitive, Zero}; use solang_parser::diagnostics::Diagnostic; use solang_parser::{pt, pt::CodeLocation}; -use targets::soroban::encoding::soroban_encode_arg; // The sizeof(struct account_data_header) pub const SOLANA_FIRST_OFFSET: u64 = 16; @@ -383,15 +382,6 @@ fn storage_initializer( for layout in &ns.contracts[contract_no].layout { let var = &ns.contracts[layout.contract_no].variables[layout.var_no]; - let soroban_init_with_vec = ns.target == Target::Soroban - && match &var.ty { - Type::String | Type::DynamicBytes | Type::Slice(_) => true, - Type::Array(elem_ty, dims) if dims.last() == Some(&ArrayLength::Dynamic) => { - !elem_ty.is_reference_type(ns) - } - _ => false, - }; - let mut value = if let Some(init) = &var.initializer { expression( init, @@ -403,8 +393,10 @@ fn storage_initializer( opt, target, ) - } else if soroban_init_with_vec { - targets::soroban::soroban_vec_new(&var.loc, &var.ty, &mut cfg, &mut vartab) + } else if let Some(default) = + target.default_storage_value(&var.loc, &var.ty, &mut cfg, &mut vartab, ns) + { + default } else { continue; }; @@ -417,11 +409,7 @@ fn storage_initializer( None, ); - //let mut value = expression(init, &mut cfg, contract_no, None, ns, &mut vartab, opt); - - if ns.target == Target::Soroban { - value = soroban_encode_arg(value, &mut cfg, &mut vartab, ns); - } + value = target.prepare_storage_value(value, &storage, &mut cfg, &mut vartab, ns); cfg.add( &mut vartab, diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index c207eb5b6..a38c4ede0 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -7,6 +7,7 @@ use crate::codegen::interface::TargetCodegen; use crate::codegen::solana_accounts::account_collection::collect_accounts_from_contract; use crate::codegen::solana_accounts::account_management::manage_contract_accounts; use crate::codegen::{dispatch, Options}; +use crate::sema::ast; use crate::sema::ast::Namespace; use crate::Target; @@ -55,6 +56,14 @@ impl TargetCodegen for SolanaTarget { } } } + + fn storage_array_length_is_inline(&self) -> bool { + true + } + + fn selector_hash_algorithm(&self) -> ast::Builtin { + ast::Builtin::Sha256 + } } impl TargetCodegen for PolkadotTarget { diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index 68b425615..c952bde7a 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod dispatch; pub(crate) mod encoding; pub(crate) mod events; -use self::encoding::soroban_encode_arg; +use self::encoding::{soroban_decode_arg, soroban_encode_arg}; use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}; use crate::codegen::error::CodegenError; use crate::codegen::expression::{expression, load_storage}; @@ -46,6 +46,69 @@ impl TargetCodegen for SorobanTarget { ) -> Vec { dispatch::function_dispatch(contract_no, all_cfg, ns, opt) } + + fn storage_array_length_is_inline(&self) -> bool { + true + } + + fn lower_load( + &self, + load: Expression, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + // Check the INNER expression's type (the pointer): if it is Ref(SorobanHandle), + // the variable holds an encoded handle and must be decoded on load. + if let Expression::Load { ref expr, .. } = load { + if let Type::Ref(inner) = expr.ty() { + if matches!(inner.as_ref(), Type::SorobanHandle(_)) { + return soroban_decode_arg(load, cfg, vartab, ns, None); + } + } + } + load + } + + fn prepare_storage_value( + &self, + value: Expression, + dest: &Expression, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + // For Store to a non-SorobanHandle Ref, pass the value through unchanged. + if let Type::Ref(inner) = dest.ty() { + if !matches!(inner.as_ref(), Type::SorobanHandle(_)) { + return value; + } + } + // SetStorage or Store to a SorobanHandle: encode as ScVal. + soroban_encode_arg(value, cfg, vartab, ns) + } + + fn default_storage_value( + &self, + loc: &pt::Loc, + ty: &Type, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ns: &Namespace, + ) -> Option { + match ty { + Type::String | Type::DynamicBytes | Type::Slice(_) => { + Some(soroban_vec_new(loc, ty, cfg, vartab)) + } + Type::Array(elem_ty, dims) + if dims.last() == Some(&ast::ArrayLength::Dynamic) + && !elem_ty.is_reference_type(ns) => + { + Some(soroban_vec_new(loc, ty, cfg, vartab)) + } + _ => None, + } + } } pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) { From ddac492cf60675b5915e1e4c5ee95e036c927843 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Thu, 18 Jun 2026 18:34:04 +0300 Subject: [PATCH 05/15] codegen: route abi_encode/abi_decode through TargetCodegen Signed-off-by: Islam-Imad --- src/codegen/constructor.rs | 4 +--- src/codegen/encoding/mod.rs | 9 --------- src/codegen/expression.rs | 28 +++++++++++++++------------- src/codegen/interface.rs | 30 ++++++++++++++++++++++++++++++ src/codegen/targets/soroban/mod.rs | 30 +++++++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/codegen/constructor.rs b/src/codegen/constructor.rs index 6f4fd5d74..1a71647e7 100644 --- a/src/codegen/constructor.rs +++ b/src/codegen/constructor.rs @@ -13,8 +13,6 @@ use crate::Target; use num_bigint::{BigInt, Sign}; use solang_parser::pt::Loc; -use super::encoding::abi_encode; - /// This function encodes the constructor arguments and place an instruction in the CFG to /// call the constructor of a contract. pub(super) fn call_constructor( @@ -102,7 +100,7 @@ pub(super) fn call_constructor( args.append(&mut constructor_args); - let (encoded_args, _) = abi_encode(loc, args, ns, vartab, cfg, false); + let (encoded_args, _) = target.abi_encode(loc, args, ns, vartab, cfg, false); cfg.add( vartab, Instr::Constructor { diff --git a/src/codegen/encoding/mod.rs b/src/codegen/encoding/mod.rs index 26030101e..8f9e30a44 100644 --- a/src/codegen/encoding/mod.rs +++ b/src/codegen/encoding/mod.rs @@ -16,7 +16,6 @@ use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::encoding::borsh_encoding::BorshEncoding; use crate::codegen::encoding::scale_encoding::ScaleEncoding; use crate::codegen::expression::load_storage; -use crate::codegen::targets::soroban::encoding::{soroban_decode, soroban_encode}; use crate::codegen::vartable::Vartable; use crate::codegen::{Builtin, Expression}; use crate::sema::ast::{ArrayLength, Namespace, RetrieveType, StructType, Type, Type::Uint}; @@ -39,10 +38,6 @@ pub(super) fn abi_encode( cfg: &mut ControlFlowGraph, packed: bool, ) -> (Expression, Expression) { - if ns.target == Target::Soroban { - let ret = soroban_encode(loc, args, ns, vartab, cfg, packed); - return (ret.0, ret.1); - } let mut encoder = create_encoder(ns, packed); let size = calculate_size_args(&mut encoder, &args, ns, vartab, cfg); let encoded_bytes = vartab.temp_name("abi_encoded", &Type::DynamicBytes); @@ -95,10 +90,6 @@ pub(super) fn abi_decode( cfg: &mut ControlFlowGraph, buffer_size_expr: Option, ) -> Vec { - if ns.target == Target::Soroban { - return soroban_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr); - } - let buffer_size = vartab.temp_anonymous(&Uint(32)); if let Some(length_expression) = buffer_size_expr { cfg.add( diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 50c7330a5..5b6d6c49b 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -use super::encoding::{abi_decode, abi_encode}; use super::revert::{ assert_failure, expr_assert, log_runtime_error, require, PanicCode, SolidityError, }; @@ -2265,7 +2264,7 @@ fn abi_encode_many( .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); - abi_encode(loc, args, ns, vartab, cfg, false).0 + target.abi_encode(loc, args, ns, vartab, cfg, false).0 } fn abi_encode_packed( @@ -2284,7 +2283,7 @@ fn abi_encode_packed( .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); - let (encoded, _) = abi_encode(loc, packed, ns, vartab, cfg, true); + let (encoded, _) = target.abi_encode(loc, packed, ns, vartab, cfg, true); encoded } @@ -2295,11 +2294,14 @@ fn encode_many_with_selector( ns: &Namespace, vartab: &mut Vartable, cfg: &mut ControlFlowGraph, + target: &dyn TargetCodegen, ) -> Expression { let mut encoder_args: Vec = Vec::with_capacity(args.len() + 1); encoder_args.push(selector); encoder_args.append(&mut args); - abi_encode(loc, encoder_args, ns, vartab, cfg, false).0 + target + .abi_encode(loc, encoder_args, ns, vartab, cfg, false) + .0 } fn abi_encode_with_selector( @@ -2327,7 +2329,7 @@ fn abi_encode_with_selector( let args = args_iter .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); - encode_many_with_selector(loc, selector, args, ns, vartab, cfg) + encode_many_with_selector(loc, selector, args, ns, vartab, cfg, target) } fn abi_encode_with_signature( @@ -2355,7 +2357,7 @@ fn abi_encode_with_signature( let args = args_iter .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); - encode_many_with_selector(loc, selector, args, ns, vartab, cfg) + encode_many_with_selector(loc, selector, args, ns, vartab, cfg, target) } fn abi_encode_call( @@ -2388,7 +2390,7 @@ fn abi_encode_call( let args = args_iter .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) .collect::>(); - encode_many_with_selector(loc, selector, args, ns, vartab, cfg) + encode_many_with_selector(loc, selector, args, ns, vartab, cfg, target) } fn builtin_evm_gasprice( @@ -2975,7 +2977,7 @@ fn expr_builtin( args_vec.push(arg); } - let args_encoded = abi_encode(loc, args_vec.clone(), ns, vartab, cfg, false); + let args_encoded = target.abi_encode(loc, args_vec.clone(), ns, vartab, cfg, false); let args_buf = args_encoded.0; @@ -4164,7 +4166,7 @@ pub fn emit_function_call( }, ); - let (payload, _) = abi_encode(loc, args, ns, vartab, cfg, false); + let (payload, _) = target.abi_encode(loc, args, ns, vartab, cfg, false); let flags = call_args.flags.as_ref().map(|expr| { expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) @@ -4207,7 +4209,7 @@ pub fn emit_function_call( .iter() .map(|e| e.ty.clone()) .collect::>(); - abi_decode( + target.abi_decode( loc, &Expression::ReturnData { loc: *loc }, &tys, @@ -4269,7 +4271,7 @@ pub fn emit_function_call( tys.insert(0, Type::Bytes(ns.target.selector_length())); args.insert(0, selector); - let (payload, _) = abi_encode(loc, args, ns, vartab, cfg, false); + let (payload, _) = target.abi_encode(loc, args, ns, vartab, cfg, false); let flags = call_args.flags.as_ref().map(|expr| { expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) @@ -4305,7 +4307,7 @@ pub fn emit_function_call( } if !func_returns.is_empty() && returns[0] != Type::Void { - abi_decode( + target.abi_decode( loc, &Expression::ReturnData { loc: *loc }, returns, @@ -4341,7 +4343,7 @@ pub fn emit_function_call( if tys.len() == 1 && tys[0] == Type::Void { vec![Expression::Poison] } else { - abi_decode(loc, &data, tys, ns, vartab, cfg, None) + target.abi_decode(loc, &data, tys, ns, vartab, cfg, None) } } _ => unreachable!(), diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index 284648bb7..1ad6662d4 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -80,4 +80,34 @@ pub(crate) trait TargetCodegen { ) -> Option { None } + + /// Encode `args` into the target's wire format, returning `(buffer, length)`. + /// The default drives the shared buffer encoder (Borsh / SCALE); Soroban encodes to + /// ScVal handles instead. + fn abi_encode( + &self, + loc: &Loc, + args: Vec, + ns: &Namespace, + vartab: &mut Vartable, + cfg: &mut ControlFlowGraph, + packed: bool, + ) -> (Expression, Expression) { + crate::codegen::encoding::abi_encode(loc, args, ns, vartab, cfg, packed) + } + + /// Decode `types` out of `buffer`. The default drives the shared buffer decoder + /// (Borsh / SCALE); Soroban decodes ScVal handles instead. + fn abi_decode( + &self, + loc: &Loc, + buffer: &Expression, + types: &[Type], + ns: &Namespace, + vartab: &mut Vartable, + cfg: &mut ControlFlowGraph, + buffer_size_expr: Option, + ) -> Vec { + crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) + } } diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index c952bde7a..5ec21c85d 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod dispatch; pub(crate) mod encoding; pub(crate) mod events; -use self::encoding::{soroban_decode_arg, soroban_encode_arg}; +use self::encoding::{soroban_decode, soroban_decode_arg, soroban_encode, soroban_encode_arg}; use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}; use crate::codegen::error::CodegenError; use crate::codegen::expression::{expression, load_storage}; @@ -109,6 +109,33 @@ impl TargetCodegen for SorobanTarget { _ => None, } } + + fn abi_encode( + &self, + loc: &pt::Loc, + args: Vec, + ns: &Namespace, + vartab: &mut Vartable, + cfg: &mut ControlFlowGraph, + packed: bool, + ) -> (Expression, Expression) { + // Soroban encodes to ScVal handles; soroban_encode returns a 3-tuple, drop the spread. + let (buffer, size, _) = soroban_encode(loc, args, ns, vartab, cfg, packed); + (buffer, size) + } + + fn abi_decode( + &self, + loc: &pt::Loc, + buffer: &Expression, + types: &[Type], + ns: &Namespace, + vartab: &mut Vartable, + cfg: &mut ControlFlowGraph, + buffer_size_expr: Option, + ) -> Vec { + soroban_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) + } } pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) { @@ -148,6 +175,7 @@ pub(super) fn validate_event_abi_types(contract_no: usize, ns: &mut Namespace) { )); } } + } } From 9930674be54d9d07f2beba1f079e2cde9211b4d2 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Thu, 18 Jun 2026 20:47:06 +0300 Subject: [PATCH 06/15] codegen: route storage_array_push/pop through TargetCodegen Signed-off-by: Islam-Imad --- src/codegen/expression.rs | 48 +--------- src/codegen/interface.rs | 58 ++++++++++- src/codegen/storage.rs | 149 +++++++---------------------- src/codegen/targets/mod.rs | 110 ++++++++++++++++++++- src/codegen/targets/soroban/mod.rs | 93 +++++++++++++++++- 5 files changed, 294 insertions(+), 164 deletions(-) diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 5b6d6c49b..f1e5028de 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -3,9 +3,7 @@ use super::revert::{ assert_failure, expr_assert, log_runtime_error, require, PanicCode, SolidityError, }; -use super::storage::{ - array_offset, array_pop, array_push, storage_slots_array_pop, storage_slots_array_push, -}; +use super::storage::array_offset; use super::targets::soroban::encoding::soroban_encode; use super::targets::soroban::encoding::{soroban_decode_arg, soroban_encode_arg}; use super::{ @@ -1259,21 +1257,7 @@ pub fn expression( args, } => { if args[0].ty().is_contract_storage() { - if ns.target == Target::Solana || args[0].ty().is_storage_bytes() { - array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, target) - } else { - storage_slots_array_push( - loc, - args, - cfg, - contract_no, - func, - ns, - vartab, - opt, - target, - ) - } + target.storage_array_push(loc, args, cfg, contract_no, func, ns, vartab, opt) } else { let second_arg = if args.len() > 1 { expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target) @@ -1302,33 +1286,7 @@ pub fn expression( args, } => { if args[0].ty().is_contract_storage() { - if ns.target == Target::Solana || args[0].ty().is_storage_bytes() { - array_pop( - loc, - args, - &ty[0], - cfg, - contract_no, - func, - ns, - vartab, - opt, - target, - ) - } else { - storage_slots_array_pop( - loc, - args, - &ty[0], - cfg, - contract_no, - func, - ns, - vartab, - opt, - target, - ) - } + target.storage_array_pop(loc, args, &ty[0], cfg, contract_no, func, ns, vartab, opt) } else { let address_res = vartab.temp_anonymous(&ty[0]); diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index 1ad6662d4..6f04cc2e1 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -3,7 +3,7 @@ use crate::codegen::cfg::ControlFlowGraph; use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; -use crate::sema::ast::{self, Namespace, Type}; +use crate::sema::ast::{self, Function, Namespace, Type}; use solang_parser::pt::Loc; /// The per-event emission strategy, produced by a target. Defined in @@ -110,4 +110,60 @@ pub(crate) trait TargetCodegen { ) -> Vec { crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) } + + /// Lower `arr.push(value)` on a contract-storage array. Each target owns its storage + /// representation (Solana flat slots, Polkadot hashed slots, Soroban host vectors), so + /// there is no shared default. + fn storage_array_push( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression; + + /// Lower `arr.pop()` on a contract-storage array. See [`Self::storage_array_push`]. + fn storage_array_pop( + &self, + loc: &Loc, + args: &[ast::Expression], + return_ty: &Type, + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression; + + /// Compute the storage slot of array element `index` for the hashed-slots push path. + /// The default derives it from `keccak256(array_slot)`; Soroban indexes its host vector + /// with an encoded key instead. + fn storage_array_entry_offset( + &self, + loc: &Loc, + var_expr: &Expression, + index: Expression, + elem_ty: &Type, + slot_ty: &Type, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + crate::codegen::storage::array_offset( + loc, + Expression::Keccak256 { + loc: *loc, + ty: slot_ty.clone(), + exprs: vec![var_expr.clone()], + }, + index, + elem_ty.clone(), + ns, + ) + } } diff --git a/src/codegen/storage.rs b/src/codegen/storage.rs index ae1544258..b113df3be 100644 --- a/src/codegen/storage.rs +++ b/src/codegen/storage.rs @@ -1,10 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::codegen::interface::TargetCodegen; -use crate::codegen::targets::soroban::encoding::soroban_encode_arg; use crate::codegen::Expression; use crate::sema::ast; -use crate::Target; use num_bigint::BigInt; use num_traits::FromPrimitive; use num_traits::One; @@ -16,7 +14,6 @@ use super::revert::SolidityError; use super::Options; use super::{ cfg::{ControlFlowGraph, Instr}, - targets::soroban::{soroban_storage_pop, soroban_storage_push}, vartable::Vartable, }; use crate::codegen::revert::{assert_failure, log_runtime_error}; @@ -97,20 +94,6 @@ pub fn storage_slots_array_push( opt: &Options, target: &dyn TargetCodegen, ) -> Expression { - let inner_ty = if let Type::StorageRef(_, inner) = args[0].ty() { - if let Type::Array(elem_ty, _) = inner.deref_any() { - elem_ty.clone() - } else { - panic!("expected storage array type"); - } - } else { - panic!("expected storage reference type"); - }; - - if ns.target == Target::Soroban && !inner_ty.is_reference_type(ns) { - return soroban_storage_push(loc, args, cfg, contract_no, func, ns, vartab, opt, target); - } - // set array+length to val_expr let slot_ty = ns.storage_type(); let length_pos = vartab.temp_anonymous(&slot_ty); @@ -133,39 +116,20 @@ pub fn storage_slots_array_push( let entry_pos = vartab.temp_anonymous(&slot_ty); - let array_offset = if ns.target == Target::Soroban { - let index = Expression::Variable { + let array_offset = target.storage_array_entry_offset( + loc, + &var_expr, + Expression::Variable { loc: *loc, ty: slot_ty.clone(), var_no: length_pos, - }; - - let index_encoded = soroban_encode_arg(index, cfg, vartab, ns); - - Expression::Subscript { - loc: *loc, - ty: elem_ty.clone(), - array_ty: Type::StorageRef(false, Box::new(elem_ty.clone())), - expr: Box::new(var_expr.clone()), - index: Box::new(index_encoded), - } - } else { - array_offset( - loc, - Expression::Keccak256 { - loc: *loc, - ty: slot_ty.clone(), - exprs: vec![var_expr.clone()], - }, - Expression::Variable { - loc: *loc, - ty: slot_ty.clone(), - var_no: length_pos, - }, - elem_ty.clone(), - ns, - ) - }; + }, + &elem_ty, + &slot_ty, + cfg, + vartab, + ns, + ); cfg.add( vartab, @@ -177,29 +141,27 @@ pub fn storage_slots_array_push( ); if args.len() == 2 { - let mut value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); - - if ns.target == Target::Soroban { - value = soroban_encode_arg(value, cfg, vartab, ns); - } + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); + let entry_slot = Expression::Variable { + loc: *loc, + ty: slot_ty.clone(), + var_no: entry_pos, + }; + let value = target.prepare_storage_value(value, &entry_slot, cfg, vartab, ns); cfg.add( vartab, Instr::SetStorage { ty: elem_ty.clone(), value, - storage: Expression::Variable { - loc: *loc, - ty: slot_ty.clone(), - var_no: entry_pos, - }, + storage: entry_slot, storage_type: None, }, ); } // increase length - let mut new_length = Expression::Add { + let new_length = Expression::Add { loc: *loc, ty: slot_ty.clone(), overflowing: true, @@ -215,9 +177,7 @@ pub fn storage_slots_array_push( }), }; - if ns.target == Target::Soroban { - new_length = soroban_encode_arg(new_length, cfg, vartab, ns); - } + let new_length = target.prepare_storage_value(new_length, &var_expr, cfg, vartab, ns); cfg.add( vartab, @@ -253,21 +213,6 @@ pub fn storage_slots_array_pop( opt: &Options, target: &dyn TargetCodegen, ) -> Expression { - if ns.target == Target::Soroban { - return soroban_storage_pop( - loc, - args, - return_ty, - cfg, - contract_no, - func, - ns, - vartab, - opt, - target, - ); - } - // set array+length to val_expr let slot_ty = ns.storage_type(); let length_ty = ns.storage_type(); @@ -326,7 +271,7 @@ pub fn storage_slots_array_pop( cfg.set_basic_block(has_elements); let new_length = vartab.temp_anonymous(&slot_ty); - let mut subtract = Expression::Subtract { + let subtract = Expression::Subtract { loc: *loc, ty: length_ty.clone(), overflowing: true, @@ -342,10 +287,6 @@ pub fn storage_slots_array_pop( }), }; - if ns.target == Target::Soroban { - subtract = soroban_encode_arg(subtract, cfg, vartab, ns); - } - cfg.add( vartab, Instr::Set { @@ -360,39 +301,21 @@ pub fn storage_slots_array_pop( let elem_ty = ty.storage_array_elem().deref_any().clone(); let entry_pos = vartab.temp_anonymous(&slot_ty); - let array_offset_expr = if ns.target == Target::Soroban { - let index = Expression::Variable { + let array_offset_expr = array_offset( + loc, + Expression::Keccak256 { loc: *loc, ty: slot_ty.clone(), - var_no: length_pos, - }; - - let index_encoded = soroban_encode_arg(index, cfg, vartab, ns); - - Expression::Subscript { + exprs: vec![var_expr.clone()], + }, + Expression::Variable { loc: *loc, - ty: elem_ty.clone(), - array_ty: Type::StorageRef(false, Box::new(elem_ty.clone())), - expr: Box::new(var_expr.clone()), - index: Box::new(index_encoded), - } - } else { - array_offset( - loc, - Expression::Keccak256 { - loc: *loc, - ty: slot_ty.clone(), - exprs: vec![var_expr.clone()], - }, - Expression::Variable { - loc: *loc, - ty: slot_ty.clone(), - var_no: new_length, - }, - elem_ty.clone(), - ns, - ) - }; + ty: slot_ty.clone(), + var_no: new_length, + }, + elem_ty.clone(), + ns, + ); cfg.add( vartab, @@ -481,10 +404,6 @@ pub fn array_push( opt: &Options, target: &dyn TargetCodegen, ) -> Expression { - if ns.target == Target::Soroban { - return soroban_storage_push(loc, args, cfg, contract_no, func, ns, vartab, opt, target); - } - let storage = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); let mut ty = args[0].ty().storage_array_elem(); diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index a38c4ede0..e2045d426 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -6,10 +6,15 @@ use crate::codegen::cfg::ControlFlowGraph; use crate::codegen::interface::TargetCodegen; use crate::codegen::solana_accounts::account_collection::collect_accounts_from_contract; use crate::codegen::solana_accounts::account_management::manage_contract_accounts; -use crate::codegen::{dispatch, Options}; +use crate::codegen::storage::{ + array_pop, array_push, storage_slots_array_pop, storage_slots_array_push, +}; +use crate::codegen::vartable::Vartable; +use crate::codegen::{dispatch, Expression, Options}; use crate::sema::ast; -use crate::sema::ast::Namespace; +use crate::sema::ast::{Function, Namespace, RetrieveType, Type}; use crate::Target; +use solang_parser::pt::Loc; use self::soroban::SorobanTarget; @@ -64,6 +69,47 @@ impl TargetCodegen for SolanaTarget { fn selector_hash_algorithm(&self) -> ast::Builtin { ast::Builtin::Sha256 } + + fn storage_array_push( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + // Solana stores dynamic arrays as flat slots. + array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) + } + + fn storage_array_pop( + &self, + loc: &Loc, + args: &[ast::Expression], + return_ty: &Type, + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + array_pop( + loc, + args, + return_ty, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + ) + } } impl TargetCodegen for PolkadotTarget { @@ -76,4 +122,64 @@ impl TargetCodegen for PolkadotTarget { ) -> Vec { dispatch::polkadot::function_dispatch(contract_no, all_cfg, ns, opt) } + + fn storage_array_push( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + // `bytes` uses the flat-slot path; typed arrays use hashed slots. + if args[0].ty().is_storage_bytes() { + array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) + } else { + storage_slots_array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) + } + } + + fn storage_array_pop( + &self, + loc: &Loc, + args: &[ast::Expression], + return_ty: &Type, + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + if args[0].ty().is_storage_bytes() { + array_pop( + loc, + args, + return_ty, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + ) + } else { + storage_slots_array_pop( + loc, + args, + return_ty, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + ) + } + } } diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index 5ec21c85d..e8d18770d 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -9,6 +9,7 @@ use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}; use crate::codegen::error::CodegenError; use crate::codegen::expression::{expression, load_storage}; use crate::codegen::interface::TargetCodegen; +use crate::codegen::storage::{array_pop, storage_slots_array_push}; use crate::codegen::vartable::Vartable; use crate::codegen::Options; use crate::codegen::{Builtin, Expression, HostFunctions}; @@ -136,6 +137,97 @@ impl TargetCodegen for SorobanTarget { ) -> Vec { soroban_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) } + + fn storage_array_push( + &self, + loc: &pt::Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + // Arrays whose elements are reference types use the shared hashed-slots path (the + // entry offset and value encoding are routed back through this target); everything + // else (scalars, `bytes`) goes through the dedicated host-vector push. + let elem_is_ref = !args[0].ty().is_storage_bytes() + && matches!( + args[0].ty(), + Type::StorageRef(_, inner) + if matches!(inner.deref_any(), Type::Array(elem_ty, _) + if elem_ty.is_reference_type(ns)) + ); + if elem_is_ref { + storage_slots_array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) + } else { + soroban_storage_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) + } + } + + fn storage_array_pop( + &self, + loc: &pt::Loc, + args: &[ast::Expression], + return_ty: &Type, + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + if args[0].ty().is_storage_bytes() { + array_pop( + loc, + args, + return_ty, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + ) + } else { + soroban_storage_pop( + loc, + args, + return_ty, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + ) + } + } + + fn storage_array_entry_offset( + &self, + loc: &pt::Loc, + var_expr: &Expression, + index: Expression, + elem_ty: &Type, + _slot_ty: &Type, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + // Soroban indexes its host vector by an encoded key rather than a hashed slot. + let index_encoded = soroban_encode_arg(index, cfg, vartab, ns); + Expression::Subscript { + loc: *loc, + ty: elem_ty.clone(), + array_ty: Type::StorageRef(false, Box::new(elem_ty.clone())), + expr: Box::new(var_expr.clone()), + index: Box::new(index_encoded), + } + } } pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) { @@ -175,7 +267,6 @@ pub(super) fn validate_event_abi_types(contract_no: usize, ns: &mut Namespace) { )); } } - } } From e45cbd831536f3b6740180b0c07cdc2bcbe21fba Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Thu, 18 Jun 2026 21:13:38 +0300 Subject: [PATCH 07/15] codegen: route event_emitter through TargetCodegen Signed-off-by: Islam-Imad --- src/codegen/events/mod.rs | 35 +++--------------------------- src/codegen/events/polkadot.rs | 8 +++---- src/codegen/events/solana.rs | 10 ++++----- src/codegen/expression.rs | 3 +-- src/codegen/interface.rs | 17 ++++++++++----- src/codegen/statements/mod.rs | 3 +-- src/codegen/targets/mod.rs | 26 ++++++++++++++++++++-- src/codegen/targets/soroban/mod.rs | 13 ++++++++++- 8 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/codegen/events/mod.rs b/src/codegen/events/mod.rs index c35e90ad6..88f28b074 100644 --- a/src/codegen/events/mod.rs +++ b/src/codegen/events/mod.rs @@ -1,19 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 -mod polkadot; -mod solana; +pub(crate) mod polkadot; +pub(crate) mod solana; use crate::codegen::cfg::ControlFlowGraph; -use crate::codegen::events::polkadot::PolkadotEventEmitter; -use crate::codegen::events::solana::SolanaEventEmitter; use crate::codegen::interface::TargetCodegen; -use crate::codegen::targets::soroban::events::SorobanEventEmitter; use crate::codegen::vartable::Vartable; use crate::codegen::Options; -use crate::sema::ast; -use crate::sema::ast::{Function, Namespace}; -use crate::Target; -use solang_parser::pt; +use crate::sema::ast::Function; /// This traits delineates the common behavior of event emission. As each target uses a different /// encoding scheme, there must be an implementation of this trait for each. @@ -37,26 +31,3 @@ pub(crate) trait EventEmitter { /// Generates the selector fn selector(&self, emitting_contract_no: usize) -> Vec; } - -/// Create a new event emitter based on the target blockchain -pub(super) fn new_event_emitter<'a>( - loc: &pt::Loc, - event_no: usize, - args: &'a [ast::Expression], - ns: &'a Namespace, -) -> Box { - match ns.target { - Target::Polkadot { .. } | Target::EVM => { - Box::new(PolkadotEventEmitter { args, ns, event_no }) - } - - Target::Solana => Box::new(SolanaEventEmitter { - loc: *loc, - args, - ns, - event_no, - }), - - Target::Soroban => Box::new(SorobanEventEmitter { args, ns, event_no }), - } -} diff --git a/src/codegen/events/polkadot.rs b/src/codegen/events/polkadot.rs index 077b664ab..ec6d40bcb 100644 --- a/src/codegen/events/polkadot.rs +++ b/src/codegen/events/polkadot.rs @@ -17,11 +17,11 @@ use solang_parser::pt; /// Data and topic encoding follow [ink! v5.0][0]. /// /// [0]: https://use.ink/basics/events/#topics -pub(super) struct PolkadotEventEmitter<'a> { +pub(crate) struct PolkadotEventEmitter<'a> { /// Arguments passed to the event - pub(super) args: &'a [ast::Expression], - pub(super) ns: &'a Namespace, - pub(super) event_no: usize, + pub(crate) args: &'a [ast::Expression], + pub(crate) ns: &'a Namespace, + pub(crate) event_no: usize, } impl EventEmitter for PolkadotEventEmitter<'_> { diff --git a/src/codegen/events/solana.rs b/src/codegen/events/solana.rs index 8218a7258..28cbab766 100644 --- a/src/codegen/events/solana.rs +++ b/src/codegen/events/solana.rs @@ -13,12 +13,12 @@ use crate::sema::ast::{Function, Namespace, Type}; use solang_parser::pt::Loc; /// This struct implements the trait 'EventEmitter' to handle the emission of events for Solana. -pub(super) struct SolanaEventEmitter<'a> { - pub(super) loc: Loc, +pub(crate) struct SolanaEventEmitter<'a> { + pub(crate) loc: Loc, /// Arguments passed to the event - pub(super) args: &'a [ast::Expression], - pub(super) ns: &'a Namespace, - pub(super) event_no: usize, + pub(crate) args: &'a [ast::Expression], + pub(crate) ns: &'a Namespace, + pub(crate) event_no: usize, } impl EventEmitter for SolanaEventEmitter<'_> { diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index f1e5028de..a3db36bd6 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -13,7 +13,6 @@ use super::{ use super::{polkadot, Options}; use crate::codegen::array_boundary::handle_array_assign; use crate::codegen::constructor::call_constructor; -use crate::codegen::events::new_event_emitter; use crate::codegen::interface::TargetCodegen; use crate::codegen::unused_variable::should_remove_assignment; use crate::codegen::{Builtin, Expression, HostFunctions}; @@ -861,7 +860,7 @@ pub fn expression( } }, ast::Expression::EventSelector { loc, ty, event_no } => { - let emitter = new_event_emitter(loc, *event_no, &[], ns); + let emitter = target.event_emitter(loc, *event_no, &[], ns); Expression::BytesLiteral { loc: *loc, diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index 6f04cc2e1..258661415 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -4,13 +4,10 @@ use crate::codegen::cfg::ControlFlowGraph; use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; use crate::sema::ast::{self, Function, Namespace, Type}; -use solang_parser::pt::Loc; +use solang_parser::pt::{self, Loc}; /// The per-event emission strategy, produced by a target. Defined in -/// [`crate::codegen::events`]; re-exported here so both boundary traits have a -/// single home. `TargetCodegen::event_emitter` is wired to return it in a later phase, -/// hence the allow until the first in-crate use lands. -#[allow(unused_imports)] +/// [`crate::codegen::events`]; re-exported here so the boundary is readable in one file. pub(crate) use crate::codegen::events::EventEmitter; pub(crate) trait TargetCodegen { @@ -140,6 +137,16 @@ pub(crate) trait TargetCodegen { opt: &Options, ) -> Expression; + /// Return the event emitter for the given event. The returned emitter borrows `args` and + /// `ns` so the lifetime must be threaded through. + fn event_emitter<'a>( + &self, + loc: &pt::Loc, + event_no: usize, + args: &'a [ast::Expression], + ns: &'a Namespace, + ) -> Box; + /// Compute the storage slot of array element `index` for the hashed-slots push path. /// The default derives it from `keccak256(array_slot)`; Soroban indexes its host vector /// with an encoded key instead. diff --git a/src/codegen/statements/mod.rs b/src/codegen/statements/mod.rs index 7ad4665c9..ee577bca0 100644 --- a/src/codegen/statements/mod.rs +++ b/src/codegen/statements/mod.rs @@ -2,7 +2,6 @@ use super::{ cfg::{ControlFlowGraph, Instr}, - events::new_event_emitter, expression::{assign_single, emit_function_call, expression}, revert::revert, unused_variable::{ @@ -634,7 +633,7 @@ pub(crate) fn statement( args, .. } => { - let emitter = new_event_emitter(loc, *event_no, args, ns); + let emitter = target.event_emitter(loc, *event_no, args, ns); emitter.emit(contract_no, func, cfg, vartab, opt, target); } Statement::Revert { diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index e2045d426..a206eacd7 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -3,7 +3,9 @@ pub(crate) mod soroban; use crate::codegen::cfg::ControlFlowGraph; -use crate::codegen::interface::TargetCodegen; +use crate::codegen::events::polkadot::PolkadotEventEmitter; +use crate::codegen::events::solana::SolanaEventEmitter; +use crate::codegen::interface::{EventEmitter, TargetCodegen}; use crate::codegen::solana_accounts::account_collection::collect_accounts_from_contract; use crate::codegen::solana_accounts::account_management::manage_contract_accounts; use crate::codegen::storage::{ @@ -14,7 +16,7 @@ use crate::codegen::{dispatch, Expression, Options}; use crate::sema::ast; use crate::sema::ast::{Function, Namespace, RetrieveType, Type}; use crate::Target; -use solang_parser::pt::Loc; +use solang_parser::pt::{self, Loc}; use self::soroban::SorobanTarget; @@ -110,6 +112,16 @@ impl TargetCodegen for SolanaTarget { self, ) } + + fn event_emitter<'a>( + &self, + loc: &pt::Loc, + event_no: usize, + args: &'a [ast::Expression], + ns: &'a Namespace, + ) -> Box { + Box::new(SolanaEventEmitter { loc: *loc, args, ns, event_no }) + } } impl TargetCodegen for PolkadotTarget { @@ -182,4 +194,14 @@ impl TargetCodegen for PolkadotTarget { ) } } + + fn event_emitter<'a>( + &self, + _loc: &pt::Loc, + event_no: usize, + args: &'a [ast::Expression], + ns: &'a Namespace, + ) -> Box { + Box::new(PolkadotEventEmitter { args, ns, event_no }) + } } diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index e8d18770d..35193e4a8 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -5,10 +5,11 @@ pub(crate) mod encoding; pub(crate) mod events; use self::encoding::{soroban_decode, soroban_decode_arg, soroban_encode, soroban_encode_arg}; +use self::events::SorobanEventEmitter; use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}; use crate::codegen::error::CodegenError; use crate::codegen::expression::{expression, load_storage}; -use crate::codegen::interface::TargetCodegen; +use crate::codegen::interface::{EventEmitter, TargetCodegen}; use crate::codegen::storage::{array_pop, storage_slots_array_push}; use crate::codegen::vartable::Vartable; use crate::codegen::Options; @@ -228,6 +229,16 @@ impl TargetCodegen for SorobanTarget { index: Box::new(index_encoded), } } + + fn event_emitter<'a>( + &self, + _loc: &pt::Loc, + event_no: usize, + args: &'a [ast::Expression], + ns: &'a Namespace, + ) -> Box { + Box::new(SorobanEventEmitter { args, ns, event_no }) + } } pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) { From 6dbe54b84bbe1a69396c33c86eb3d05f9c4116f8 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Fri, 19 Jun 2026 15:46:47 +0300 Subject: [PATCH 08/15] codegen: route struct access, print, builtin, and mapping hooks through TargetCodegen Signed-off-by: Islam-Imad --- src/codegen/constructor.rs | 2 +- src/codegen/encoding/mod.rs | 36 +++-- src/codegen/expression.rs | 204 +++++++--------------------- src/codegen/interface.rs | 94 ++++++++++--- src/codegen/mod.rs | 29 ++-- src/codegen/statements/try_catch.rs | 2 +- src/codegen/storage.rs | 23 +++- src/codegen/targets/mod.rs | 156 ++++++++++++++++++++- src/codegen/targets/soroban/mod.rs | 120 +++++++++++++++- src/sema/diagnostics.rs | 7 + 10 files changed, 456 insertions(+), 217 deletions(-) diff --git a/src/codegen/constructor.rs b/src/codegen/constructor.rs index 1a71647e7..8489a595c 100644 --- a/src/codegen/constructor.rs +++ b/src/codegen/constructor.rs @@ -39,7 +39,7 @@ pub(super) fn call_constructor( let gas = if let Some(gas) = &call_args.gas { expression(gas, cfg, callee_contract_no, func, ns, vartab, opt, target) } else { - default_gas(ns) + default_gas(ns, target) }; let salt = call_args diff --git a/src/codegen/encoding/mod.rs b/src/codegen/encoding/mod.rs index 8f9e30a44..c229e2489 100644 --- a/src/codegen/encoding/mod.rs +++ b/src/codegen/encoding/mod.rs @@ -16,6 +16,8 @@ use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::encoding::borsh_encoding::BorshEncoding; use crate::codegen::encoding::scale_encoding::ScaleEncoding; use crate::codegen::expression::load_storage; +use crate::codegen::interface::TargetCodegen; +use crate::codegen::targets::make_target; use crate::codegen::vartable::Vartable; use crate::codegen::{Builtin, Expression}; use crate::sema::ast::{ArrayLength, Namespace, RetrieveType, StructType, Type, Type::Uint}; @@ -39,7 +41,8 @@ pub(super) fn abi_encode( packed: bool, ) -> (Expression, Expression) { let mut encoder = create_encoder(ns, packed); - let size = calculate_size_args(&mut encoder, &args, ns, vartab, cfg); + let target = make_target(ns); + let size = calculate_size_args(&mut encoder, &args, ns, vartab, cfg, target.as_ref()); let encoded_bytes = vartab.temp_name("abi_encoded", &Type::DynamicBytes); let expr = Expression::AllocDynamicBytes { loc: *loc, @@ -155,6 +158,7 @@ fn calculate_size_args( ns: &Namespace, vartab: &mut Vartable, cfg: &mut ControlFlowGraph, + target: &dyn TargetCodegen, ) -> Expression { if args.is_empty() { return Expression::NumberLiteral { @@ -163,9 +167,9 @@ fn calculate_size_args( value: BigInt::zero(), }; } - let mut size = encoder.get_expr_size(0, &args[0], ns, vartab, cfg); + let mut size = encoder.get_expr_size(0, &args[0], ns, vartab, cfg, target); for (i, item) in args.iter().enumerate().skip(1) { - let additional = encoder.get_expr_size(i, item, ns, vartab, cfg); + let additional = encoder.get_expr_size(i, item, ns, vartab, cfg, target); size = Expression::Add { loc: Codegen, ty: Uint(32), @@ -1373,6 +1377,7 @@ pub(crate) trait AbiEncoding { ns: &Namespace, vartab: &mut Vartable, cfg: &mut ControlFlowGraph, + target: &dyn TargetCodegen, ) -> Expression { let ty = expr.ty().unwrap_user_type(ns); match &ty { @@ -1399,14 +1404,14 @@ pub(crate) trait AbiEncoding { value: BigInt::from(ns.target.selector_length()), }, Type::Struct(struct_ty) => { - self.calculate_struct_size(arg_no, expr, struct_ty, ns, vartab, cfg) + self.calculate_struct_size(arg_no, expr, struct_ty, ns, vartab, cfg, target) } Type::Slice(ty) => { let dims = vec![ArrayLength::Dynamic]; - self.calculate_array_size(expr, ty, &dims, arg_no, ns, vartab, cfg) + self.calculate_array_size(expr, ty, &dims, arg_no, ns, vartab, cfg, target) } Type::Array(ty, dims) => { - self.calculate_array_size(expr, ty, dims, arg_no, ns, vartab, cfg) + self.calculate_array_size(expr, ty, dims, arg_no, ns, vartab, cfg, target) } Type::ExternalFunction { .. } => { let selector_len: BigInt = ns.target.selector_length().into(); @@ -1419,18 +1424,18 @@ pub(crate) trait AbiEncoding { } Type::Ref(r) => { if let Type::Struct(struct_ty) = &**r { - return self.calculate_struct_size(arg_no, expr, struct_ty, ns, vartab, cfg); + return self.calculate_struct_size(arg_no, expr, struct_ty, ns, vartab, cfg, target); } let loaded = Expression::Load { loc: Codegen, ty: *r.clone(), expr: expr.clone().into(), }; - self.get_expr_size(arg_no, &loaded, ns, vartab, cfg) + self.get_expr_size(arg_no, &loaded, ns, vartab, cfg, target) } Type::StorageRef(_, r) => { - let var = load_storage(&Codegen, r, expr.clone(), cfg, vartab, None, ns); - let size = self.get_expr_size(arg_no, &var, ns, vartab, cfg); + let var = load_storage(&Codegen, r, expr.clone(), cfg, vartab, None, ns, target); + let size = self.get_expr_size(arg_no, &var, ns, vartab, cfg, target); self.storage_cache_insert(arg_no, var.clone()); size } @@ -1468,6 +1473,7 @@ pub(crate) trait AbiEncoding { ns: &Namespace, vartab: &mut Vartable, cfg: &mut ControlFlowGraph, + target: &dyn TargetCodegen, ) -> Expression { let dyn_dims = dims.iter().filter(|d| **d == ArrayLength::Dynamic).count(); @@ -1587,6 +1593,7 @@ pub(crate) trait AbiEncoding { &mut index_vec, vartab, cfg, + target, ); Expression::Variable { loc: Codegen, @@ -1610,6 +1617,7 @@ pub(crate) trait AbiEncoding { indexes: &mut Vec, vartab: &mut Vartable, cfg: &mut ControlFlowGraph, + target: &dyn TargetCodegen, ) { // If this dimension is dynamic, account for the encoded vector length variable. if !self.is_packed() && dims[dimension] == ArrayLength::Dynamic { @@ -1646,7 +1654,7 @@ pub(crate) trait AbiEncoding { cfg.set_basic_block(for_loop.body_block); if 0 == dimension { let deref = index_array(arr.clone(), dims, indexes, false); - let elem_size = self.get_expr_size(arg_no, &deref, ns, vartab, cfg); + let elem_size = self.get_expr_size(arg_no, &deref, ns, vartab, cfg, target); let size_var = Expression::Variable { loc: Codegen, ty: Uint(32), @@ -1677,6 +1685,7 @@ pub(crate) trait AbiEncoding { indexes, vartab, cfg, + target, ); } finish_array_loop(&for_loop, vartab, cfg); @@ -1691,6 +1700,7 @@ pub(crate) trait AbiEncoding { ns: &Namespace, vartab: &mut Vartable, cfg: &mut ControlFlowGraph, + target: &dyn TargetCodegen, ) -> Expression { if let Some(struct_size) = ns.calculate_struct_non_padded_size(struct_ty) { return Expression::NumberLiteral { @@ -1701,11 +1711,11 @@ pub(crate) trait AbiEncoding { } let first_type = struct_ty.definition(ns).fields[0].ty.clone(); let first_field = load_struct_member(first_type, expr.clone(), 0, ns); - let mut size = self.get_expr_size(arg_no, &first_field, ns, vartab, cfg); + let mut size = self.get_expr_size(arg_no, &first_field, ns, vartab, cfg, target); for i in 1..struct_ty.definition(ns).fields.len() { let ty = struct_ty.definition(ns).fields[i].ty.clone(); let field = load_struct_member(ty.clone(), expr.clone(), i, ns); - let expr_size = self.get_expr_size(arg_no, &field, ns, vartab, cfg).into(); + let expr_size = self.get_expr_size(arg_no, &field, ns, vartab, cfg, target).into(); size = Expression::Add { loc: Codegen, ty: Uint(32), diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index a3db36bd6..ff9d4cfd8 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -4,8 +4,7 @@ use super::revert::{ assert_failure, expr_assert, log_runtime_error, require, PanicCode, SolidityError, }; use super::storage::array_offset; -use super::targets::soroban::encoding::soroban_encode; -use super::targets::soroban::encoding::{soroban_decode_arg, soroban_encode_arg}; +use super::targets::soroban::encoding::{soroban_encode, soroban_encode_arg}; use super::{ cfg::{ControlFlowGraph, Instr, InternalCallTy}, vartable::Vartable, @@ -62,7 +61,7 @@ pub fn expression( let storage_type = storage_type(expr, ns); let storage = expression(expr, cfg, contract_no, func, ns, vartab, opt, target); - load_storage(loc, ty, storage, cfg, vartab, storage_type, ns) + load_storage(loc, ty, storage, cfg, vartab, storage_type, ns, target) } ast::Expression::Add { loc, @@ -787,7 +786,16 @@ pub fn expression( elem_ty: elem_ty.clone(), } } else { - load_storage(loc, &ns.storage_type(), array, cfg, vartab, None, ns) + load_storage( + loc, + &ns.storage_type(), + array, + cfg, + vartab, + None, + ns, + target, + ) } } ArrayLength::Fixed(length) => { @@ -930,72 +938,10 @@ pub fn expression( field: field_no, } if ty.is_contract_storage() => { if let Type::Struct(struct_ty) = var.ty().deref_any() { - let offset = if ns.target == Target::Solana { - struct_ty.definition(ns).storage_offsets[*field_no].clone() - } else { - struct_ty.definition(ns).fields[..*field_no] - .iter() - .filter(|field| !field.infinite_size) - .map(|field| field.ty.storage_slots(ns)) - .sum() - }; - - if ns.target == Target::Soroban { - // In Soroban, storage struct members are accessed via a key whose representation is a Soroban Vec. - // Therefore instead of adding the offset we insert it as a separate argument. - - let soroban_key = - expression(var, cfg, contract_no, func, ns, vartab, opt, target); - - let offset = Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(32), - value: offset, - }; - - let offset_encoded = soroban_encode_arg(offset, cfg, vartab, ns); - - let res = vartab.temp_name("vec_push_codegen", &Type::Uint(64)); - let var = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Uint(64), - var_no: res, - }; - - let enum_vec_put = Instr::Call { - res: vec![res], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::VecPushBack.name().to_string(), - }, - args: vec![soroban_key, offset_encoded], - }; - - cfg.add(vartab, enum_vec_put); - - var - } else { - Expression::Add { - loc: *loc, - ty: ns.storage_type(), - overflowing: true, - left: Box::new(expression( - var, - cfg, - contract_no, - func, - ns, - vartab, - opt, - target, - )), - right: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: ns.storage_type(), - value: offset, - }), - } - } + let var_expr = expression(var, cfg, contract_no, func, ns, vartab, opt, target); + target.lower_storage_struct_member( + loc, var_expr, struct_ty, *field_no, ns, cfg, vartab, + ) } else { unreachable!(); } @@ -1330,11 +1276,7 @@ pub fn expression( if opt.log_prints { let expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); - let to_print = if ns.target.is_polkadot() { - add_prefix_and_delimiter_to_print(expr) - } else { - expr - }; + let to_print = target.lower_print_expr(expr); let res = if let Expression::AllocDynamicBytes { loc, @@ -1419,15 +1361,6 @@ pub fn expression( args, .. } => abi_encode_call(args, cfg, contract_no, func, ns, vartab, loc, opt, target), - // The Polkadot gas price builtin takes an argument; the others do not - ast::Expression::Builtin { - loc, - kind: ast::Builtin::Gasprice, - args: expr, - .. - } if expr.len() == 1 && ns.target == Target::EVM => { - builtin_evm_gasprice(loc, expr, cfg, contract_no, func, ns, vartab, opt, target) - } ast::Expression::Builtin { loc, tys, @@ -1449,19 +1382,27 @@ pub fn expression( tys, kind, args, - } => expr_builtin( - args, - cfg, - contract_no, - func, - ns, - vartab, - loc, - tys, - *kind, - opt, - target, - ), + } => { + // Target gets first refusal; EVM uses this for gasleft(amount), for example. + if let Some(e) = + target.lower_builtin(loc, *kind, args, cfg, contract_no, func, ns, vartab, opt) + { + return e; + } + expr_builtin( + args, + cfg, + contract_no, + func, + ns, + vartab, + loc, + tys, + *kind, + opt, + target, + ) + } ast::Expression::FormatString { loc, format: args } => { format_string(args, cfg, contract_no, func, ns, vartab, loc, opt, target) } @@ -1685,6 +1626,7 @@ fn post_incdec( vartab, storage_type.clone(), ns, + target, ), _ => v, }; @@ -1827,6 +1769,7 @@ fn pre_incdec( vartab, storage_type.clone(), ns, + target, ), _ => v, }; @@ -2350,7 +2293,7 @@ fn abi_encode_call( encode_many_with_selector(loc, selector, args, ns, vartab, cfg, target) } -fn builtin_evm_gasprice( +pub(crate) fn builtin_evm_gasprice( loc: &pt::Loc, expr: &[ast::Expression], cfg: &mut ControlFlowGraph, @@ -2679,29 +2622,6 @@ fn expr_builtin( }; } - // In soroban, address is retrieved via a host function call - if ns.target == Target::Soroban { - let address_var_no = vartab.temp_anonymous(&Type::Uint(64)); - let address_var = Expression::Variable { - loc: *loc, - ty: Type::Address(false), - var_no: address_var_no, - }; - - let retrieve_address = Instr::Call { - res: vec![address_var_no], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::GetCurrentContractAddress.name().to_string(), - }, - args: vec![], - }; - - cfg.add(vartab, retrieve_address); - - return address_var; - } - // In emit, GetAddress returns a pointer to the address let codegen_expr = Expression::Builtin { loc: *loc, @@ -3970,7 +3890,7 @@ pub fn emit_function_call( let gas = if let Some(gas) = &call_args.gas { expression(gas, cfg, caller_contract_no, func, ns, vartab, opt, target) } else { - default_gas(ns) + default_gas(ns, target) }; let value = if let Some(value) = &call_args.value { expression( @@ -4082,7 +4002,7 @@ pub fn emit_function_call( let gas = if let Some(gas) = &call_args.gas { expression(gas, cfg, caller_contract_no, func, ns, vartab, opt, target) } else { - default_gas(ns) + default_gas(ns, target) }; let accounts = call_args.accounts.map(|expr| { expression(expr, cfg, caller_contract_no, func, ns, vartab, opt, target) @@ -4201,7 +4121,7 @@ pub fn emit_function_call( let gas = if let Some(gas) = &call_args.gas { expression(gas, cfg, caller_contract_no, func, ns, vartab, opt, target) } else { - default_gas(ns) + default_gas(ns, target) }; let value = if let Some(value) = &call_args.value { expression( @@ -4307,16 +4227,12 @@ pub fn emit_function_call( } } -pub fn default_gas(ns: &Namespace) -> Expression { +pub fn default_gas(_ns: &Namespace, target: &dyn TargetCodegen) -> Expression { Expression::NumberLiteral { loc: pt::Loc::Codegen, ty: Type::Uint(64), - // See EIP150 - value: if ns.target == Target::EVM { - BigInt::from(i64::MAX) - } else { - BigInt::zero() - }, + // See EIP-150; EVM uses i64::MAX, other targets use 0. + value: target.default_gas_builtin(), } } @@ -4367,20 +4283,7 @@ fn array_subscript( let array = expression(array, cfg, contract_no, func, ns, vartab, opt, target); let index = expression(index, cfg, contract_no, func, ns, vartab, opt, target); - return match ns.target { - Target::Solana | Target::Soroban | Target::EVM => Expression::Subscript { - loc: *loc, - ty: elem_ty.clone(), - array_ty: array_ty.clone(), - expr: Box::new(array), - index: Box::new(index), - }, - Target::Polkadot { .. } => Expression::Keccak256 { - loc: *loc, - ty: array_ty.clone(), - exprs: vec![array, index], - }, - }; + return target.lower_mapping_subscript(loc, elem_ty, array_ty, array, index); } let mut array = expression(array, cfg, contract_no, func, ns, vartab, opt, target); @@ -4421,7 +4324,7 @@ fn array_subscript( }; // TODO(Soroban): Storage type here is None, since arrays are not yet supported in Soroban let array_length = - load_storage(loc, &ty, array.clone(), cfg, vartab, None, ns); + load_storage(loc, &ty, array.clone(), cfg, vartab, None, ns, target); if ns.target != Target::Soroban { array = Expression::Keccak256 { loc: *loc, @@ -4819,6 +4722,7 @@ pub fn load_storage( vartab: &mut Vartable, storage_type: Option, ns: &Namespace, + target: &dyn TargetCodegen, ) -> Expression { let res = vartab.temp_anonymous(ty); @@ -4838,11 +4742,7 @@ pub fn load_storage( var_no: res, }; - if ns.target == Target::Soroban { - soroban_decode_arg(var, cfg, vartab, ns, None) - } else { - var - } + target.lower_load_storage(var, cfg, vartab, ns) } fn array_literal_to_memory_array( @@ -4963,7 +4863,7 @@ fn code(loc: &Loc, _contract_no: usize, _ns: &Namespace, _opt: &Options) -> Expr } } -fn add_prefix_and_delimiter_to_print(mut expr: Expression) -> Expression { +pub(crate) fn add_prefix_and_delimiter_to_print(mut expr: Expression) -> Expression { let prefix = b"print: "; let delimiter = b",\n"; diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index 258661415..a121e533d 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -3,11 +3,11 @@ use crate::codegen::cfg::ControlFlowGraph; use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; -use crate::sema::ast::{self, Function, Namespace, Type}; +use crate::sema::ast::{self, Function, Namespace, StructType, Type}; +use num_bigint::BigInt; +use num_traits::Zero; use solang_parser::pt::{self, Loc}; -/// The per-event emission strategy, produced by a target. Defined in -/// [`crate::codegen::events`]; re-exported here so the boundary is readable in one file. pub(crate) use crate::codegen::events::EventEmitter; pub(crate) trait TargetCodegen { @@ -29,8 +29,6 @@ pub(crate) trait TargetCodegen { /// Whole-program post-processing, called once after every contract's CFGs. fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {} - /// Hash algorithm used for function selector computation. - /// Keccak256 everywhere except Solana (Sha256). fn selector_hash_algorithm(&self) -> ast::Builtin { ast::Builtin::Keccak256 } @@ -41,6 +39,57 @@ pub(crate) trait TargetCodegen { false } + /// Starting offset for the first storage slot. Solana reserves the first 16 bytes for + /// account metadata; all other targets begin at slot 0. + fn initial_storage_slot(&self) -> BigInt { + BigInt::zero() + } + + fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt { + slot + } + + fn default_gas_builtin(&self) -> BigInt { + BigInt::zero() + } + + fn lower_print_expr(&self, expr: Expression) -> Expression { + expr + } + + fn lower_mapping_subscript( + &self, + loc: &Loc, + elem_ty: &Type, + array_ty: &Type, + array: Expression, + index: Expression, + ) -> Expression { + Expression::Subscript { + loc: *loc, + ty: elem_ty.clone(), + array_ty: array_ty.clone(), + expr: Box::new(array), + index: Box::new(index), + } + } + + /// Target-specific builtin lowering; `None` falls through to shared `expr_builtin`. + fn lower_builtin( + &self, + _loc: &Loc, + _builtin: ast::Builtin, + _args: &[ast::Expression], + _cfg: &mut ControlFlowGraph, + _contract_no: usize, + _func: Option<&Function>, + _ns: &Namespace, + _vartab: &mut Vartable, + _opt: &Options, + ) -> Option { + None + } + /// Optionally rewrite a freshly-built `Load` expression. /// Soroban decodes handles on load; other targets pass through unchanged. fn lower_load( @@ -78,9 +127,6 @@ pub(crate) trait TargetCodegen { None } - /// Encode `args` into the target's wire format, returning `(buffer, length)`. - /// The default drives the shared buffer encoder (Borsh / SCALE); Soroban encodes to - /// ScVal handles instead. fn abi_encode( &self, loc: &Loc, @@ -93,8 +139,6 @@ pub(crate) trait TargetCodegen { crate::codegen::encoding::abi_encode(loc, args, ns, vartab, cfg, packed) } - /// Decode `types` out of `buffer`. The default drives the shared buffer decoder - /// (Borsh / SCALE); Soroban decodes ScVal handles instead. fn abi_decode( &self, loc: &Loc, @@ -108,9 +152,6 @@ pub(crate) trait TargetCodegen { crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) } - /// Lower `arr.push(value)` on a contract-storage array. Each target owns its storage - /// representation (Solana flat slots, Polkadot hashed slots, Soroban host vectors), so - /// there is no shared default. fn storage_array_push( &self, loc: &Loc, @@ -123,7 +164,6 @@ pub(crate) trait TargetCodegen { opt: &Options, ) -> Expression; - /// Lower `arr.pop()` on a contract-storage array. See [`Self::storage_array_push`]. fn storage_array_pop( &self, loc: &Loc, @@ -137,8 +177,6 @@ pub(crate) trait TargetCodegen { opt: &Options, ) -> Expression; - /// Return the event emitter for the given event. The returned emitter borrows `args` and - /// `ns` so the lifetime must be threaded through. fn event_emitter<'a>( &self, loc: &pt::Loc, @@ -147,9 +185,6 @@ pub(crate) trait TargetCodegen { ns: &'a Namespace, ) -> Box; - /// Compute the storage slot of array element `index` for the hashed-slots push path. - /// The default derives it from `keccak256(array_slot)`; Soroban indexes its host vector - /// with an encoded key instead. fn storage_array_entry_offset( &self, loc: &Loc, @@ -173,4 +208,25 @@ pub(crate) trait TargetCodegen { ns, ) } + + fn lower_storage_struct_member( + &self, + loc: &Loc, + var_expr: Expression, + struct_ty: &StructType, + field_no: usize, + ns: &Namespace, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ) -> Expression; + + fn lower_load_storage( + &self, + value: Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + value + } } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 72b6db955..3cc031107 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -289,10 +289,10 @@ pub fn codegen(ns: &mut Namespace, opt: &Options) { fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options, target: &dyn TargetCodegen) { if !ns.diagnostics.any_errors() && ns.contracts[contract_no].instantiable { - layout(contract_no, ns); - + layout(contract_no, ns, target); + let errors_before = ns.diagnostics.count_errors(); target.validate_contract(contract_no, ns); - if ns.diagnostics.any_errors() { + if ns.diagnostics.count_errors() > errors_before { return; } @@ -354,8 +354,9 @@ fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options, target: &dyn ns.contracts[contract_no].default_constructor = Some((func, cfg_no)); } + let errors_before = ns.diagnostics.count_errors(); target.validate_cfgs(&all_cfg, ns); - if ns.diagnostics.any_errors() { + if ns.diagnostics.count_errors() > errors_before { return; } @@ -432,28 +433,14 @@ fn storage_initializer( } /// Layout the contract. We determine the layout of variables and deal with overriding variables -fn layout(contract_no: usize, ns: &mut Namespace) { - let mut slot = if ns.target == Target::Solana { - BigInt::from(SOLANA_FIRST_OFFSET) - } else { - BigInt::zero() - }; +fn layout(contract_no: usize, ns: &mut Namespace, target: &dyn TargetCodegen) { + let mut slot = target.initial_storage_slot(); for base_contract_no in ns.contract_bases(contract_no) { for var_no in 0..ns.contracts[base_contract_no].variables.len() { if !ns.contracts[base_contract_no].variables[var_no].constant { let ty = ns.contracts[base_contract_no].variables[var_no].ty.clone(); - - if ns.target == Target::Solana { - // elements need to be aligned on solana - let alignment = ty.align_of(ns); - - let offset = slot.clone() % alignment; - - if offset > BigInt::zero() { - slot += alignment - offset; - } - } + slot = target.align_storage_slot(slot, &ty, ns); ns.contracts[contract_no].layout.push(Layout { slot: slot.clone(), diff --git a/src/codegen/statements/try_catch.rs b/src/codegen/statements/try_catch.rs index 5d093a545..e8386f974 100644 --- a/src/codegen/statements/try_catch.rs +++ b/src/codegen/statements/try_catch.rs @@ -261,7 +261,7 @@ fn exec_try( target, ) } else { - default_gas(ns) + default_gas(ns, target) }; let function = expression( function, diff --git a/src/codegen/storage.rs b/src/codegen/storage.rs index b113df3be..2061c8552 100644 --- a/src/codegen/storage.rs +++ b/src/codegen/storage.rs @@ -101,7 +101,16 @@ pub fn storage_slots_array_push( let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); // TODO(Soroban): Storage type here is None, since arrays are not yet supported in Soroban - let expr = load_storage(loc, &slot_ty, var_expr.clone(), cfg, vartab, None, ns); + let expr = load_storage( + loc, + &slot_ty, + var_expr.clone(), + cfg, + vartab, + None, + ns, + target, + ); cfg.add( vartab, @@ -221,7 +230,16 @@ pub fn storage_slots_array_pop( let ty = args[0].ty(); let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); // TODO(Soroban): Storage type here is None, since arrays are not yet supported in Soroban - let expr = load_storage(loc, &length_ty, var_expr.clone(), cfg, vartab, None, ns); + let expr = load_storage( + loc, + &length_ty, + var_expr.clone(), + cfg, + vartab, + None, + ns, + target, + ); cfg.add( vartab, @@ -341,6 +359,7 @@ pub fn storage_slots_array_pop( vartab, None, ns, + target, ); cfg.add( diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index a206eacd7..6c9ce9e1c 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -14,8 +14,10 @@ use crate::codegen::storage::{ use crate::codegen::vartable::Vartable; use crate::codegen::{dispatch, Expression, Options}; use crate::sema::ast; -use crate::sema::ast::{Function, Namespace, RetrieveType, Type}; +use crate::sema::ast::{Function, Namespace, RetrieveType, StructType, Type}; use crate::Target; +use num_bigint::BigInt; +use num_traits::Zero; use solang_parser::pt::{self, Loc}; use self::soroban::SorobanTarget; @@ -24,13 +26,15 @@ pub(crate) fn make_target(ns: &Namespace) -> Box { match &ns.target { Target::Soroban => Box::new(SorobanTarget), Target::Solana => Box::new(SolanaTarget), - // EVM reuses the Polkadot codegen path — intentional, not a gap. - Target::Polkadot { .. } | Target::EVM => Box::new(PolkadotTarget), + Target::Polkadot { .. } => Box::new(PolkadotTarget { is_evm: false }), + Target::EVM => Box::new(PolkadotTarget { is_evm: true }), } } pub(crate) struct SolanaTarget; -pub(crate) struct PolkadotTarget; +pub(crate) struct PolkadotTarget { + pub(crate) is_evm: bool, +} impl TargetCodegen for SolanaTarget { fn function_dispatch( @@ -72,6 +76,19 @@ impl TargetCodegen for SolanaTarget { ast::Builtin::Sha256 } + fn initial_storage_slot(&self) -> BigInt { + BigInt::from(crate::codegen::SOLANA_FIRST_OFFSET) + } + + fn align_storage_slot(&self, mut slot: BigInt, ty: &Type, ns: &Namespace) -> BigInt { + let alignment = ty.align_of(ns); + let offset = slot.clone() % alignment; + if offset > BigInt::zero() { + slot += alignment - offset; + } + slot + } + fn storage_array_push( &self, loc: &Loc, @@ -120,7 +137,36 @@ impl TargetCodegen for SolanaTarget { args: &'a [ast::Expression], ns: &'a Namespace, ) -> Box { - Box::new(SolanaEventEmitter { loc: *loc, args, ns, event_no }) + Box::new(SolanaEventEmitter { + loc: *loc, + args, + ns, + event_no, + }) + } + + fn lower_storage_struct_member( + &self, + loc: &Loc, + var_expr: Expression, + struct_ty: &StructType, + field_no: usize, + ns: &Namespace, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + ) -> Expression { + let offset = struct_ty.definition(ns).storage_offsets[field_no].clone(); + Expression::Add { + loc: *loc, + ty: ns.storage_type(), + overflowing: true, + left: Box::new(var_expr), + right: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: ns.storage_type(), + value: offset, + }), + } } } @@ -204,4 +250,104 @@ impl TargetCodegen for PolkadotTarget { ) -> Box { Box::new(PolkadotEventEmitter { args, ns, event_no }) } + + fn default_gas_builtin(&self) -> BigInt { + if self.is_evm { + BigInt::from(i64::MAX) + } else { + BigInt::zero() + } + } + + fn lower_print_expr(&self, expr: Expression) -> Expression { + if self.is_evm { + expr + } else { + crate::codegen::expression::add_prefix_and_delimiter_to_print(expr) + } + } + + fn lower_mapping_subscript( + &self, + loc: &Loc, + elem_ty: &Type, + array_ty: &Type, + array: Expression, + index: Expression, + ) -> Expression { + if self.is_evm { + Expression::Subscript { + loc: *loc, + ty: elem_ty.clone(), + array_ty: array_ty.clone(), + expr: Box::new(array), + index: Box::new(index), + } + } else { + Expression::Keccak256 { + loc: *loc, + ty: array_ty.clone(), + exprs: vec![array, index], + } + } + } + + fn lower_builtin( + &self, + loc: &Loc, + builtin: ast::Builtin, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Option { + match builtin { + ast::Builtin::Gasprice if self.is_evm && args.len() == 1 => { + Some(crate::codegen::expression::builtin_evm_gasprice( + loc, + args, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + )) + } + _ => None, + } + } + + fn lower_storage_struct_member( + &self, + loc: &Loc, + var_expr: Expression, + struct_ty: &StructType, + field_no: usize, + ns: &Namespace, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + ) -> Expression { + // Polkadot/EVM lay struct fields out in consecutive storage slots. + let offset: BigInt = struct_ty.definition(ns).fields[..field_no] + .iter() + .filter(|field| !field.infinite_size) + .map(|field| field.ty.storage_slots(ns)) + .sum(); + Expression::Add { + loc: *loc, + ty: ns.storage_type(), + overflowing: true, + left: Box::new(var_expr), + right: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: ns.storage_type(), + value: offset, + }), + } + } } diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index 35193e4a8..f3a939e76 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -15,9 +15,10 @@ use crate::codegen::vartable::Vartable; use crate::codegen::Options; use crate::codegen::{Builtin, Expression, HostFunctions}; use crate::sema::ast; -use crate::sema::ast::{Function, Namespace, RetrieveType, Type}; +use crate::sema::ast::{Function, Namespace, RetrieveType, StructType, Type}; use crate::sema::Recurse; use crate::Target; +use num_bigint::{BigInt, Sign}; use solang_parser::helpers::CodeLocation; use solang_parser::{diagnostics::Diagnostic, pt}; use std::collections::BTreeSet; @@ -239,6 +240,101 @@ impl TargetCodegen for SorobanTarget { ) -> Box { Box::new(SorobanEventEmitter { args, ns, event_no }) } + + fn lower_builtin( + &self, + loc: &pt::Loc, + builtin: ast::Builtin, + _args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + _func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + _opt: &Options, + ) -> Option { + match builtin { + ast::Builtin::GetAddress => { + // program_id is a compile-time constant address (Solana/Soroban); return it directly. + if let Some(constant_id) = &ns.contracts[contract_no].program_id { + return Some(Expression::NumberLiteral { + loc: *loc, + ty: Type::Address(false), + value: BigInt::from_bytes_be(Sign::Plus, constant_id), + }); + } + let address_var_no = vartab.temp_anonymous(&Type::Uint(64)); + let address_var = Expression::Variable { + loc: *loc, + ty: Type::Address(false), + var_no: address_var_no, + }; + cfg.add( + vartab, + Instr::Call { + res: vec![address_var_no], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::GetCurrentContractAddress.name().to_string(), + }, + args: vec![], + }, + ); + Some(address_var) + } + _ => None, + } + } + + fn lower_storage_struct_member( + &self, + loc: &pt::Loc, + var_expr: Expression, + struct_ty: &StructType, + field_no: usize, + ns: &Namespace, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ) -> Expression { + let offset: BigInt = struct_ty.definition(ns).fields[..field_no] + .iter() + .filter(|field| !field.infinite_size) + .map(|field| field.ty.storage_slots(ns)) + .sum(); + let offset_expr = Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(32), + value: offset, + }; + let offset_encoded = soroban_encode_arg(offset_expr, cfg, vartab, ns); + let res = vartab.temp_name("vec_push_codegen", &Type::Uint(64)); + cfg.add( + vartab, + Instr::Call { + res: vec![res], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::VecPushBack.name().to_string(), + }, + args: vec![var_expr, offset_encoded], + }, + ); + Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + var_no: res, + } + } + + fn lower_load_storage( + &self, + value: Expression, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + soroban_decode_arg(value, cfg, vartab, ns, None) + } } pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) { @@ -867,7 +963,16 @@ pub(crate) fn soroban_storage_push( let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); let vec_ty = args[0].ty(); - let old_vec_obj = load_storage(loc, &vec_ty, var_expr.clone(), cfg, vartab, None, ns); + let old_vec_obj = load_storage( + loc, + &vec_ty, + var_expr.clone(), + cfg, + vartab, + None, + ns, + target, + ); let new_vec_var = soroban_vec_push_back(loc, old_vec_obj, &vec_ty, value, cfg, ns, vartab); // Storage wrapper: store updated vec object. @@ -899,7 +1004,16 @@ pub(crate) fn soroban_storage_pop( let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); let vec_ty = args[0].ty(); - let old_vec_obj = load_storage(loc, &vec_ty, var_expr.clone(), cfg, vartab, None, ns); + let old_vec_obj = load_storage( + loc, + &vec_ty, + var_expr.clone(), + cfg, + vartab, + None, + ns, + target, + ); let new_vec_var = soroban_vec_pop_back(loc, old_vec_obj, &vec_ty, cfg, vartab); let new_vec_no = match &new_vec_var { Expression::Variable { var_no, .. } => *var_no, diff --git a/src/sema/diagnostics.rs b/src/sema/diagnostics.rs index b47cda270..4f9231cd7 100644 --- a/src/sema/diagnostics.rs +++ b/src/sema/diagnostics.rs @@ -72,6 +72,13 @@ impl Diagnostics { .count() } + pub fn count_errors(&self) -> usize { + self.contents + .iter() + .filter(|&x| x.level == Level::Error) + .count() + } + pub fn first_warning(&self) -> &Diagnostic { self.contents .iter() From 57e4f94198240b8e1c163d51d07cba35475c789f Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Fri, 19 Jun 2026 19:17:42 +0300 Subject: [PATCH 09/15] codegen: route payable send/transfer through TargetCodegen::lower_builtin Signed-off-by: Islam-Imad --- src/codegen/expression.rs | 153 ------------------------------------ src/codegen/targets/mod.rs | 154 ++++++++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 156 deletions(-) diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index ff9d4cfd8..e248301c1 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -1319,18 +1319,6 @@ pub fn expression( args, .. } => self_destruct(args, cfg, contract_no, func, ns, vartab, opt, target), - ast::Expression::Builtin { - loc, - kind: ast::Builtin::PayableSend, - args, - .. - } => payable_send(args, cfg, contract_no, func, ns, vartab, loc, opt, target), - ast::Expression::Builtin { - loc, - kind: ast::Builtin::PayableTransfer, - args, - .. - } => payable_transfer(args, cfg, contract_no, func, ns, vartab, loc, opt, target), ast::Expression::Builtin { loc, kind: ast::Builtin::AbiEncode, @@ -1383,7 +1371,6 @@ pub fn expression( kind, args, } => { - // Target gets first refusal; EVM uses this for gasleft(amount), for example. if let Some(e) = target.lower_builtin(loc, *kind, args, cfg, contract_no, func, ns, vartab, opt) { @@ -2008,146 +1995,6 @@ fn self_destruct( Expression::Poison } -fn payable_send( - args: &[ast::Expression], - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - loc: &pt::Loc, - opt: &Options, - target: &dyn TargetCodegen, -) -> Expression { - let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); - let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); - let success = vartab.temp( - &pt::Identifier { - loc: *loc, - name: "success".to_owned(), - }, - &Type::Uint(32), - ); - - // Ethereum can only transfer via external call - if ns.target == Target::EVM { - cfg.add( - vartab, - Instr::ExternalCall { - loc: *loc, - success: Some(success), - address: Some(address), - accounts: ExternalCallAccounts::AbsentArgument, - seeds: None, - payload: Expression::AllocDynamicBytes { - loc: *loc, - ty: Type::DynamicBytes, - size: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(32), - value: BigInt::from(0), - }), - initializer: Some(vec![]), - }, - value, - gas: Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(64), - value: BigInt::from(i64::MAX), - }, - callty: CallTy::Regular, - contract_function_no: None, - flags: None, - }, - ); - return Expression::Variable { - loc: *loc, - ty: Type::Bool, - var_no: success, - }; - } - - cfg.add( - vartab, - Instr::ValueTransfer { - success: Some(success), - address, - value, - }, - ); - - if ns.target != Target::Solana { - polkadot::check_transfer_ret(loc, success, cfg, ns, opt, vartab, false).unwrap() - } else { - unreachable!("Value transfer does not exist on Solana"); - } -} - -fn payable_transfer( - args: &[ast::Expression], - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - loc: &pt::Loc, - opt: &Options, - target: &dyn TargetCodegen, -) -> Expression { - let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); - let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); - if ns.target == Target::EVM { - // Ethereum can only transfer via external call - cfg.add( - vartab, - Instr::ExternalCall { - loc: *loc, - success: None, - accounts: ExternalCallAccounts::AbsentArgument, - seeds: None, - address: Some(address), - payload: Expression::AllocDynamicBytes { - loc: *loc, - ty: Type::DynamicBytes, - size: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(32), - value: BigInt::from(0), - }), - initializer: Some(vec![]), - }, - value, - gas: Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(64), - value: BigInt::from(i64::MAX), - }, - callty: CallTy::Regular, - contract_function_no: None, - flags: None, - }, - ); - return Expression::Poison; - } - - let success = ns - .target - .is_polkadot() - .then(|| vartab.temp_name("success", &Type::Uint(32))); - let ins = Instr::ValueTransfer { - success, - address, - value, - }; - cfg.add(vartab, ins); - - if ns.target.is_polkadot() { - polkadot::check_transfer_ret(loc, success.unwrap(), cfg, ns, opt, vartab, true); - } - - Expression::Poison -} - fn abi_encode_many( args: &[ast::Expression], cfg: &mut ControlFlowGraph, diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index 6c9ce9e1c..06bf018bd 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -2,9 +2,10 @@ pub(crate) mod soroban; -use crate::codegen::cfg::ControlFlowGraph; +use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::events::polkadot::PolkadotEventEmitter; use crate::codegen::events::solana::SolanaEventEmitter; +use crate::codegen::expression::expression; use crate::codegen::interface::{EventEmitter, TargetCodegen}; use crate::codegen::solana_accounts::account_collection::collect_accounts_from_contract; use crate::codegen::solana_accounts::account_management::manage_contract_accounts; @@ -12,9 +13,11 @@ use crate::codegen::storage::{ array_pop, array_push, storage_slots_array_pop, storage_slots_array_push, }; use crate::codegen::vartable::Vartable; -use crate::codegen::{dispatch, Expression, Options}; +use crate::codegen::{dispatch, polkadot, Expression, Options}; use crate::sema::ast; -use crate::sema::ast::{Function, Namespace, RetrieveType, StructType, Type}; +use crate::sema::ast::{ + CallTy, ExternalCallAccounts, Function, Namespace, RetrieveType, StructType, Type, +}; use crate::Target; use num_bigint::BigInt; use num_traits::Zero; @@ -318,6 +321,12 @@ impl TargetCodegen for PolkadotTarget { self, )) } + ast::Builtin::PayableSend => { + Some(self.payable_send(loc, args, cfg, contract_no, func, ns, vartab, opt)) + } + ast::Builtin::PayableTransfer => { + Some(self.payable_transfer(loc, args, cfg, contract_no, func, ns, vartab, opt)) + } _ => None, } } @@ -351,3 +360,142 @@ impl TargetCodegen for PolkadotTarget { } } } + +impl PolkadotTarget { + /// Lower `address.send(value)` (`Builtin::PayableSend`). EVM routes through an external + /// call with an empty payload; Polkadot emits a `ValueTransfer` and checks the return code. + fn payable_send( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); + let success = vartab.temp( + &pt::Identifier { + loc: *loc, + name: "success".to_owned(), + }, + &Type::Uint(32), + ); + + // Ethereum can only transfer via external call + if self.is_evm { + cfg.add( + vartab, + Instr::ExternalCall { + loc: *loc, + success: Some(success), + address: Some(address), + accounts: ExternalCallAccounts::AbsentArgument, + seeds: None, + payload: Expression::AllocDynamicBytes { + loc: *loc, + ty: Type::DynamicBytes, + size: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(32), + value: BigInt::from(0), + }), + initializer: Some(vec![]), + }, + value, + gas: Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(64), + value: BigInt::from(i64::MAX), + }, + callty: CallTy::Regular, + contract_function_no: None, + flags: None, + }, + ); + return Expression::Variable { + loc: *loc, + ty: Type::Bool, + var_no: success, + }; + } + + cfg.add( + vartab, + Instr::ValueTransfer { + success: Some(success), + address, + value, + }, + ); + + polkadot::check_transfer_ret(loc, success, cfg, ns, opt, vartab, false).unwrap() + } + + /// Lower `address.transfer(value)` (`Builtin::PayableTransfer`). EVM routes through an + /// external call; Polkadot emits a `ValueTransfer` and reverts on a non-zero return code. + fn payable_transfer( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); + if self.is_evm { + // Ethereum can only transfer via external call + cfg.add( + vartab, + Instr::ExternalCall { + loc: *loc, + success: None, + accounts: ExternalCallAccounts::AbsentArgument, + seeds: None, + address: Some(address), + payload: Expression::AllocDynamicBytes { + loc: *loc, + ty: Type::DynamicBytes, + size: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(32), + value: BigInt::from(0), + }), + initializer: Some(vec![]), + }, + value, + gas: Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(64), + value: BigInt::from(i64::MAX), + }, + callty: CallTy::Regular, + contract_function_no: None, + flags: None, + }, + ); + return Expression::Poison; + } + + let success = vartab.temp_name("success", &Type::Uint(32)); + cfg.add( + vartab, + Instr::ValueTransfer { + success: Some(success), + address, + value, + }, + ); + + polkadot::check_transfer_ret(loc, success, cfg, ns, opt, vartab, true); + + Expression::Poison + } +} From ee5f45a8c625dc7f965141fb605ff114b6fea7f5 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Fri, 19 Jun 2026 21:28:58 +0300 Subject: [PATCH 10/15] codegen: relocate Solana sources into targets/solana/ Signed-off-by: Islam-Imad --- src/codegen/dispatch/mod.rs | 1 - src/codegen/encoding/buffer_validator.rs | 18 +- src/codegen/encoding/mod.rs | 12 +- src/codegen/events/mod.rs | 1 - src/codegen/mod.rs | 2 - src/codegen/targets/mod.rs | 140 +--------------- .../solana/accounts}/account_collection.rs | 2 +- .../solana/accounts}/account_management.rs | 2 +- .../solana/accounts}/mod.rs | 0 .../solana/deploy.rs} | 13 +- .../solana.rs => targets/solana/dispatch.rs} | 2 +- .../solana/encoding.rs} | 4 +- .../solana.rs => targets/solana/events.rs} | 0 src/codegen/targets/solana/mod.rs | 156 ++++++++++++++++++ 14 files changed, 185 insertions(+), 168 deletions(-) rename src/codegen/{solana_accounts => targets/solana/accounts}/account_collection.rs (99%) rename src/codegen/{solana_accounts => targets/solana/accounts}/account_management.rs (99%) rename src/codegen/{solana_accounts => targets/solana/accounts}/mod.rs (100%) rename src/codegen/{solana_deploy.rs => targets/solana/deploy.rs} (98%) rename src/codegen/{dispatch/solana.rs => targets/solana/dispatch.rs} (99%) rename src/codegen/{encoding/borsh_encoding.rs => targets/solana/encoding.rs} (98%) rename src/codegen/{events/solana.rs => targets/solana/events.rs} (100%) create mode 100644 src/codegen/targets/solana/mod.rs diff --git a/src/codegen/dispatch/mod.rs b/src/codegen/dispatch/mod.rs index ff9c29d40..5807663e2 100644 --- a/src/codegen/dispatch/mod.rs +++ b/src/codegen/dispatch/mod.rs @@ -1,4 +1,3 @@ // SPDX-License-Identifier: Apache-2.0 pub(crate) mod polkadot; -pub(super) mod solana; diff --git a/src/codegen/encoding/buffer_validator.rs b/src/codegen/encoding/buffer_validator.rs index 8eafe4917..7bb6489a5 100644 --- a/src/codegen/encoding/buffer_validator.rs +++ b/src/codegen/encoding/buffer_validator.rs @@ -39,12 +39,12 @@ impl BufferValidator<'_> { } /// Set which item we are currently reading from the buffer - pub(super) fn set_argument_number(&mut self, arg_no: usize) { + pub(crate) fn set_argument_number(&mut self, arg_no: usize) { self.current_arg = arg_no; } /// Initialize the validator, by verifying every type that has a fixed size - pub(super) fn initialize_validation( + pub(crate) fn initialize_validation( &mut self, offset: &Expression, ns: &Namespace, @@ -57,7 +57,7 @@ impl BufferValidator<'_> { } /// Validate the buffer for the current argument, if necessary. - pub(super) fn validate_buffer( + pub(crate) fn validate_buffer( &mut self, offset: &Expression, ns: &Namespace, @@ -73,7 +73,7 @@ impl BufferValidator<'_> { } /// Validate if a given offset is within the buffer's bound. - pub(super) fn validate_offset( + pub(crate) fn validate_offset( &self, offset: Expression, ns: &Namespace, @@ -84,17 +84,17 @@ impl BufferValidator<'_> { } /// Checks if a buffer validation is necessary - pub(super) fn validation_necessary(&self) -> bool { + pub(crate) fn validation_necessary(&self) -> bool { self.verified_until.is_none() || self.current_arg > self.verified_until.unwrap() } /// After an array validation, we do not need to re-check its elements. - pub(super) fn validate_array(&mut self) { + pub(crate) fn validate_array(&mut self) { self.verified_until = Some(self.current_arg); } /// Validate if offset + size is within the buffer's boundaries - pub(super) fn validate_offset_plus_size( + pub(crate) fn validate_offset_plus_size( &mut self, offset: &Expression, size: &Expression, @@ -115,7 +115,7 @@ impl BufferValidator<'_> { } /// Validates if we have read all the bytes in a buffer - pub(super) fn validate_all_bytes_read( + pub(crate) fn validate_all_bytes_read( &self, end_offset: Expression, ns: &Namespace, @@ -229,7 +229,7 @@ impl BufferValidator<'_> { } /// Create a new buffer validator to validate struct fields. - pub(super) fn create_sub_validator<'a>(&self, types: &'a [Type]) -> BufferValidator<'a> { + pub(crate) fn create_sub_validator<'a>(&self, types: &'a [Type]) -> BufferValidator<'a> { // If the struct has been previously validated, there is no need to validate it again, // so verified_until and current_arg are set to type.len() to avoid any further validation. BufferValidator { diff --git a/src/codegen/encoding/mod.rs b/src/codegen/encoding/mod.rs index c229e2489..ce9b04d78 100644 --- a/src/codegen/encoding/mod.rs +++ b/src/codegen/encoding/mod.rs @@ -8,16 +8,15 @@ /// - `AbiEncoding` defines the encoding and decoding API and must be implemented by all schemes. /// - There are some helper functions to work with more complex types. /// Any such helper function should work fine regardless of the encoding scheme being used. -mod borsh_encoding; -mod buffer_validator; +pub(crate) mod buffer_validator; pub(super) mod scale_encoding; use crate::codegen::cfg::{ControlFlowGraph, Instr}; -use crate::codegen::encoding::borsh_encoding::BorshEncoding; use crate::codegen::encoding::scale_encoding::ScaleEncoding; use crate::codegen::expression::load_storage; use crate::codegen::interface::TargetCodegen; use crate::codegen::targets::make_target; +use crate::codegen::targets::solana::encoding::BorshEncoding; use crate::codegen::vartable::Vartable; use crate::codegen::{Builtin, Expression}; use crate::sema::ast::{ArrayLength, Namespace, RetrieveType, StructType, Type, Type::Uint}; @@ -1424,7 +1423,8 @@ pub(crate) trait AbiEncoding { } Type::Ref(r) => { if let Type::Struct(struct_ty) = &**r { - return self.calculate_struct_size(arg_no, expr, struct_ty, ns, vartab, cfg, target); + return self + .calculate_struct_size(arg_no, expr, struct_ty, ns, vartab, cfg, target); } let loaded = Expression::Load { loc: Codegen, @@ -1715,7 +1715,9 @@ pub(crate) trait AbiEncoding { for i in 1..struct_ty.definition(ns).fields.len() { let ty = struct_ty.definition(ns).fields[i].ty.clone(); let field = load_struct_member(ty.clone(), expr.clone(), i, ns); - let expr_size = self.get_expr_size(arg_no, &field, ns, vartab, cfg, target).into(); + let expr_size = self + .get_expr_size(arg_no, &field, ns, vartab, cfg, target) + .into(); size = Expression::Add { loc: Codegen, ty: Uint(32), diff --git a/src/codegen/events/mod.rs b/src/codegen/events/mod.rs index 88f28b074..f2e0a54a9 100644 --- a/src/codegen/events/mod.rs +++ b/src/codegen/events/mod.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pub(crate) mod polkadot; -pub(crate) mod solana; use crate::codegen::cfg::ControlFlowGraph; use crate::codegen::interface::TargetCodegen; diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 3cc031107..662365e7e 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -14,8 +14,6 @@ mod interface; pub(super) mod polkadot; mod reaching_definitions; pub mod revert; -mod solana_accounts; -mod solana_deploy; mod statements; mod storage; mod strength_reduce; diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index 06bf018bd..6256b398f 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -1,14 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 +pub(crate) mod solana; pub(crate) mod soroban; use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::events::polkadot::PolkadotEventEmitter; -use crate::codegen::events::solana::SolanaEventEmitter; use crate::codegen::expression::expression; use crate::codegen::interface::{EventEmitter, TargetCodegen}; -use crate::codegen::solana_accounts::account_collection::collect_accounts_from_contract; -use crate::codegen::solana_accounts::account_management::manage_contract_accounts; use crate::codegen::storage::{ array_pop, array_push, storage_slots_array_pop, storage_slots_array_push, }; @@ -23,6 +21,7 @@ use num_bigint::BigInt; use num_traits::Zero; use solang_parser::pt::{self, Loc}; +use self::solana::SolanaTarget; use self::soroban::SorobanTarget; pub(crate) fn make_target(ns: &Namespace) -> Box { @@ -34,145 +33,10 @@ pub(crate) fn make_target(ns: &Namespace) -> Box { } } -pub(crate) struct SolanaTarget; pub(crate) struct PolkadotTarget { pub(crate) is_evm: bool, } -impl TargetCodegen for SolanaTarget { - fn function_dispatch( - &self, - contract_no: usize, - all_cfg: &mut [ControlFlowGraph], - ns: &mut Namespace, - opt: &Options, - ) -> Vec { - vec![dispatch::solana::function_dispatch( - contract_no, - all_cfg, - ns, - opt, - self, - )] - } - - fn post_process_program(&self, ns: &mut Namespace, _opt: &Options) { - for contract_no in 0..ns.contracts.len() { - if ns.contracts[contract_no].instantiable { - let diag = collect_accounts_from_contract(contract_no, ns); - ns.diagnostics.extend(diag); - } - } - - for contract_no in 0..ns.contracts.len() { - if ns.contracts[contract_no].instantiable { - manage_contract_accounts(contract_no, ns); - } - } - } - - fn storage_array_length_is_inline(&self) -> bool { - true - } - - fn selector_hash_algorithm(&self) -> ast::Builtin { - ast::Builtin::Sha256 - } - - fn initial_storage_slot(&self) -> BigInt { - BigInt::from(crate::codegen::SOLANA_FIRST_OFFSET) - } - - fn align_storage_slot(&self, mut slot: BigInt, ty: &Type, ns: &Namespace) -> BigInt { - let alignment = ty.align_of(ns); - let offset = slot.clone() % alignment; - if offset > BigInt::zero() { - slot += alignment - offset; - } - slot - } - - fn storage_array_push( - &self, - loc: &Loc, - args: &[ast::Expression], - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - opt: &Options, - ) -> Expression { - // Solana stores dynamic arrays as flat slots. - array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) - } - - fn storage_array_pop( - &self, - loc: &Loc, - args: &[ast::Expression], - return_ty: &Type, - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - opt: &Options, - ) -> Expression { - array_pop( - loc, - args, - return_ty, - cfg, - contract_no, - func, - ns, - vartab, - opt, - self, - ) - } - - fn event_emitter<'a>( - &self, - loc: &pt::Loc, - event_no: usize, - args: &'a [ast::Expression], - ns: &'a Namespace, - ) -> Box { - Box::new(SolanaEventEmitter { - loc: *loc, - args, - ns, - event_no, - }) - } - - fn lower_storage_struct_member( - &self, - loc: &Loc, - var_expr: Expression, - struct_ty: &StructType, - field_no: usize, - ns: &Namespace, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - ) -> Expression { - let offset = struct_ty.definition(ns).storage_offsets[field_no].clone(); - Expression::Add { - loc: *loc, - ty: ns.storage_type(), - overflowing: true, - left: Box::new(var_expr), - right: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: ns.storage_type(), - value: offset, - }), - } - } -} - impl TargetCodegen for PolkadotTarget { fn function_dispatch( &self, diff --git a/src/codegen/solana_accounts/account_collection.rs b/src/codegen/targets/solana/accounts/account_collection.rs similarity index 99% rename from src/codegen/solana_accounts/account_collection.rs rename to src/codegen/targets/solana/accounts/account_collection.rs index d8a3f07d1..3c4be4f66 100644 --- a/src/codegen/solana_accounts/account_collection.rs +++ b/src/codegen/targets/solana/accounts/account_collection.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 +use super::account_from_number; use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}; -use crate::codegen::solana_accounts::account_from_number; use crate::codegen::{Builtin, Expression}; use crate::sema::ast::{Contract, ExternalCallAccounts, Function, Namespace, SolanaAccount}; use crate::sema::diagnostics::Diagnostics; diff --git a/src/codegen/solana_accounts/account_management.rs b/src/codegen/targets/solana/accounts/account_management.rs similarity index 99% rename from src/codegen/solana_accounts/account_management.rs rename to src/codegen/targets/solana/accounts/account_management.rs index 654e9c0c8..7db995fb5 100644 --- a/src/codegen/solana_accounts/account_management.rs +++ b/src/codegen/targets/solana/accounts/account_management.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::codegen::cfg::Instr; -use crate::codegen::dispatch::solana::SOLANA_DISPATCH_CFG_NAME; +use crate::codegen::targets::solana::dispatch::SOLANA_DISPATCH_CFG_NAME; use crate::codegen::{Builtin, Expression}; use crate::sema::ast::{ ArrayLength, Contract, ExternalCallAccounts, Function, Namespace, StructType, Type, diff --git a/src/codegen/solana_accounts/mod.rs b/src/codegen/targets/solana/accounts/mod.rs similarity index 100% rename from src/codegen/solana_accounts/mod.rs rename to src/codegen/targets/solana/accounts/mod.rs diff --git a/src/codegen/solana_deploy.rs b/src/codegen/targets/solana/deploy.rs similarity index 98% rename from src/codegen/solana_deploy.rs rename to src/codegen/targets/solana/deploy.rs index ada4bbefb..b4f246936 100644 --- a/src/codegen/solana_deploy.rs +++ b/src/codegen/targets/solana/deploy.rs @@ -1,14 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 -use super::{ - cfg::ReturnCode, expression, Builtin, ControlFlowGraph, Expression, Instr, Options, Type, - Vartable, -}; +use super::accounts::account_management::{account_meta_literal, retrieve_key_from_account_info}; +use crate::codegen::cfg::{ControlFlowGraph, Instr, ReturnCode}; +use crate::codegen::expression::expression; use crate::codegen::interface::TargetCodegen; use crate::codegen::revert::string_to_expr; -use crate::codegen::solana_accounts::account_management::{ - account_meta_literal, retrieve_key_from_account_info, -}; +use crate::codegen::vartable::Vartable; +use crate::codegen::{Builtin, Expression, Options}; +use crate::sema::ast::Type; use crate::sema::ast::{ self, ArrayLength, CallTy, ExternalCallAccounts, Function, FunctionAttributes, Namespace, StructType, diff --git a/src/codegen/dispatch/solana.rs b/src/codegen/targets/solana/dispatch.rs similarity index 99% rename from src/codegen/dispatch/solana.rs rename to src/codegen/targets/solana/dispatch.rs index 0b3245061..2423ee471 100644 --- a/src/codegen/dispatch/solana.rs +++ b/src/codegen/targets/solana/dispatch.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 +use super::deploy::solana_deploy; use crate::codegen::{ cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy, ReturnCode}, interface::TargetCodegen, - solana_deploy::solana_deploy, vartable::Vartable, Builtin, Expression, Options, }; diff --git a/src/codegen/encoding/borsh_encoding.rs b/src/codegen/targets/solana/encoding.rs similarity index 98% rename from src/codegen/encoding/borsh_encoding.rs rename to src/codegen/targets/solana/encoding.rs index 5d2836a02..0b83425fd 100644 --- a/src/codegen/encoding/borsh_encoding.rs +++ b/src/codegen/targets/solana/encoding.rs @@ -11,10 +11,10 @@ use solang_parser::pt::Loc::Codegen; use std::collections::HashMap; use std::ops::AddAssign; -use super::buffer_validator::BufferValidator; +use crate::codegen::encoding::buffer_validator::BufferValidator; /// This struct implements the trait AbiEncoding for Borsh encoding -pub(super) struct BorshEncoding { +pub(crate) struct BorshEncoding { storage_cache: HashMap, /// Are we packed encoding? packed_encoder: bool, diff --git a/src/codegen/events/solana.rs b/src/codegen/targets/solana/events.rs similarity index 100% rename from src/codegen/events/solana.rs rename to src/codegen/targets/solana/events.rs diff --git a/src/codegen/targets/solana/mod.rs b/src/codegen/targets/solana/mod.rs new file mode 100644 index 000000000..538f55044 --- /dev/null +++ b/src/codegen/targets/solana/mod.rs @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod accounts; +pub(super) mod deploy; +pub(crate) mod dispatch; +pub(crate) mod encoding; +mod events; + +use self::accounts::account_collection::collect_accounts_from_contract; +use self::accounts::account_management::manage_contract_accounts; +use self::events::SolanaEventEmitter; +use crate::codegen::cfg::ControlFlowGraph; +use crate::codegen::interface::{EventEmitter, TargetCodegen}; +use crate::codegen::storage::{array_pop, array_push}; +use crate::codegen::vartable::Vartable; +use crate::codegen::{Expression, Options}; +use crate::sema::ast::{self, Function, Namespace, StructType, Type}; +use num_bigint::BigInt; +use num_traits::Zero; +use solang_parser::pt::{self, Loc}; + +pub(crate) struct SolanaTarget; + +impl TargetCodegen for SolanaTarget { + fn function_dispatch( + &self, + contract_no: usize, + all_cfg: &mut [ControlFlowGraph], + ns: &mut Namespace, + opt: &Options, + ) -> Vec { + vec![dispatch::function_dispatch( + contract_no, + all_cfg, + ns, + opt, + self, + )] + } + + fn post_process_program(&self, ns: &mut Namespace, _opt: &Options) { + for contract_no in 0..ns.contracts.len() { + if ns.contracts[contract_no].instantiable { + let diag = collect_accounts_from_contract(contract_no, ns); + ns.diagnostics.extend(diag); + } + } + + for contract_no in 0..ns.contracts.len() { + if ns.contracts[contract_no].instantiable { + manage_contract_accounts(contract_no, ns); + } + } + } + + fn storage_array_length_is_inline(&self) -> bool { + true + } + + fn selector_hash_algorithm(&self) -> ast::Builtin { + ast::Builtin::Sha256 + } + + fn initial_storage_slot(&self) -> BigInt { + BigInt::from(crate::codegen::SOLANA_FIRST_OFFSET) + } + + fn align_storage_slot(&self, mut slot: BigInt, ty: &Type, ns: &Namespace) -> BigInt { + let alignment = ty.align_of(ns); + let offset = slot.clone() % alignment; + if offset > BigInt::zero() { + slot += alignment - offset; + } + slot + } + + fn storage_array_push( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + // Solana stores dynamic arrays as flat slots. + array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) + } + + fn storage_array_pop( + &self, + loc: &Loc, + args: &[ast::Expression], + return_ty: &Type, + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + array_pop( + loc, + args, + return_ty, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + ) + } + + fn event_emitter<'a>( + &self, + loc: &pt::Loc, + event_no: usize, + args: &'a [ast::Expression], + ns: &'a Namespace, + ) -> Box { + Box::new(SolanaEventEmitter { + loc: *loc, + args, + ns, + event_no, + }) + } + + fn lower_storage_struct_member( + &self, + loc: &Loc, + var_expr: Expression, + struct_ty: &StructType, + field_no: usize, + ns: &Namespace, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + ) -> Expression { + let offset = struct_ty.definition(ns).storage_offsets[field_no].clone(); + Expression::Add { + loc: *loc, + ty: ns.storage_type(), + overflowing: true, + left: Box::new(var_expr), + right: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: ns.storage_type(), + value: offset, + }), + } + } +} From c76b75cec47f18f99a3e2c3cbf90d9ac07d7a311 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Sat, 20 Jun 2026 10:45:47 +0300 Subject: [PATCH 11/15] codegen: relocate Polkadot, flatten encoding/events/statements/optimize; seal TargetCodegen Signed-off-by: Islam-Imad --- src/abi/polkadot.rs | 2 +- src/codegen/dispatch/mod.rs | 3 - src/codegen/events/mod.rs | 32 -- src/codegen/expression.rs | 3 +- src/codegen/interface.rs | 97 ++-- src/codegen/mod.rs | 28 +- src/codegen/{ => optimize}/array_boundary.rs | 2 +- .../{ => optimize}/constant_folding.rs | 4 +- src/codegen/{ => optimize}/dead_storage.rs | 2 +- src/codegen/optimize/mod.rs | 11 + .../{ => optimize}/reaching_definitions.rs | 2 +- .../strength_reduce/expression_values.rs | 0 .../{ => optimize}/strength_reduce/mod.rs | 2 +- .../strength_reduce/reaching_values.rs | 0 .../{ => optimize}/strength_reduce/tests.rs | 2 +- .../{ => optimize}/strength_reduce/value.rs | 0 .../anticipated_expressions.rs | 0 .../available_expression.rs | 0 .../available_expression_set.rs | 0 .../available_variable.rs | 0 .../common_subexpression_tracker.rs | 0 .../subexpression_elimination/expression.rs | 0 .../subexpression_elimination/instruction.rs | 0 .../subexpression_elimination/mod.rs | 0 .../subexpression_elimination/operator.rs | 0 .../subexpression_elimination/tests.rs | 0 .../{ => optimize}/undefined_variable.rs | 0 src/codegen/{ => optimize}/unused_variable.rs | 2 +- src/codegen/{ => optimize}/vector_to_slice.rs | 4 +- .../{statements/mod.rs => statements.rs} | 32 +- .../{encoding/mod.rs => targets/abi.rs} | 9 +- .../{encoding => targets}/buffer_validator.rs | 0 src/codegen/targets/mod.rs | 353 +------------ .../polkadot/dispatch.rs} | 0 .../polkadot/encoding.rs} | 9 +- .../polkadot/events.rs} | 2 +- src/codegen/targets/polkadot/mod.rs | 469 ++++++++++++++++++ .../polkadot/return_code.rs} | 2 +- .../polkadot}/try_catch.rs | 8 +- src/codegen/targets/solana/events.rs | 2 +- src/codegen/targets/solana/mod.rs | 135 +++++ src/codegen/targets/soroban/events.rs | 2 +- src/codegen/targets/soroban/mod.rs | 40 ++ src/emit/polkadot/mod.rs | 4 +- src/emit/polkadot/target.rs | 2 +- 45 files changed, 752 insertions(+), 513 deletions(-) delete mode 100644 src/codegen/dispatch/mod.rs delete mode 100644 src/codegen/events/mod.rs rename src/codegen/{ => optimize}/array_boundary.rs (98%) rename src/codegen/{ => optimize}/constant_folding.rs (99%) rename src/codegen/{ => optimize}/dead_storage.rs (99%) create mode 100644 src/codegen/optimize/mod.rs rename src/codegen/{ => optimize}/reaching_definitions.rs (99%) rename src/codegen/{ => optimize}/strength_reduce/expression_values.rs (100%) rename src/codegen/{ => optimize}/strength_reduce/mod.rs (99%) rename src/codegen/{ => optimize}/strength_reduce/reaching_values.rs (100%) rename src/codegen/{ => optimize}/strength_reduce/tests.rs (99%) rename src/codegen/{ => optimize}/strength_reduce/value.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/anticipated_expressions.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/available_expression.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/available_expression_set.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/available_variable.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/common_subexpression_tracker.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/expression.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/instruction.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/mod.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/operator.rs (100%) rename src/codegen/{ => optimize}/subexpression_elimination/tests.rs (100%) rename src/codegen/{ => optimize}/undefined_variable.rs (100%) rename src/codegen/{ => optimize}/unused_variable.rs (99%) rename src/codegen/{ => optimize}/vector_to_slice.rs (98%) rename src/codegen/{statements/mod.rs => statements.rs} (98%) rename src/codegen/{encoding/mod.rs => targets/abi.rs} (99%) rename src/codegen/{encoding => targets}/buffer_validator.rs (100%) rename src/codegen/{dispatch/polkadot.rs => targets/polkadot/dispatch.rs} (100%) rename src/codegen/{encoding/scale_encoding.rs => targets/polkadot/encoding.rs} (98%) rename src/codegen/{events/polkadot.rs => targets/polkadot/events.rs} (99%) create mode 100644 src/codegen/targets/polkadot/mod.rs rename src/codegen/{polkadot.rs => targets/polkadot/return_code.rs} (99%) rename src/codegen/{statements => targets/polkadot}/try_catch.rs (99%) diff --git a/src/abi/polkadot.rs b/src/abi/polkadot.rs index 30eb6bf8f..b8543399b 100644 --- a/src/abi/polkadot.rs +++ b/src/abi/polkadot.rs @@ -22,8 +22,8 @@ use semver::Version; use solang_parser::pt; use crate::{ - codegen::polkadot::SCRATCH_SIZE, codegen::revert::{SolidityError, ERROR_SELECTOR, PANIC_SELECTOR}, + codegen::targets::polkadot::return_code::SCRATCH_SIZE, sema::{ ast::{self, ArrayLength, EventDecl, Function}, tags::render, diff --git a/src/codegen/dispatch/mod.rs b/src/codegen/dispatch/mod.rs deleted file mode 100644 index 5807663e2..000000000 --- a/src/codegen/dispatch/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pub(crate) mod polkadot; diff --git a/src/codegen/events/mod.rs b/src/codegen/events/mod.rs deleted file mode 100644 index f2e0a54a9..000000000 --- a/src/codegen/events/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pub(crate) mod polkadot; - -use crate::codegen::cfg::ControlFlowGraph; -use crate::codegen::interface::TargetCodegen; -use crate::codegen::vartable::Vartable; -use crate::codegen::Options; -use crate::sema::ast::Function; - -/// This traits delineates the common behavior of event emission. As each target uses a different -/// encoding scheme, there must be an implementation of this trait for each. -/// -/// Re-exported from [`crate::codegen::interface`] as part of the target-codegen boundary; it is -/// `pub(crate)` so it can be named there. -pub(crate) trait EventEmitter { - /// Generate the CFG instructions for emitting an event. - /// All necessary code analysis should have been done during parsing and 'sema'; - /// If code generation does not work here, there is a bug in the compiler. - fn emit( - &self, - contract_no: usize, - func: &Function, - cfg: &mut ControlFlowGraph, - vartab: &mut Vartable, - opt: &Options, - target: &dyn TargetCodegen, - ); - - /// Generates the selector - fn selector(&self, emitting_contract_no: usize) -> Vec; -} diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index e248301c1..64ed734d3 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -5,14 +5,15 @@ use super::revert::{ }; use super::storage::array_offset; use super::targets::soroban::encoding::{soroban_encode, soroban_encode_arg}; +use super::Options; use super::{ cfg::{ControlFlowGraph, Instr, InternalCallTy}, vartable::Vartable, }; -use super::{polkadot, Options}; use crate::codegen::array_boundary::handle_array_assign; use crate::codegen::constructor::call_constructor; use crate::codegen::interface::TargetCodegen; +use crate::codegen::targets::polkadot::return_code as polkadot; use crate::codegen::unused_variable::should_remove_assignment; use crate::codegen::{Builtin, Expression, HostFunctions}; use crate::sema::ast::ExternalCallAccounts; diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index a121e533d..9051d39e3 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -5,17 +5,28 @@ use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; use crate::sema::ast::{self, Function, Namespace, StructType, Type}; use num_bigint::BigInt; -use num_traits::Zero; use solang_parser::pt::{self, Loc}; -pub(crate) use crate::codegen::events::EventEmitter; +pub(crate) trait EventEmitter { + fn emit( + &self, + contract_no: usize, + func: &crate::sema::ast::Function, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + opt: &Options, + target: &dyn TargetCodegen, + ); + + fn selector(&self, emitting_contract_no: usize) -> Vec; +} pub(crate) trait TargetCodegen { /// Pre-CFG validation. Runs after storage layout, before any CFG is built. - fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace) {} + fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace); /// Post-CFG validation; needs the freshly built CFGs. - fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace) {} + fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace); /// Build the dispatcher CFG(s) appended after every function CFG is generated. fn function_dispatch( @@ -27,35 +38,23 @@ pub(crate) trait TargetCodegen { ) -> Vec; /// Whole-program post-processing, called once after every contract's CFGs. - fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {} + fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options); - fn selector_hash_algorithm(&self) -> ast::Builtin { - ast::Builtin::Keccak256 - } + fn selector_hash_algorithm(&self) -> ast::Builtin; /// Whether dynamic storage arrays store their length inline in the value (Solana/Soroban) /// or in a separate storage slot (Polkadot). - fn storage_array_length_is_inline(&self) -> bool { - false - } + fn storage_array_length_is_inline(&self) -> bool; /// Starting offset for the first storage slot. Solana reserves the first 16 bytes for /// account metadata; all other targets begin at slot 0. - fn initial_storage_slot(&self) -> BigInt { - BigInt::zero() - } + fn initial_storage_slot(&self) -> BigInt; - fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt { - slot - } + fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt; - fn default_gas_builtin(&self) -> BigInt { - BigInt::zero() - } + fn default_gas_builtin(&self) -> BigInt; - fn lower_print_expr(&self, expr: Expression) -> Expression { - expr - } + fn lower_print_expr(&self, expr: Expression) -> Expression; fn lower_mapping_subscript( &self, @@ -64,15 +63,7 @@ pub(crate) trait TargetCodegen { array_ty: &Type, array: Expression, index: Expression, - ) -> Expression { - Expression::Subscript { - loc: *loc, - ty: elem_ty.clone(), - array_ty: array_ty.clone(), - expr: Box::new(array), - index: Box::new(index), - } - } + ) -> Expression; /// Target-specific builtin lowering; `None` falls through to shared `expr_builtin`. fn lower_builtin( @@ -86,9 +77,7 @@ pub(crate) trait TargetCodegen { _ns: &Namespace, _vartab: &mut Vartable, _opt: &Options, - ) -> Option { - None - } + ) -> Option; /// Optionally rewrite a freshly-built `Load` expression. /// Soroban decodes handles on load; other targets pass through unchanged. @@ -98,9 +87,7 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, _ns: &Namespace, - ) -> Expression { - load - } + ) -> Expression; /// Transform a value just before it is written to storage or a storage-backed ref. /// Soroban encodes values to ScVal handles; other targets pass through unchanged. @@ -111,9 +98,7 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, _ns: &Namespace, - ) -> Expression { - value - } + ) -> Expression; /// Default value for an uninitialised storage variable; `None` means "skip the variable". fn default_storage_value( @@ -123,9 +108,7 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, _ns: &Namespace, - ) -> Option { - None - } + ) -> Option; fn abi_encode( &self, @@ -135,9 +118,7 @@ pub(crate) trait TargetCodegen { vartab: &mut Vartable, cfg: &mut ControlFlowGraph, packed: bool, - ) -> (Expression, Expression) { - crate::codegen::encoding::abi_encode(loc, args, ns, vartab, cfg, packed) - } + ) -> (Expression, Expression); fn abi_decode( &self, @@ -148,9 +129,7 @@ pub(crate) trait TargetCodegen { vartab: &mut Vartable, cfg: &mut ControlFlowGraph, buffer_size_expr: Option, - ) -> Vec { - crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) - } + ) -> Vec; fn storage_array_push( &self, @@ -195,19 +174,7 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, ns: &Namespace, - ) -> Expression { - crate::codegen::storage::array_offset( - loc, - Expression::Keccak256 { - loc: *loc, - ty: slot_ty.clone(), - exprs: vec![var_expr.clone()], - }, - index, - elem_ty.clone(), - ns, - ) - } + ) -> Expression; fn lower_storage_struct_member( &self, @@ -226,7 +193,5 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, _ns: &Namespace, - ) -> Expression { - value - } + ) -> Expression; } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 662365e7e..3b138fd97 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -1,29 +1,27 @@ // SPDX-License-Identifier: Apache-2.0 -mod array_boundary; pub mod cfg; -mod constant_folding; mod constructor; -mod dead_storage; -pub(crate) mod dispatch; -pub(crate) mod encoding; +pub(crate) use targets::abi as encoding; pub(crate) mod error; -mod events; mod expression; mod interface; -pub(super) mod polkadot; -mod reaching_definitions; +mod optimize; +pub(crate) use optimize::array_boundary; +pub(crate) use optimize::constant_folding; +pub(crate) use optimize::dead_storage; +pub(crate) use optimize::reaching_definitions; +pub(crate) use optimize::strength_reduce; +pub(crate) use optimize::subexpression_elimination; +pub(crate) use optimize::undefined_variable; +pub(crate) use optimize::unused_variable; +pub(crate) use optimize::vector_to_slice; pub mod revert; -mod statements; +pub(crate) mod statements; mod storage; -mod strength_reduce; -pub(crate) mod subexpression_elimination; -mod targets; +pub(crate) mod targets; mod tests; -mod undefined_variable; -mod unused_variable; pub(crate) mod vartable; -mod vector_to_slice; mod yul; use self::{ diff --git a/src/codegen/array_boundary.rs b/src/codegen/optimize/array_boundary.rs similarity index 98% rename from src/codegen/array_boundary.rs rename to src/codegen/optimize/array_boundary.rs index 866b4d07d..8abb3d902 100644 --- a/src/codegen/array_boundary.rs +++ b/src/codegen/optimize/array_boundary.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -use super::vartable::Vartable; use crate::codegen::cfg::{ControlFlowGraph, Instr}; +use crate::codegen::vartable::Vartable; use crate::codegen::Expression; use crate::sema::ast::Type; use solang_parser::pt::Loc; diff --git a/src/codegen/constant_folding.rs b/src/codegen/optimize/constant_folding.rs similarity index 99% rename from src/codegen/constant_folding.rs rename to src/codegen/optimize/constant_folding.rs index 5a1582f57..e34c6edf8 100644 --- a/src/codegen/constant_folding.rs +++ b/src/codegen/optimize/constant_folding.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -use super::cfg::{ControlFlowGraph, Instr}; -use super::reaching_definitions; +use crate::codegen::cfg::{ControlFlowGraph, Instr}; +use crate::codegen::reaching_definitions; use crate::codegen::{Builtin, Expression}; use crate::sema::{ ast::{Diagnostic, Namespace, RetrieveType, StringLocation, Type}, diff --git a/src/codegen/dead_storage.rs b/src/codegen/optimize/dead_storage.rs similarity index 99% rename from src/codegen/dead_storage.rs rename to src/codegen/optimize/dead_storage.rs index abf808efd..fe0ed9f28 100644 --- a/src/codegen/dead_storage.rs +++ b/src/codegen/optimize/dead_storage.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -use super::cfg::{BasicBlock, ControlFlowGraph, Instr}; +use crate::codegen::cfg::{BasicBlock, ControlFlowGraph, Instr}; use crate::codegen::Expression; use crate::sema::ast::{Namespace, RetrieveType, Type}; use solang_parser::pt::Loc; diff --git a/src/codegen/optimize/mod.rs b/src/codegen/optimize/mod.rs new file mode 100644 index 000000000..bfdc3b56e --- /dev/null +++ b/src/codegen/optimize/mod.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod array_boundary; +pub(crate) mod constant_folding; +pub(crate) mod dead_storage; +pub(crate) mod reaching_definitions; +pub(crate) mod strength_reduce; +pub(crate) mod subexpression_elimination; +pub(crate) mod undefined_variable; +pub(crate) mod unused_variable; +pub(crate) mod vector_to_slice; diff --git a/src/codegen/reaching_definitions.rs b/src/codegen/optimize/reaching_definitions.rs similarity index 99% rename from src/codegen/reaching_definitions.rs rename to src/codegen/optimize/reaching_definitions.rs index c0234ff19..0b32a7818 100644 --- a/src/codegen/reaching_definitions.rs +++ b/src/codegen/optimize/reaching_definitions.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -use super::cfg::{BasicBlock, ControlFlowGraph, Instr}; +use crate::codegen::cfg::{BasicBlock, ControlFlowGraph, Instr}; use crate::codegen::Expression; use indexmap::IndexMap; use std::collections::HashSet; diff --git a/src/codegen/strength_reduce/expression_values.rs b/src/codegen/optimize/strength_reduce/expression_values.rs similarity index 100% rename from src/codegen/strength_reduce/expression_values.rs rename to src/codegen/optimize/strength_reduce/expression_values.rs diff --git a/src/codegen/strength_reduce/mod.rs b/src/codegen/optimize/strength_reduce/mod.rs similarity index 99% rename from src/codegen/strength_reduce/mod.rs rename to src/codegen/optimize/strength_reduce/mod.rs index 8a4da73f6..943116c96 100644 --- a/src/codegen/strength_reduce/mod.rs +++ b/src/codegen/optimize/strength_reduce/mod.rs @@ -5,7 +5,7 @@ mod reaching_values; mod tests; mod value; -use super::cfg::{ControlFlowGraph, Instr}; +use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::Expression; use crate::sema::ast::{ExternalCallAccounts, Namespace, Type}; use bitvec::prelude::*; diff --git a/src/codegen/strength_reduce/reaching_values.rs b/src/codegen/optimize/strength_reduce/reaching_values.rs similarity index 100% rename from src/codegen/strength_reduce/reaching_values.rs rename to src/codegen/optimize/strength_reduce/reaching_values.rs diff --git a/src/codegen/strength_reduce/tests.rs b/src/codegen/optimize/strength_reduce/tests.rs similarity index 99% rename from src/codegen/strength_reduce/tests.rs rename to src/codegen/optimize/strength_reduce/tests.rs index 9770374dc..9702c3ac9 100644 --- a/src/codegen/strength_reduce/tests.rs +++ b/src/codegen/optimize/strength_reduce/tests.rs @@ -3,7 +3,7 @@ #![cfg(test)] use super::expression_values::expression_values; use super::{highest_set_bit, Variables}; -use crate::codegen::strength_reduce::value::Value; +use crate::codegen::optimize::strength_reduce::value::Value; use crate::codegen::Expression; use crate::sema::ast::{Namespace, Type}; use bitvec::prelude::BitArray; diff --git a/src/codegen/strength_reduce/value.rs b/src/codegen/optimize/strength_reduce/value.rs similarity index 100% rename from src/codegen/strength_reduce/value.rs rename to src/codegen/optimize/strength_reduce/value.rs diff --git a/src/codegen/subexpression_elimination/anticipated_expressions.rs b/src/codegen/optimize/subexpression_elimination/anticipated_expressions.rs similarity index 100% rename from src/codegen/subexpression_elimination/anticipated_expressions.rs rename to src/codegen/optimize/subexpression_elimination/anticipated_expressions.rs diff --git a/src/codegen/subexpression_elimination/available_expression.rs b/src/codegen/optimize/subexpression_elimination/available_expression.rs similarity index 100% rename from src/codegen/subexpression_elimination/available_expression.rs rename to src/codegen/optimize/subexpression_elimination/available_expression.rs diff --git a/src/codegen/subexpression_elimination/available_expression_set.rs b/src/codegen/optimize/subexpression_elimination/available_expression_set.rs similarity index 100% rename from src/codegen/subexpression_elimination/available_expression_set.rs rename to src/codegen/optimize/subexpression_elimination/available_expression_set.rs diff --git a/src/codegen/subexpression_elimination/available_variable.rs b/src/codegen/optimize/subexpression_elimination/available_variable.rs similarity index 100% rename from src/codegen/subexpression_elimination/available_variable.rs rename to src/codegen/optimize/subexpression_elimination/available_variable.rs diff --git a/src/codegen/subexpression_elimination/common_subexpression_tracker.rs b/src/codegen/optimize/subexpression_elimination/common_subexpression_tracker.rs similarity index 100% rename from src/codegen/subexpression_elimination/common_subexpression_tracker.rs rename to src/codegen/optimize/subexpression_elimination/common_subexpression_tracker.rs diff --git a/src/codegen/subexpression_elimination/expression.rs b/src/codegen/optimize/subexpression_elimination/expression.rs similarity index 100% rename from src/codegen/subexpression_elimination/expression.rs rename to src/codegen/optimize/subexpression_elimination/expression.rs diff --git a/src/codegen/subexpression_elimination/instruction.rs b/src/codegen/optimize/subexpression_elimination/instruction.rs similarity index 100% rename from src/codegen/subexpression_elimination/instruction.rs rename to src/codegen/optimize/subexpression_elimination/instruction.rs diff --git a/src/codegen/subexpression_elimination/mod.rs b/src/codegen/optimize/subexpression_elimination/mod.rs similarity index 100% rename from src/codegen/subexpression_elimination/mod.rs rename to src/codegen/optimize/subexpression_elimination/mod.rs diff --git a/src/codegen/subexpression_elimination/operator.rs b/src/codegen/optimize/subexpression_elimination/operator.rs similarity index 100% rename from src/codegen/subexpression_elimination/operator.rs rename to src/codegen/optimize/subexpression_elimination/operator.rs diff --git a/src/codegen/subexpression_elimination/tests.rs b/src/codegen/optimize/subexpression_elimination/tests.rs similarity index 100% rename from src/codegen/subexpression_elimination/tests.rs rename to src/codegen/optimize/subexpression_elimination/tests.rs diff --git a/src/codegen/undefined_variable.rs b/src/codegen/optimize/undefined_variable.rs similarity index 100% rename from src/codegen/undefined_variable.rs rename to src/codegen/optimize/undefined_variable.rs diff --git a/src/codegen/unused_variable.rs b/src/codegen/optimize/unused_variable.rs similarity index 99% rename from src/codegen/unused_variable.rs rename to src/codegen/optimize/unused_variable.rs index 150e35c6c..e6b126170 100644 --- a/src/codegen/unused_variable.rs +++ b/src/codegen/optimize/unused_variable.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -use super::Options; use crate::codegen::interface::TargetCodegen; +use crate::codegen::Options; use crate::codegen::{cfg::ControlFlowGraph, vartable::Vartable, OptimizationLevel}; use crate::sema::ast::RetrieveType; use crate::sema::ast::{Builtin, Expression, Function, Namespace}; diff --git a/src/codegen/vector_to_slice.rs b/src/codegen/optimize/vector_to_slice.rs similarity index 98% rename from src/codegen/vector_to_slice.rs rename to src/codegen/optimize/vector_to_slice.rs index 29241b8e6..28ba044dd 100644 --- a/src/codegen/vector_to_slice.rs +++ b/src/codegen/optimize/vector_to_slice.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 -use super::cfg::{BasicBlock, ControlFlowGraph, Instr}; -use super::reaching_definitions::{Def, Transfer}; use crate::codegen::cfg::ASTFunction; +use crate::codegen::cfg::{BasicBlock, ControlFlowGraph, Instr}; +use crate::codegen::reaching_definitions::{Def, Transfer}; use crate::codegen::Expression; use crate::sema::ast::{Namespace, Type}; use indexmap::IndexMap; diff --git a/src/codegen/statements/mod.rs b/src/codegen/statements.rs similarity index 98% rename from src/codegen/statements/mod.rs rename to src/codegen/statements.rs index ee577bca0..f65f9256f 100644 --- a/src/codegen/statements/mod.rs +++ b/src/codegen/statements.rs @@ -9,7 +9,7 @@ use super::{ }, vartable::Vartable, yul::inline_assembly_cfg, - Builtin, Expression, Options, + Expression, Options, }; use crate::codegen::interface::TargetCodegen; use crate::sema::ast::{ @@ -22,8 +22,6 @@ use num_bigint::BigInt; use num_traits::Zero; use solang_parser::pt::{self, CodeLocation, Loc, Loc::Codegen}; -mod try_catch; - /// Resolve a statement, which might be a block of statements or an entire body of a function pub(crate) fn statement( stmt: &Statement, @@ -614,19 +612,21 @@ pub(crate) fn statement( opt, target, ), - Statement::TryCatch(_, _, try_stmt) => self::try_catch::try_catch( - try_stmt, - func, - cfg, - contract_no, - ns, - vartab, - loops, - placeholder, - return_override, - opt, - target, - ), + Statement::TryCatch(_, _, try_stmt) => { + crate::codegen::targets::polkadot::try_catch::try_catch( + try_stmt, + func, + cfg, + contract_no, + ns, + vartab, + loops, + placeholder, + return_override, + opt, + target, + ) + } Statement::Emit { loc, event_no, diff --git a/src/codegen/encoding/mod.rs b/src/codegen/targets/abi.rs similarity index 99% rename from src/codegen/encoding/mod.rs rename to src/codegen/targets/abi.rs index ce9b04d78..799a51774 100644 --- a/src/codegen/encoding/mod.rs +++ b/src/codegen/targets/abi.rs @@ -8,14 +8,13 @@ /// - `AbiEncoding` defines the encoding and decoding API and must be implemented by all schemes. /// - There are some helper functions to work with more complex types. /// Any such helper function should work fine regardless of the encoding scheme being used. -pub(crate) mod buffer_validator; -pub(super) mod scale_encoding; +pub(crate) use super::buffer_validator; use crate::codegen::cfg::{ControlFlowGraph, Instr}; -use crate::codegen::encoding::scale_encoding::ScaleEncoding; use crate::codegen::expression::load_storage; use crate::codegen::interface::TargetCodegen; use crate::codegen::targets::make_target; +use crate::codegen::targets::polkadot::encoding::ScaleEncoding; use crate::codegen::targets::solana::encoding::BorshEncoding; use crate::codegen::vartable::Vartable; use crate::codegen::{Builtin, Expression}; @@ -31,7 +30,7 @@ use self::buffer_validator::BufferValidator; /// Insert encoding instructions into the `cfg` for any `Expression` in `args`. /// Returns a pointer to the encoded data and the size as a 32bit integer. -pub(super) fn abi_encode( +pub(crate) fn abi_encode( loc: &Loc, args: Vec, ns: &Namespace, @@ -83,7 +82,7 @@ pub(super) fn abi_encode( /// Insert decoding routines into the `cfg` for the `Expression`s in `args`. /// Returns a vector containing the encoded data. -pub(super) fn abi_decode( +pub(crate) fn abi_decode( loc: &Loc, buffer: &Expression, types: &[Type], diff --git a/src/codegen/encoding/buffer_validator.rs b/src/codegen/targets/buffer_validator.rs similarity index 100% rename from src/codegen/encoding/buffer_validator.rs rename to src/codegen/targets/buffer_validator.rs diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index 6256b398f..f7386d267 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -1,26 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 +pub(crate) mod abi; +pub(crate) mod buffer_validator; +pub(crate) mod polkadot; pub(crate) mod solana; pub(crate) mod soroban; -use crate::codegen::cfg::{ControlFlowGraph, Instr}; -use crate::codegen::events::polkadot::PolkadotEventEmitter; -use crate::codegen::expression::expression; -use crate::codegen::interface::{EventEmitter, TargetCodegen}; -use crate::codegen::storage::{ - array_pop, array_push, storage_slots_array_pop, storage_slots_array_push, -}; -use crate::codegen::vartable::Vartable; -use crate::codegen::{dispatch, polkadot, Expression, Options}; -use crate::sema::ast; -use crate::sema::ast::{ - CallTy, ExternalCallAccounts, Function, Namespace, RetrieveType, StructType, Type, -}; +use crate::codegen::interface::TargetCodegen; +use crate::sema::ast::Namespace; use crate::Target; -use num_bigint::BigInt; -use num_traits::Zero; -use solang_parser::pt::{self, Loc}; +use self::polkadot::PolkadotTarget; use self::solana::SolanaTarget; use self::soroban::SorobanTarget; @@ -32,334 +22,3 @@ pub(crate) fn make_target(ns: &Namespace) -> Box { Target::EVM => Box::new(PolkadotTarget { is_evm: true }), } } - -pub(crate) struct PolkadotTarget { - pub(crate) is_evm: bool, -} - -impl TargetCodegen for PolkadotTarget { - fn function_dispatch( - &self, - contract_no: usize, - all_cfg: &mut [ControlFlowGraph], - ns: &mut Namespace, - opt: &Options, - ) -> Vec { - dispatch::polkadot::function_dispatch(contract_no, all_cfg, ns, opt) - } - - fn storage_array_push( - &self, - loc: &Loc, - args: &[ast::Expression], - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - opt: &Options, - ) -> Expression { - // `bytes` uses the flat-slot path; typed arrays use hashed slots. - if args[0].ty().is_storage_bytes() { - array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) - } else { - storage_slots_array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) - } - } - - fn storage_array_pop( - &self, - loc: &Loc, - args: &[ast::Expression], - return_ty: &Type, - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - opt: &Options, - ) -> Expression { - if args[0].ty().is_storage_bytes() { - array_pop( - loc, - args, - return_ty, - cfg, - contract_no, - func, - ns, - vartab, - opt, - self, - ) - } else { - storage_slots_array_pop( - loc, - args, - return_ty, - cfg, - contract_no, - func, - ns, - vartab, - opt, - self, - ) - } - } - - fn event_emitter<'a>( - &self, - _loc: &pt::Loc, - event_no: usize, - args: &'a [ast::Expression], - ns: &'a Namespace, - ) -> Box { - Box::new(PolkadotEventEmitter { args, ns, event_no }) - } - - fn default_gas_builtin(&self) -> BigInt { - if self.is_evm { - BigInt::from(i64::MAX) - } else { - BigInt::zero() - } - } - - fn lower_print_expr(&self, expr: Expression) -> Expression { - if self.is_evm { - expr - } else { - crate::codegen::expression::add_prefix_and_delimiter_to_print(expr) - } - } - - fn lower_mapping_subscript( - &self, - loc: &Loc, - elem_ty: &Type, - array_ty: &Type, - array: Expression, - index: Expression, - ) -> Expression { - if self.is_evm { - Expression::Subscript { - loc: *loc, - ty: elem_ty.clone(), - array_ty: array_ty.clone(), - expr: Box::new(array), - index: Box::new(index), - } - } else { - Expression::Keccak256 { - loc: *loc, - ty: array_ty.clone(), - exprs: vec![array, index], - } - } - } - - fn lower_builtin( - &self, - loc: &Loc, - builtin: ast::Builtin, - args: &[ast::Expression], - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - opt: &Options, - ) -> Option { - match builtin { - ast::Builtin::Gasprice if self.is_evm && args.len() == 1 => { - Some(crate::codegen::expression::builtin_evm_gasprice( - loc, - args, - cfg, - contract_no, - func, - ns, - vartab, - opt, - self, - )) - } - ast::Builtin::PayableSend => { - Some(self.payable_send(loc, args, cfg, contract_no, func, ns, vartab, opt)) - } - ast::Builtin::PayableTransfer => { - Some(self.payable_transfer(loc, args, cfg, contract_no, func, ns, vartab, opt)) - } - _ => None, - } - } - - fn lower_storage_struct_member( - &self, - loc: &Loc, - var_expr: Expression, - struct_ty: &StructType, - field_no: usize, - ns: &Namespace, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - ) -> Expression { - // Polkadot/EVM lay struct fields out in consecutive storage slots. - let offset: BigInt = struct_ty.definition(ns).fields[..field_no] - .iter() - .filter(|field| !field.infinite_size) - .map(|field| field.ty.storage_slots(ns)) - .sum(); - Expression::Add { - loc: *loc, - ty: ns.storage_type(), - overflowing: true, - left: Box::new(var_expr), - right: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: ns.storage_type(), - value: offset, - }), - } - } -} - -impl PolkadotTarget { - /// Lower `address.send(value)` (`Builtin::PayableSend`). EVM routes through an external - /// call with an empty payload; Polkadot emits a `ValueTransfer` and checks the return code. - fn payable_send( - &self, - loc: &Loc, - args: &[ast::Expression], - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - opt: &Options, - ) -> Expression { - let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); - let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); - let success = vartab.temp( - &pt::Identifier { - loc: *loc, - name: "success".to_owned(), - }, - &Type::Uint(32), - ); - - // Ethereum can only transfer via external call - if self.is_evm { - cfg.add( - vartab, - Instr::ExternalCall { - loc: *loc, - success: Some(success), - address: Some(address), - accounts: ExternalCallAccounts::AbsentArgument, - seeds: None, - payload: Expression::AllocDynamicBytes { - loc: *loc, - ty: Type::DynamicBytes, - size: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(32), - value: BigInt::from(0), - }), - initializer: Some(vec![]), - }, - value, - gas: Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(64), - value: BigInt::from(i64::MAX), - }, - callty: CallTy::Regular, - contract_function_no: None, - flags: None, - }, - ); - return Expression::Variable { - loc: *loc, - ty: Type::Bool, - var_no: success, - }; - } - - cfg.add( - vartab, - Instr::ValueTransfer { - success: Some(success), - address, - value, - }, - ); - - polkadot::check_transfer_ret(loc, success, cfg, ns, opt, vartab, false).unwrap() - } - - /// Lower `address.transfer(value)` (`Builtin::PayableTransfer`). EVM routes through an - /// external call; Polkadot emits a `ValueTransfer` and reverts on a non-zero return code. - fn payable_transfer( - &self, - loc: &Loc, - args: &[ast::Expression], - cfg: &mut ControlFlowGraph, - contract_no: usize, - func: Option<&Function>, - ns: &Namespace, - vartab: &mut Vartable, - opt: &Options, - ) -> Expression { - let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); - let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); - if self.is_evm { - // Ethereum can only transfer via external call - cfg.add( - vartab, - Instr::ExternalCall { - loc: *loc, - success: None, - accounts: ExternalCallAccounts::AbsentArgument, - seeds: None, - address: Some(address), - payload: Expression::AllocDynamicBytes { - loc: *loc, - ty: Type::DynamicBytes, - size: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(32), - value: BigInt::from(0), - }), - initializer: Some(vec![]), - }, - value, - gas: Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(64), - value: BigInt::from(i64::MAX), - }, - callty: CallTy::Regular, - contract_function_no: None, - flags: None, - }, - ); - return Expression::Poison; - } - - let success = vartab.temp_name("success", &Type::Uint(32)); - cfg.add( - vartab, - Instr::ValueTransfer { - success: Some(success), - address, - value, - }, - ); - - polkadot::check_transfer_ret(loc, success, cfg, ns, opt, vartab, true); - - Expression::Poison - } -} diff --git a/src/codegen/dispatch/polkadot.rs b/src/codegen/targets/polkadot/dispatch.rs similarity index 100% rename from src/codegen/dispatch/polkadot.rs rename to src/codegen/targets/polkadot/dispatch.rs diff --git a/src/codegen/encoding/scale_encoding.rs b/src/codegen/targets/polkadot/encoding.rs similarity index 98% rename from src/codegen/encoding/scale_encoding.rs rename to src/codegen/targets/polkadot/encoding.rs index d7895db2d..b39c430bf 100644 --- a/src/codegen/encoding/scale_encoding.rs +++ b/src/codegen/targets/polkadot/encoding.rs @@ -11,9 +11,9 @@ use primitive_types::U256; use solang_parser::pt::Loc::Codegen; use std::collections::HashMap; -use super::buffer_validator::BufferValidator; +use crate::codegen::encoding::buffer_validator::BufferValidator; -pub(super) struct ScaleEncoding { +pub(crate) struct ScaleEncoding { storage_cache: HashMap, packed_encoder: bool, } @@ -630,10 +630,7 @@ mod tests { use primitive_types::U256; use crate::{ - codegen::{ - encoding::{scale_encoding::ScaleEncoding, AbiEncoding}, - Expression, - }, + codegen::{encoding::AbiEncoding, targets::polkadot::encoding::ScaleEncoding, Expression}, sema::ast::Type, }; diff --git a/src/codegen/events/polkadot.rs b/src/codegen/targets/polkadot/events.rs similarity index 99% rename from src/codegen/events/polkadot.rs rename to src/codegen/targets/polkadot/events.rs index ec6d40bcb..6dd485b35 100644 --- a/src/codegen/events/polkadot.rs +++ b/src/codegen/targets/polkadot/events.rs @@ -4,8 +4,8 @@ use std::vec; use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::encoding::abi_encode; -use crate::codegen::events::EventEmitter; use crate::codegen::expression::expression; +use crate::codegen::interface::EventEmitter; use crate::codegen::interface::TargetCodegen; use crate::codegen::vartable::Vartable; use crate::codegen::{Builtin, Expression, Options}; diff --git a/src/codegen/targets/polkadot/mod.rs b/src/codegen/targets/polkadot/mod.rs new file mode 100644 index 000000000..f332b3bcb --- /dev/null +++ b/src/codegen/targets/polkadot/mod.rs @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod dispatch; +pub(crate) mod encoding; +pub(crate) mod events; +pub(crate) mod return_code; +pub(crate) mod try_catch; + +use crate::codegen::cfg::{ControlFlowGraph, Instr}; +use crate::codegen::expression::expression; +use crate::codegen::interface::{EventEmitter, TargetCodegen}; +use crate::codegen::storage::{ + array_pop, array_push, storage_slots_array_pop, storage_slots_array_push, +}; +use crate::codegen::vartable::Vartable; +use crate::codegen::{Expression, Options}; +use crate::sema::ast; +use crate::sema::ast::{ + CallTy, ExternalCallAccounts, Function, Namespace, RetrieveType, StructType, Type, +}; +use num_bigint::BigInt; +use num_traits::Zero; +use solang_parser::pt::{self, Loc}; + +use self::events::PolkadotEventEmitter; + +pub(crate) struct PolkadotTarget { + pub(crate) is_evm: bool, +} + +impl TargetCodegen for PolkadotTarget { + fn function_dispatch( + &self, + contract_no: usize, + all_cfg: &mut [ControlFlowGraph], + ns: &mut Namespace, + opt: &Options, + ) -> Vec { + dispatch::function_dispatch(contract_no, all_cfg, ns, opt) + } + + fn storage_array_push( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + // `bytes` uses the flat-slot path; typed arrays use hashed slots. + if args[0].ty().is_storage_bytes() { + array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) + } else { + storage_slots_array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self) + } + } + + fn storage_array_pop( + &self, + loc: &Loc, + args: &[ast::Expression], + return_ty: &Type, + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + if args[0].ty().is_storage_bytes() { + array_pop( + loc, + args, + return_ty, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + ) + } else { + storage_slots_array_pop( + loc, + args, + return_ty, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + ) + } + } + + fn event_emitter<'a>( + &self, + _loc: &pt::Loc, + event_no: usize, + args: &'a [ast::Expression], + ns: &'a Namespace, + ) -> Box { + Box::new(PolkadotEventEmitter { args, ns, event_no }) + } + + fn default_gas_builtin(&self) -> BigInt { + if self.is_evm { + BigInt::from(i64::MAX) + } else { + BigInt::zero() + } + } + + fn lower_print_expr(&self, expr: Expression) -> Expression { + if self.is_evm { + expr + } else { + crate::codegen::expression::add_prefix_and_delimiter_to_print(expr) + } + } + + fn lower_mapping_subscript( + &self, + loc: &Loc, + elem_ty: &Type, + array_ty: &Type, + array: Expression, + index: Expression, + ) -> Expression { + if self.is_evm { + Expression::Subscript { + loc: *loc, + ty: elem_ty.clone(), + array_ty: array_ty.clone(), + expr: Box::new(array), + index: Box::new(index), + } + } else { + Expression::Keccak256 { + loc: *loc, + ty: array_ty.clone(), + exprs: vec![array, index], + } + } + } + + fn lower_builtin( + &self, + loc: &Loc, + builtin: ast::Builtin, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Option { + match builtin { + ast::Builtin::Gasprice if self.is_evm && args.len() == 1 => { + Some(crate::codegen::expression::builtin_evm_gasprice( + loc, + args, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + )) + } + ast::Builtin::PayableSend => { + Some(self.payable_send(loc, args, cfg, contract_no, func, ns, vartab, opt)) + } + ast::Builtin::PayableTransfer => { + Some(self.payable_transfer(loc, args, cfg, contract_no, func, ns, vartab, opt)) + } + _ => None, + } + } + + fn lower_storage_struct_member( + &self, + loc: &Loc, + var_expr: Expression, + struct_ty: &StructType, + field_no: usize, + ns: &Namespace, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + ) -> Expression { + // Polkadot/EVM lay struct fields out in consecutive storage slots. + let offset: BigInt = struct_ty.definition(ns).fields[..field_no] + .iter() + .filter(|field| !field.infinite_size) + .map(|field| field.ty.storage_slots(ns)) + .sum(); + Expression::Add { + loc: *loc, + ty: ns.storage_type(), + overflowing: true, + left: Box::new(var_expr), + right: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: ns.storage_type(), + value: offset, + }), + } + } + + fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace) {} + + fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace) {} + + fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {} + + fn selector_hash_algorithm(&self) -> ast::Builtin { + ast::Builtin::Keccak256 + } + + fn storage_array_length_is_inline(&self) -> bool { + false + } + + fn initial_storage_slot(&self) -> BigInt { + BigInt::zero() + } + + fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt { + slot + } + + fn lower_load( + &self, + load: Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + load + } + + fn prepare_storage_value( + &self, + value: Expression, + _dest: &Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + value + } + + fn default_storage_value( + &self, + _loc: &Loc, + _ty: &Type, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Option { + None + } + + fn abi_encode( + &self, + loc: &Loc, + args: Vec, + ns: &Namespace, + vartab: &mut Vartable, + cfg: &mut ControlFlowGraph, + packed: bool, + ) -> (Expression, Expression) { + crate::codegen::encoding::abi_encode(loc, args, ns, vartab, cfg, packed) + } + + fn abi_decode( + &self, + loc: &Loc, + buffer: &Expression, + types: &[Type], + ns: &Namespace, + vartab: &mut Vartable, + cfg: &mut ControlFlowGraph, + buffer_size_expr: Option, + ) -> Vec { + crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) + } + + fn storage_array_entry_offset( + &self, + loc: &Loc, + var_expr: &Expression, + index: Expression, + elem_ty: &Type, + slot_ty: &Type, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + crate::codegen::storage::array_offset( + loc, + Expression::Keccak256 { + loc: *loc, + ty: slot_ty.clone(), + exprs: vec![var_expr.clone()], + }, + index, + elem_ty.clone(), + ns, + ) + } + + fn lower_load_storage( + &self, + value: Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + value + } +} + +impl PolkadotTarget { + /// Lower `address.send(value)` (`Builtin::PayableSend`). EVM routes through an external + /// call with an empty payload; Polkadot emits a `ValueTransfer` and checks the return code. + fn payable_send( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); + let success = vartab.temp( + &pt::Identifier { + loc: *loc, + name: "success".to_owned(), + }, + &Type::Uint(32), + ); + + // Ethereum can only transfer via external call + if self.is_evm { + cfg.add( + vartab, + Instr::ExternalCall { + loc: *loc, + success: Some(success), + address: Some(address), + accounts: ExternalCallAccounts::AbsentArgument, + seeds: None, + payload: Expression::AllocDynamicBytes { + loc: *loc, + ty: Type::DynamicBytes, + size: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(32), + value: BigInt::from(0), + }), + initializer: Some(vec![]), + }, + value, + gas: Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(64), + value: BigInt::from(i64::MAX), + }, + callty: CallTy::Regular, + contract_function_no: None, + flags: None, + }, + ); + return Expression::Variable { + loc: *loc, + ty: Type::Bool, + var_no: success, + }; + } + + cfg.add( + vartab, + Instr::ValueTransfer { + success: Some(success), + address, + value, + }, + ); + + return_code::check_transfer_ret(loc, success, cfg, ns, opt, vartab, false).unwrap() + } + + /// Lower `address.transfer(value)` (`Builtin::PayableTransfer`). EVM routes through an + /// external call; Polkadot emits a `ValueTransfer` and reverts on a non-zero return code. + fn payable_transfer( + &self, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, + ) -> Expression { + let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); + if self.is_evm { + // Ethereum can only transfer via external call + cfg.add( + vartab, + Instr::ExternalCall { + loc: *loc, + success: None, + accounts: ExternalCallAccounts::AbsentArgument, + seeds: None, + address: Some(address), + payload: Expression::AllocDynamicBytes { + loc: *loc, + ty: Type::DynamicBytes, + size: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(32), + value: BigInt::from(0), + }), + initializer: Some(vec![]), + }, + value, + gas: Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(64), + value: BigInt::from(i64::MAX), + }, + callty: CallTy::Regular, + contract_function_no: None, + flags: None, + }, + ); + return Expression::Poison; + } + + let success = vartab.temp_name("success", &Type::Uint(32)); + cfg.add( + vartab, + Instr::ValueTransfer { + success: Some(success), + address, + value, + }, + ); + + return_code::check_transfer_ret(loc, success, cfg, ns, opt, vartab, true); + + Expression::Poison + } +} diff --git a/src/codegen/polkadot.rs b/src/codegen/targets/polkadot/return_code.rs similarity index 99% rename from src/codegen/polkadot.rs rename to src/codegen/targets/polkadot/return_code.rs index 65871d260..032aaa3b9 100644 --- a/src/codegen/polkadot.rs +++ b/src/codegen/targets/polkadot/return_code.rs @@ -125,7 +125,7 @@ impl RetCodeCheck { /// /// If `bubble_up` is set to true, this will revert the contract execution on failure. /// Otherwise, the expression comparing the return code against `0` is returned. -pub(super) fn check_transfer_ret( +pub(crate) fn check_transfer_ret( loc: &Loc, success: usize, cfg: &mut ControlFlowGraph, diff --git a/src/codegen/statements/try_catch.rs b/src/codegen/targets/polkadot/try_catch.rs similarity index 99% rename from src/codegen/statements/try_catch.rs rename to src/codegen/targets/polkadot/try_catch.rs index e8386f974..23a8b27fe 100644 --- a/src/codegen/statements/try_catch.rs +++ b/src/codegen/targets/polkadot/try_catch.rs @@ -1,16 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 -use super::{statement, Builtin, LoopScopes, Options}; +use crate::codegen::targets::polkadot::return_code as polkadot; use crate::codegen::{ cfg::{ControlFlowGraph, Instr}, constructor::call_constructor, encoding::{abi_decode, abi_encode}, expression::{default_gas, expression}, interface::TargetCodegen, - polkadot, revert::{ERROR_SELECTOR, PANIC_SELECTOR}, + statements::{statement, LoopScopes}, vartable::Vartable, - Expression, + Builtin, Expression, Options, }; use crate::sema::ast::{ self, CallTy, ExternalCallAccounts, Function, Namespace, RetrieveType, TryCatch, Type, @@ -21,7 +21,7 @@ use num_traits::Zero; use solang_parser::pt::{self, CodeLocation, Loc::Codegen}; /// Resolve try catch statement -pub(super) fn try_catch( +pub(crate) fn try_catch( try_stmt: &TryCatch, func: &Function, cfg: &mut ControlFlowGraph, diff --git a/src/codegen/targets/solana/events.rs b/src/codegen/targets/solana/events.rs index 28cbab766..ef19da481 100644 --- a/src/codegen/targets/solana/events.rs +++ b/src/codegen/targets/solana/events.rs @@ -3,8 +3,8 @@ use crate::abi::anchor::event_discriminator; use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::codegen::encoding::abi_encode; -use crate::codegen::events::EventEmitter; use crate::codegen::expression::expression; +use crate::codegen::interface::EventEmitter; use crate::codegen::interface::TargetCodegen; use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; diff --git a/src/codegen/targets/solana/mod.rs b/src/codegen/targets/solana/mod.rs index 538f55044..2453ea7de 100644 --- a/src/codegen/targets/solana/mod.rs +++ b/src/codegen/targets/solana/mod.rs @@ -153,4 +153,139 @@ impl TargetCodegen for SolanaTarget { }), } } + + fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace) {} + + fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace) {} + + fn default_gas_builtin(&self) -> BigInt { + BigInt::zero() + } + + fn lower_print_expr(&self, expr: Expression) -> Expression { + expr + } + + fn lower_mapping_subscript( + &self, + loc: &Loc, + elem_ty: &Type, + array_ty: &Type, + array: Expression, + index: Expression, + ) -> Expression { + Expression::Subscript { + loc: *loc, + ty: elem_ty.clone(), + array_ty: array_ty.clone(), + expr: Box::new(array), + index: Box::new(index), + } + } + + fn lower_builtin( + &self, + _loc: &Loc, + _builtin: ast::Builtin, + _args: &[ast::Expression], + _cfg: &mut ControlFlowGraph, + _contract_no: usize, + _func: Option<&Function>, + _ns: &Namespace, + _vartab: &mut Vartable, + _opt: &Options, + ) -> Option { + None + } + + fn lower_load( + &self, + load: Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + load + } + + fn prepare_storage_value( + &self, + value: Expression, + _dest: &Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + value + } + + fn default_storage_value( + &self, + _loc: &Loc, + _ty: &Type, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Option { + None + } + + fn abi_encode( + &self, + loc: &Loc, + args: Vec, + ns: &Namespace, + vartab: &mut Vartable, + cfg: &mut ControlFlowGraph, + packed: bool, + ) -> (Expression, Expression) { + crate::codegen::encoding::abi_encode(loc, args, ns, vartab, cfg, packed) + } + + fn abi_decode( + &self, + loc: &Loc, + buffer: &Expression, + types: &[Type], + ns: &Namespace, + vartab: &mut Vartable, + cfg: &mut ControlFlowGraph, + buffer_size_expr: Option, + ) -> Vec { + crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) + } + + fn storage_array_entry_offset( + &self, + loc: &Loc, + var_expr: &Expression, + index: Expression, + elem_ty: &Type, + slot_ty: &Type, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + crate::codegen::storage::array_offset( + loc, + Expression::Keccak256 { + loc: *loc, + ty: slot_ty.clone(), + exprs: vec![var_expr.clone()], + }, + index, + elem_ty.clone(), + ns, + ) + } + + fn lower_load_storage( + &self, + value: Expression, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + value + } } diff --git a/src/codegen/targets/soroban/events.rs b/src/codegen/targets/soroban/events.rs index be8887999..3fb302d5d 100644 --- a/src/codegen/targets/soroban/events.rs +++ b/src/codegen/targets/soroban/events.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::codegen::cfg::{ControlFlowGraph, Instr}; -use crate::codegen::events::EventEmitter; use crate::codegen::expression::expression; +use crate::codegen::interface::EventEmitter; use crate::codegen::interface::TargetCodegen; use crate::codegen::targets::soroban::encoding::soroban_encode_arg; use crate::codegen::vartable::Vartable; diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index f3a939e76..bdf3416ba 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -19,6 +19,7 @@ use crate::sema::ast::{Function, Namespace, RetrieveType, StructType, Type}; use crate::sema::Recurse; use crate::Target; use num_bigint::{BigInt, Sign}; +use num_traits::Zero; use solang_parser::helpers::CodeLocation; use solang_parser::{diagnostics::Diagnostic, pt}; use std::collections::BTreeSet; @@ -335,6 +336,45 @@ impl TargetCodegen for SorobanTarget { ) -> Expression { soroban_decode_arg(value, cfg, vartab, ns, None) } + + fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {} + + fn selector_hash_algorithm(&self) -> ast::Builtin { + ast::Builtin::Keccak256 + } + + fn initial_storage_slot(&self) -> BigInt { + BigInt::zero() + } + + fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt { + slot + } + + fn default_gas_builtin(&self) -> BigInt { + BigInt::zero() + } + + fn lower_print_expr(&self, expr: Expression) -> Expression { + expr + } + + fn lower_mapping_subscript( + &self, + loc: &pt::Loc, + elem_ty: &Type, + array_ty: &Type, + array: Expression, + index: Expression, + ) -> Expression { + Expression::Subscript { + loc: *loc, + ty: elem_ty.clone(), + array_ty: array_ty.clone(), + expr: Box::new(array), + index: Box::new(index), + } + } } pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) { diff --git a/src/emit/polkadot/mod.rs b/src/emit/polkadot/mod.rs index 0c837e85d..6b6b23f66 100644 --- a/src/emit/polkadot/mod.rs +++ b/src/emit/polkadot/mod.rs @@ -2,7 +2,7 @@ use std::ffi::CString; -use crate::codegen::polkadot::SCRATCH_SIZE; +use crate::codegen::targets::polkadot::return_code::SCRATCH_SIZE; use crate::codegen::{Options, STORAGE_INITIALIZER}; use crate::sema::ast::{Contract, Namespace}; use inkwell::context::Context; @@ -10,7 +10,7 @@ use inkwell::module::{Linkage, Module}; use inkwell::values::{BasicMetadataValueEnum, FunctionValue, IntValue, PointerValue}; use inkwell::AddressSpace; -use crate::codegen::dispatch::polkadot::DispatchType; +use crate::codegen::targets::polkadot::dispatch::DispatchType; use crate::emit::functions::emit_functions; use crate::emit::{Binary, TargetRuntime}; diff --git a/src/emit/polkadot/target.rs b/src/emit/polkadot/target.rs index 17de405bc..6b3099615 100644 --- a/src/emit/polkadot/target.rs +++ b/src/emit/polkadot/target.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::codegen::cfg::HashTy; -use crate::codegen::polkadot::SCRATCH_SIZE; use crate::codegen::revert::PanicCode; +use crate::codegen::targets::polkadot::return_code::SCRATCH_SIZE; use crate::emit::binary::Binary; use crate::emit::expression::expression; use crate::emit::polkadot::PolkadotTarget; From 6bb4269b7487e50d60b2461627c6e07ba57b5dfc Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Sat, 20 Jun 2026 14:35:11 +0300 Subject: [PATCH 12/15] codegen: route storage_slots_array_pop through storage_array_entry_offset Signed-off-by: Islam-Imad --- src/codegen/interface.rs | 11 ----------- src/codegen/storage.rs | 13 ++++++------- src/codegen/targets/soroban/mod.rs | 17 ++--------------- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index 9051d39e3..d58765c35 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -37,17 +37,12 @@ pub(crate) trait TargetCodegen { opt: &Options, ) -> Vec; - /// Whole-program post-processing, called once after every contract's CFGs. fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options); fn selector_hash_algorithm(&self) -> ast::Builtin; - /// Whether dynamic storage arrays store their length inline in the value (Solana/Soroban) - /// or in a separate storage slot (Polkadot). fn storage_array_length_is_inline(&self) -> bool; - /// Starting offset for the first storage slot. Solana reserves the first 16 bytes for - /// account metadata; all other targets begin at slot 0. fn initial_storage_slot(&self) -> BigInt; fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt; @@ -65,7 +60,6 @@ pub(crate) trait TargetCodegen { index: Expression, ) -> Expression; - /// Target-specific builtin lowering; `None` falls through to shared `expr_builtin`. fn lower_builtin( &self, _loc: &Loc, @@ -79,8 +73,6 @@ pub(crate) trait TargetCodegen { _opt: &Options, ) -> Option; - /// Optionally rewrite a freshly-built `Load` expression. - /// Soroban decodes handles on load; other targets pass through unchanged. fn lower_load( &self, load: Expression, @@ -89,8 +81,6 @@ pub(crate) trait TargetCodegen { _ns: &Namespace, ) -> Expression; - /// Transform a value just before it is written to storage or a storage-backed ref. - /// Soroban encodes values to ScVal handles; other targets pass through unchanged. fn prepare_storage_value( &self, value: Expression, @@ -100,7 +90,6 @@ pub(crate) trait TargetCodegen { _ns: &Namespace, ) -> Expression; - /// Default value for an uninitialised storage variable; `None` means "skip the variable". fn default_storage_value( &self, _loc: &Loc, diff --git a/src/codegen/storage.rs b/src/codegen/storage.rs index 2061c8552..55d1524b5 100644 --- a/src/codegen/storage.rs +++ b/src/codegen/storage.rs @@ -319,19 +319,18 @@ pub fn storage_slots_array_pop( let elem_ty = ty.storage_array_elem().deref_any().clone(); let entry_pos = vartab.temp_anonymous(&slot_ty); - let array_offset_expr = array_offset( + let array_offset_expr = target.storage_array_entry_offset( loc, - Expression::Keccak256 { - loc: *loc, - ty: slot_ty.clone(), - exprs: vec![var_expr.clone()], - }, + &var_expr, Expression::Variable { loc: *loc, ty: slot_ty.clone(), var_no: new_length, }, - elem_ty.clone(), + &elem_ty, + &slot_ty, + cfg, + vartab, ns, ); diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index bdf3416ba..f11410532 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -55,6 +55,7 @@ impl TargetCodegen for SorobanTarget { true } + /// Soroban lazy decode path: if memory contains encoded handles, decode on demand. fn lower_load( &self, load: Expression, @@ -62,8 +63,6 @@ impl TargetCodegen for SorobanTarget { vartab: &mut Vartable, ns: &Namespace, ) -> Expression { - // Check the INNER expression's type (the pointer): if it is Ref(SorobanHandle), - // the variable holds an encoded handle and must be decoded on load. if let Expression::Load { ref expr, .. } = load { if let Type::Ref(inner) = expr.ty() { if matches!(inner.as_ref(), Type::SorobanHandle(_)) { @@ -256,7 +255,7 @@ impl TargetCodegen for SorobanTarget { ) -> Option { match builtin { ast::Builtin::GetAddress => { - // program_id is a compile-time constant address (Solana/Soroban); return it directly. + // // In soroban, address is retrieved via a host function call if let Some(constant_id) = &ns.contracts[contract_no].program_id { return Some(Expression::NumberLiteral { loc: *loc, @@ -378,10 +377,6 @@ impl TargetCodegen for SorobanTarget { } pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) { - if ns.target != Target::Soroban { - return; - } - for variable in &ns.contracts[contract_no].variables { if !matches!(variable.visibility, pt::Visibility::Public(_)) { continue; @@ -399,10 +394,6 @@ pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace } pub(super) fn validate_event_abi_types(contract_no: usize, ns: &mut Namespace) { - if ns.target != Target::Soroban { - return; - } - for event_no in ns.contracts[contract_no].emits_events.clone() { for field in &ns.events[event_no].fields { if let Some(unsupported_type) = unsupported_event_type(&field.ty, ns) { @@ -418,10 +409,6 @@ pub(super) fn validate_event_abi_types(contract_no: usize, ns: &mut Namespace) { } pub(super) fn validate_abi_types(all_cfg: &[ControlFlowGraph], ns: &mut Namespace) { - if ns.target != Target::Soroban { - return; - } - for cfg in all_cfg { if !cfg.public { continue; From 3fada02111b1aefdbbbc31aa1291f8209b6de371 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Sat, 20 Jun 2026 19:28:42 +0300 Subject: [PATCH 13/15] codegen: introduce lower_storage_array_length in TargetCodegen Signed-off-by: Islam-Imad --- src/codegen/expression.rs | 20 +------------------- src/codegen/interface.rs | 11 ++++++++++- src/codegen/targets/polkadot/mod.rs | 15 ++++++++++++--- src/codegen/targets/solana/mod.rs | 18 ++++++++++++++++-- src/codegen/targets/soroban/mod.rs | 18 ++++++++++++++++-- 5 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 64ed734d3..9aa59a8f6 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -779,25 +779,7 @@ pub fn expression( }, Type::Array(_, dim) => match dim.last().unwrap() { ArrayLength::Dynamic => { - if target.storage_array_length_is_inline() { - Expression::StorageArrayLength { - loc: *loc, - ty: ty.clone(), - array: Box::new(array), - elem_ty: elem_ty.clone(), - } - } else { - load_storage( - loc, - &ns.storage_type(), - array, - cfg, - vartab, - None, - ns, - target, - ) - } + target.lower_storage_array_length(loc, ty, array, elem_ty, cfg, vartab, ns) } ArrayLength::Fixed(length) => { let ast_expr = bigint_to_expression( diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index d58765c35..d1503cbfd 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -41,7 +41,16 @@ pub(crate) trait TargetCodegen { fn selector_hash_algorithm(&self) -> ast::Builtin; - fn storage_array_length_is_inline(&self) -> bool; + fn lower_storage_array_length( + &self, + loc: &Loc, + ty: &Type, + array: Expression, + elem_ty: &Type, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression; fn initial_storage_slot(&self) -> BigInt; diff --git a/src/codegen/targets/polkadot/mod.rs b/src/codegen/targets/polkadot/mod.rs index f332b3bcb..a6ec59147 100644 --- a/src/codegen/targets/polkadot/mod.rs +++ b/src/codegen/targets/polkadot/mod.rs @@ -7,7 +7,7 @@ pub(crate) mod return_code; pub(crate) mod try_catch; use crate::codegen::cfg::{ControlFlowGraph, Instr}; -use crate::codegen::expression::expression; +use crate::codegen::expression::{expression, load_storage}; use crate::codegen::interface::{EventEmitter, TargetCodegen}; use crate::codegen::storage::{ array_pop, array_push, storage_slots_array_pop, storage_slots_array_push, @@ -225,8 +225,17 @@ impl TargetCodegen for PolkadotTarget { ast::Builtin::Keccak256 } - fn storage_array_length_is_inline(&self) -> bool { - false + fn lower_storage_array_length( + &self, + loc: &Loc, + _ty: &Type, + array: Expression, + _elem_ty: &Type, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + load_storage(loc, &ns.storage_type(), array, cfg, vartab, None, ns, self) } fn initial_storage_slot(&self) -> BigInt { diff --git a/src/codegen/targets/solana/mod.rs b/src/codegen/targets/solana/mod.rs index 2453ea7de..bfc018a28 100644 --- a/src/codegen/targets/solana/mod.rs +++ b/src/codegen/targets/solana/mod.rs @@ -53,8 +53,22 @@ impl TargetCodegen for SolanaTarget { } } - fn storage_array_length_is_inline(&self) -> bool { - true + fn lower_storage_array_length( + &self, + loc: &Loc, + ty: &Type, + array: Expression, + elem_ty: &Type, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + Expression::StorageArrayLength { + loc: *loc, + ty: ty.clone(), + array: Box::new(array), + elem_ty: elem_ty.clone(), + } } fn selector_hash_algorithm(&self) -> ast::Builtin { diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index f11410532..1aa8ea7d2 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -51,8 +51,22 @@ impl TargetCodegen for SorobanTarget { dispatch::function_dispatch(contract_no, all_cfg, ns, opt) } - fn storage_array_length_is_inline(&self) -> bool { - true + fn lower_storage_array_length( + &self, + loc: &pt::Loc, + ty: &Type, + array: Expression, + elem_ty: &Type, + _cfg: &mut ControlFlowGraph, + _vartab: &mut Vartable, + _ns: &Namespace, + ) -> Expression { + Expression::StorageArrayLength { + loc: *loc, + ty: ty.clone(), + array: Box::new(array), + elem_ty: elem_ty.clone(), + } } /// Soroban lazy decode path: if memory contains encoded handles, decode on demand. From d740e63654ef952e3a76a60929450c4dee0a9ea9 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Sun, 21 Jun 2026 12:33:27 +0300 Subject: [PATCH 14/15] codegen: relocate soroban builtins [auth - extendttl] Signed-off-by: Islam-Imad --- src/codegen/constructor.rs | 1 + src/codegen/expression.rs | 496 +--------------------- src/codegen/mod.rs | 6 +- src/codegen/targets/polkadot/try_catch.rs | 1 + src/codegen/targets/soroban/mod.rs | 488 ++++++++++++++++++++- 5 files changed, 489 insertions(+), 503 deletions(-) diff --git a/src/codegen/constructor.rs b/src/codegen/constructor.rs index 8489a595c..0e029329d 100644 --- a/src/codegen/constructor.rs +++ b/src/codegen/constructor.rs @@ -15,6 +15,7 @@ use solang_parser::pt::Loc; /// This function encodes the constructor arguments and place an instruction in the CFG to /// call the constructor of a contract. +#[allow(clippy::too_many_arguments)] pub(super) fn call_constructor( loc: &Loc, contract_no: usize, diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 9aa59a8f6..c21122bd1 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -4,7 +4,7 @@ use super::revert::{ assert_failure, expr_assert, log_runtime_error, require, PanicCode, SolidityError, }; use super::storage::array_offset; -use super::targets::soroban::encoding::{soroban_encode, soroban_encode_arg}; +use super::targets::soroban::encoding::soroban_encode_arg; use super::Options; use super::{ cfg::{ControlFlowGraph, Instr, InternalCallTy}, @@ -15,7 +15,7 @@ use crate::codegen::constructor::call_constructor; use crate::codegen::interface::TargetCodegen; use crate::codegen::targets::polkadot::return_code as polkadot; use crate::codegen::unused_variable::should_remove_assignment; -use crate::codegen::{Builtin, Expression, HostFunctions}; +use crate::codegen::{Builtin, Expression}; use crate::sema::ast::ExternalCallAccounts; use crate::sema::{ ast, @@ -29,7 +29,6 @@ use crate::sema::{ expression::ResolveTo, }; use crate::Target; -use core::panic; use num_bigint::{BigInt, Sign}; use num_traits::{FromPrimitive, One, ToPrimitive, Zero}; use solang_parser::pt::{self, CodeLocation, Loc}; @@ -2565,497 +2564,6 @@ fn expr_builtin( code(loc, *contract_no, ns, opt) } - ast::Builtin::RequireAuth => { - let var_temp = vartab.temp( - &pt::Identifier { - name: "auth".to_owned(), - loc: *loc, - }, - &Type::Bool, - ); - - let var = Expression::Variable { - loc: *loc, - ty: Type::Address(false), - var_no: var_temp, - }; - let expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); - - let expr = if let Type::StorageRef(_, _) = args[0].ty() { - let expr_no = vartab.temp_anonymous(&Type::Address(false)); - let expr = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Address(false), - var_no: expr_no, - }; - - let storage_load = Instr::LoadStorage { - res: expr_no, - ty: Type::Address(false), - storage: expr.clone(), - storage_type: None, - }; - - cfg.add(vartab, storage_load); - - expr - } else { - expr - }; - - let instr = Instr::Call { - res: vec![var_temp], - return_tys: vec![Type::Void], - call: InternalCallTy::HostFunction { - name: HostFunctions::RequireAuth.name().to_string(), - }, - args: vec![expr], - }; - - cfg.add(vartab, instr); - - var - } - - // This is the trickiest host function to implement. The reason is takes `InvokerContractAuthEntry` enum as an argument. - // let x = SubContractInvocation { - // context: ContractContext { - // contract: c.clone(), - // fn_name: symbol_short!("increment"), - // args: vec![&env, current_contract.into_val(&env)], - // }, - // sub_invocations: vec![&env], - // }; - // let auth_context = auth::InvokerContractAuthEntry::Contract(x); - // Most of the logic done here is just to encode the above struct as the host expects it. - // FIXME: This uses a series of MapNew, and multiple inserts to create the struct. - // This is not efficient and should be optimized. - // Instead, we should use MapNewFromLinearMemory to create the struct in one go. - ast::Builtin::AuthAsCurrContract => { - let symbol_key_1 = Expression::BytesLiteral { - loc: Loc::Codegen, - ty: Type::String, - value: "contract".as_bytes().to_vec(), - }; - let symbol_key_2 = Expression::BytesLiteral { - loc: Loc::Codegen, - ty: Type::String, - value: "fn_name".as_bytes().to_vec(), - }; - let symbol_key_3 = Expression::BytesLiteral { - loc: Loc::Codegen, - ty: Type::String, - value: "args".as_bytes().to_vec(), - }; - - let symbols = soroban_encode( - loc, - vec![symbol_key_1, symbol_key_2, symbol_key_3], - ns, - vartab, - cfg, - false, - ) - .2; - - let contract_value = - expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target); - let fn_name_symbol = - expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target); - - let symbol_string = - if let Expression::BytesLiteral { loc, ty: _, value } = fn_name_symbol { - Expression::BytesLiteral { - loc, - ty: Type::String, - value, - } - } else { - unreachable!() - }; - let encode_func_symbol = - soroban_encode(loc, vec![symbol_string], ns, vartab, cfg, false).2[0].clone(); - - ///////////////////////////////////PREPARE ARGS FOR CONTEXT MAP//////////////////////////////////// - - let mut args_vec = Vec::new(); - for arg in args.iter().skip(2) { - let arg = expression(arg, cfg, contract_no, func, ns, vartab, opt, target); - args_vec.push(arg); - } - - let args_encoded = target.abi_encode(loc, args_vec.clone(), ns, vartab, cfg, false); - - let args_buf = args_encoded.0; - - let args_buf_ptr = Expression::VectorData { - pointer: Box::new(args_buf.clone()), - }; - - let args_buf_extended = Expression::ZeroExt { - loc: Loc::Codegen, - ty: Type::Uint(64), - expr: Box::new(args_buf_ptr.clone()), - }; - - let args_buf_shifted = Expression::ShiftLeft { - loc: Loc::Codegen, - ty: Type::Uint(64), - left: Box::new(args_buf_extended.clone()), - right: Box::new(Expression::NumberLiteral { - loc: Loc::Codegen, - ty: Type::Uint(64), - value: BigInt::from(32), - }), - }; - - let args_buf_pos = Expression::Add { - loc: Loc::Codegen, - ty: Type::Uint(64), - left: Box::new(args_buf_shifted.clone()), - right: Box::new(Expression::NumberLiteral { - loc: Loc::Codegen, - ty: Type::Uint(64), - value: BigInt::from(4), - }), - overflowing: false, - }; - - let args_len = Expression::NumberLiteral { - loc: Loc::Codegen, - ty: Type::Uint(64), - value: BigInt::from(args_vec.len()), - }; - let args_len_encoded = Expression::ShiftLeft { - loc: Loc::Codegen, - ty: Type::Uint(64), - left: Box::new(args_len.clone()), - right: Box::new(Expression::NumberLiteral { - loc: Loc::Codegen, - ty: Type::Uint(64), - value: BigInt::from(32), - }), - }; - let args_len_encoded = Expression::Add { - loc: Loc::Codegen, - ty: Type::Uint(64), - left: Box::new(args_len_encoded.clone()), - right: Box::new(Expression::NumberLiteral { - loc: Loc::Codegen, - ty: Type::Uint(64), - value: BigInt::from(4), - }), - overflowing: false, - }; - - let args_vec_var_no = vartab.temp_anonymous(&Type::Uint(64)); - let args_vec_var = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Uint(64), - var_no: args_vec_var_no, - }; - - let vec_new_from_linear_mem = Instr::Call { - res: vec![args_vec_var_no], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::VectorNewFromLinearMemory.name().to_string(), - }, - args: vec![args_buf_pos.clone(), args_len_encoded], - }; - - cfg.add(vartab, vec_new_from_linear_mem); - - let context_map = vartab.temp_anonymous(&Type::Uint(64)); - let context_map_var = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Uint(64), - var_no: context_map, - }; - - let context_map_new = Instr::Call { - res: vec![context_map], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::MapNew.name().to_string(), - }, - args: vec![], - }; - - cfg.add(vartab, context_map_new); - - let context_map_put = Instr::Call { - res: vec![context_map], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::MapPut.name().to_string(), - }, - args: vec![context_map_var.clone(), symbols[0].clone(), contract_value], - }; - - cfg.add(vartab, context_map_put); - - let context_map_put_2 = Instr::Call { - res: vec![context_map], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::MapPut.name().to_string(), - }, - args: vec![ - context_map_var.clone(), - symbols[1].clone(), - encode_func_symbol, - ], - }; - - cfg.add(vartab, context_map_put_2); - - let context_map_put_3 = Instr::Call { - res: vec![context_map], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::MapPut.name().to_string(), - }, - args: vec![ - context_map_var.clone(), - symbols[2].clone(), - args_vec_var.clone(), - ], - }; - - cfg.add(vartab, context_map_put_3); - - /////////////////////////////////////////////////////////////////////////////////// - - // Now forming "sub invocations" map - // FIXME: This should eventually be fixed to take other sub_invocations as arguments. For now, it is hardcoded to take an empty vector. - - let key_1 = Expression::BytesLiteral { - loc: Loc::Codegen, - ty: Type::String, - value: "context".as_bytes().to_vec(), - }; - - let key_2 = Expression::BytesLiteral { - loc: Loc::Codegen, - ty: Type::String, - value: "sub_invocations".as_bytes().to_vec(), - }; - - let keys = soroban_encode(loc, vec![key_1, key_2], ns, vartab, cfg, false).2; - - let sub_invocations_map = vartab.temp_anonymous(&Type::Uint(64)); - let sub_invocations_map_var = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Uint(64), - var_no: sub_invocations_map, - }; - - let sub_invocations_map_new = Instr::Call { - res: vec![sub_invocations_map], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::MapNew.name().to_string(), - }, - args: vec![], - }; - - cfg.add(vartab, sub_invocations_map_new); - - let sub_invocations_map_put = Instr::Call { - res: vec![sub_invocations_map], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::MapPut.name().to_string(), - }, - args: vec![ - sub_invocations_map_var.clone(), - keys[0].clone(), - context_map_var, - ], - }; - - cfg.add(vartab, sub_invocations_map_put); - - let empy_vec_var = vartab.temp_anonymous(&Type::Uint(64)); - let empty_vec_expr = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Uint(64), - var_no: empy_vec_var, - }; - let empty_vec = Instr::Call { - res: vec![empy_vec_var], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::VectorNew.name().to_string(), - }, - args: vec![], - }; - - cfg.add(vartab, empty_vec); - - let sub_invocations_map_put_2 = Instr::Call { - res: vec![sub_invocations_map], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::MapPut.name().to_string(), - }, - args: vec![ - sub_invocations_map_var.clone(), - keys[1].clone(), - empty_vec_expr, - ], - }; - - cfg.add(vartab, sub_invocations_map_put_2); - - /////////////////////////////////////////////////////////////////////////////////// - - // now forming the enum. The enum is a VecObject[Symbol("Contract"), sub invokations map]. - // FIXME: This should use VecNewFromLinearMemory to create the enum in one go. - - let contract_capitalized = Expression::BytesLiteral { - loc: Loc::Codegen, - ty: Type::String, - value: "Contract".as_bytes().to_vec(), - }; - - let contract_capitalized = - soroban_encode(loc, vec![contract_capitalized], ns, vartab, cfg, false).2[0] - .clone(); - - let enum_vec = vartab.temp_anonymous(&Type::Uint(64)); - let enum_vec_var = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Uint(64), - var_no: enum_vec, - }; - - let enum_vec_new = Instr::Call { - res: vec![enum_vec], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::VectorNew.name().to_string(), - }, - args: vec![], - }; - - cfg.add(vartab, enum_vec_new); - - let enum_vec_put = Instr::Call { - res: vec![enum_vec], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::VecPushBack.name().to_string(), - }, - args: vec![enum_vec_var.clone(), contract_capitalized], - }; - - cfg.add(vartab, enum_vec_put); - - let enum_vec_put_2 = Instr::Call { - res: vec![enum_vec], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::VecPushBack.name().to_string(), - }, - args: vec![enum_vec_var.clone(), sub_invocations_map_var], - }; - - cfg.add(vartab, enum_vec_put_2); - - /////////////////////////////////////////////////////////////////////////////////// - // now put the enum into a vec - - let vec = vartab.temp_anonymous(&Type::Uint(64)); - let vec_var = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Uint(64), - var_no: vec, - }; - - let vec_new = Instr::Call { - res: vec![vec], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::VectorNew.name().to_string(), - }, - args: vec![], - }; - - cfg.add(vartab, vec_new); - - let vec_push_back = Instr::Call { - res: vec![vec], - return_tys: vec![Type::Uint(64)], - call: InternalCallTy::HostFunction { - name: HostFunctions::VecPushBack.name().to_string(), - }, - args: vec![vec_var.clone(), enum_vec_var], - }; - - cfg.add(vartab, vec_push_back); - - /////////////////////////////////////////////////////////////////////////////////// - // now for the moment of truth - the call to the host function auth_as_curr_contract - - let call_res = vartab.temp_anonymous(&Type::Uint(64)); - let call_res_var = Expression::Variable { - loc: Loc::Codegen, - ty: Type::Uint(64), - var_no: call_res, - }; - - let auth_call = Instr::Call { - res: vec![call_res], - return_tys: vec![Type::Void], - call: InternalCallTy::HostFunction { - name: HostFunctions::AuthAsCurrContract.name().to_string(), - }, - args: vec![vec_var], - }; - - cfg.add(vartab, auth_call); - - call_res_var - } - ast::Builtin::ExtendTtl => { - let mut arguments: Vec = args - .iter() - .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, target)) - .collect(); - - // var_no is the first argument of the builtin - let var_no = match arguments[0].clone() { - Expression::NumberLiteral { value, .. } => value, - _ => panic!("First argument of extendTtl() must be a number literal"), - } - .to_usize() - .expect("Unable to convert var_no to usize"); - let var = ns.contracts[contract_no].variables.get(var_no).unwrap(); - let storage_type_usize = match var - .storage_type - .clone() - .expect("Unable to get storage type") { - solang_parser::pt::StorageType::Temporary(_) => 0, - solang_parser::pt::StorageType::Persistent(_) => 1, - solang_parser::pt::StorageType::Instance(_) => panic!("Calling extendTtl() on instance storage is not allowed. Use `extendInstanceTtl()` instead."), - }; - - // append the storage type to the arguments - arguments.push(Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(32), - value: BigInt::from(storage_type_usize), - }); - - Expression::Builtin { - loc: *loc, - tys: tys.to_vec(), - kind: (&builtin).into(), - args: arguments, - } - } _ => { let arguments: Vec = args .iter() diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 3b138fd97..b4245e86e 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -286,9 +286,8 @@ pub fn codegen(ns: &mut Namespace, opt: &Options) { fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options, target: &dyn TargetCodegen) { if !ns.diagnostics.any_errors() && ns.contracts[contract_no].instantiable { layout(contract_no, ns, target); - let errors_before = ns.diagnostics.count_errors(); target.validate_contract(contract_no, ns); - if ns.diagnostics.count_errors() > errors_before { + if ns.diagnostics.any_errors() { return; } @@ -350,9 +349,8 @@ fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options, target: &dyn ns.contracts[contract_no].default_constructor = Some((func, cfg_no)); } - let errors_before = ns.diagnostics.count_errors(); target.validate_cfgs(&all_cfg, ns); - if ns.diagnostics.count_errors() > errors_before { + if ns.diagnostics.any_errors() { return; } diff --git a/src/codegen/targets/polkadot/try_catch.rs b/src/codegen/targets/polkadot/try_catch.rs index 23a8b27fe..c2413536e 100644 --- a/src/codegen/targets/polkadot/try_catch.rs +++ b/src/codegen/targets/polkadot/try_catch.rs @@ -428,6 +428,7 @@ fn insert_success_code_block( /// Insert all catch cases into the CFG (error selectors and catch-all clause). /// Currently, only catching "Error" and "Panic" errors is supported. /// Other errors will lead to the catch-all (if any) or bubble up the error. +#[allow(clippy::too_many_arguments)] fn insert_catch_clauses( try_stmt: &TryCatch, func: &Function, diff --git a/src/codegen/targets/soroban/mod.rs b/src/codegen/targets/soroban/mod.rs index 1aa8ea7d2..e3e746eaa 100644 --- a/src/codegen/targets/soroban/mod.rs +++ b/src/codegen/targets/soroban/mod.rs @@ -17,9 +17,8 @@ use crate::codegen::{Builtin, Expression, HostFunctions}; use crate::sema::ast; use crate::sema::ast::{Function, Namespace, RetrieveType, StructType, Type}; use crate::sema::Recurse; -use crate::Target; use num_bigint::{BigInt, Sign}; -use num_traits::Zero; +use num_traits::{ToPrimitive, Zero}; use solang_parser::helpers::CodeLocation; use solang_parser::{diagnostics::Diagnostic, pt}; use std::collections::BTreeSet; @@ -259,13 +258,13 @@ impl TargetCodegen for SorobanTarget { &self, loc: &pt::Loc, builtin: ast::Builtin, - _args: &[ast::Expression], + args: &[ast::Expression], cfg: &mut ControlFlowGraph, contract_no: usize, - _func: Option<&Function>, + func: Option<&Function>, ns: &Namespace, vartab: &mut Vartable, - _opt: &Options, + opt: &Options, ) -> Option { match builtin { ast::Builtin::GetAddress => { @@ -296,6 +295,485 @@ impl TargetCodegen for SorobanTarget { ); Some(address_var) } + ast::Builtin::RequireAuth => { + let var_temp = vartab.temp( + &pt::Identifier { + name: "auth".to_owned(), + loc: *loc, + }, + &Type::Bool, + ); + + let var = Expression::Variable { + loc: *loc, + ty: Type::Address(false), + var_no: var_temp, + }; + let expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); + + let expr = if let Type::StorageRef(_, _) = args[0].ty() { + let expr_no = vartab.temp_anonymous(&Type::Address(false)); + let expr = Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Address(false), + var_no: expr_no, + }; + + let storage_load = Instr::LoadStorage { + res: expr_no, + ty: Type::Address(false), + storage: expr.clone(), + storage_type: None, + }; + + cfg.add(vartab, storage_load); + + expr + } else { + expr + }; + + let instr = Instr::Call { + res: vec![var_temp], + return_tys: vec![Type::Void], + call: InternalCallTy::HostFunction { + name: HostFunctions::RequireAuth.name().to_string(), + }, + args: vec![expr], + }; + + cfg.add(vartab, instr); + + Some(var) + } + // This is the trickiest host function to implement. The reason is takes `InvokerContractAuthEntry` enum as an argument. + // let x = SubContractInvocation { + // context: ContractContext { + // contract: c.clone(), + // fn_name: symbol_short!("increment"), + // args: vec![&env, current_contract.into_val(&env)], + // }, + // sub_invocations: vec![&env], + // }; + // let auth_context = auth::InvokerContractAuthEntry::Contract(x); + // Most of the logic done here is just to encode the above struct as the host expects it. + // FIXME: This uses a series of MapNew, and multiple inserts to create the struct. + // This is not efficient and should be optimized. + // Instead, we should use MapNewFromLinearMemory to create the struct in one go. + ast::Builtin::AuthAsCurrContract => { + let symbol_key_1 = Expression::BytesLiteral { + loc: pt::Loc::Codegen, + ty: Type::String, + value: "contract".as_bytes().to_vec(), + }; + let symbol_key_2 = Expression::BytesLiteral { + loc: pt::Loc::Codegen, + ty: Type::String, + value: "fn_name".as_bytes().to_vec(), + }; + let symbol_key_3 = Expression::BytesLiteral { + loc: pt::Loc::Codegen, + ty: Type::String, + value: "args".as_bytes().to_vec(), + }; + + let symbols = soroban_encode( + loc, + vec![symbol_key_1, symbol_key_2, symbol_key_3], + ns, + vartab, + cfg, + false, + ) + .2; + + let contract_value = + expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); + let fn_name_symbol = + expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); + + let symbol_string = + if let Expression::BytesLiteral { loc, ty: _, value } = fn_name_symbol { + Expression::BytesLiteral { + loc, + ty: Type::String, + value, + } + } else { + unreachable!() + }; + let encode_func_symbol = + soroban_encode(loc, vec![symbol_string], ns, vartab, cfg, false).2[0].clone(); + + let mut args_vec = Vec::new(); + for arg in args.iter().skip(2) { + let arg = expression(arg, cfg, contract_no, func, ns, vartab, opt, self); + args_vec.push(arg); + } + + let args_encoded = self.abi_encode(loc, args_vec.clone(), ns, vartab, cfg, false); + + let args_buf = args_encoded.0; + + let args_buf_ptr = Expression::VectorData { + pointer: Box::new(args_buf.clone()), + }; + + let args_buf_extended = Expression::ZeroExt { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + expr: Box::new(args_buf_ptr.clone()), + }; + + let args_buf_shifted = Expression::ShiftLeft { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + left: Box::new(args_buf_extended.clone()), + right: Box::new(Expression::NumberLiteral { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + value: BigInt::from(32), + }), + }; + + let args_buf_pos = Expression::Add { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + left: Box::new(args_buf_shifted.clone()), + right: Box::new(Expression::NumberLiteral { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + value: BigInt::from(4), + }), + overflowing: false, + }; + + let args_len = Expression::NumberLiteral { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + value: BigInt::from(args_vec.len()), + }; + let args_len_encoded = Expression::ShiftLeft { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + left: Box::new(args_len.clone()), + right: Box::new(Expression::NumberLiteral { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + value: BigInt::from(32), + }), + }; + let args_len_encoded = Expression::Add { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + left: Box::new(args_len_encoded.clone()), + right: Box::new(Expression::NumberLiteral { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + value: BigInt::from(4), + }), + overflowing: false, + }; + + let args_vec_var_no = vartab.temp_anonymous(&Type::Uint(64)); + let args_vec_var = Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + var_no: args_vec_var_no, + }; + + let vec_new_from_linear_mem = Instr::Call { + res: vec![args_vec_var_no], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::VectorNewFromLinearMemory.name().to_string(), + }, + args: vec![args_buf_pos.clone(), args_len_encoded], + }; + + cfg.add(vartab, vec_new_from_linear_mem); + + let context_map = vartab.temp_anonymous(&Type::Uint(64)); + let context_map_var = Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + var_no: context_map, + }; + + let context_map_new = Instr::Call { + res: vec![context_map], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::MapNew.name().to_string(), + }, + args: vec![], + }; + + cfg.add(vartab, context_map_new); + + let context_map_put = Instr::Call { + res: vec![context_map], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::MapPut.name().to_string(), + }, + args: vec![context_map_var.clone(), symbols[0].clone(), contract_value], + }; + + cfg.add(vartab, context_map_put); + + let context_map_put_2 = Instr::Call { + res: vec![context_map], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::MapPut.name().to_string(), + }, + args: vec![ + context_map_var.clone(), + symbols[1].clone(), + encode_func_symbol, + ], + }; + + cfg.add(vartab, context_map_put_2); + + let context_map_put_3 = Instr::Call { + res: vec![context_map], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::MapPut.name().to_string(), + }, + args: vec![ + context_map_var.clone(), + symbols[2].clone(), + args_vec_var.clone(), + ], + }; + + cfg.add(vartab, context_map_put_3); + + // Now forming "sub invocations" map + // FIXME: This should eventually be fixed to take other sub_invocations as arguments. For now, it is hardcoded to take an empty vector. + + let key_1 = Expression::BytesLiteral { + loc: pt::Loc::Codegen, + ty: Type::String, + value: "context".as_bytes().to_vec(), + }; + + let key_2 = Expression::BytesLiteral { + loc: pt::Loc::Codegen, + ty: Type::String, + value: "sub_invocations".as_bytes().to_vec(), + }; + + let keys = soroban_encode(loc, vec![key_1, key_2], ns, vartab, cfg, false).2; + + let sub_invocations_map = vartab.temp_anonymous(&Type::Uint(64)); + let sub_invocations_map_var = Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + var_no: sub_invocations_map, + }; + + let sub_invocations_map_new = Instr::Call { + res: vec![sub_invocations_map], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::MapNew.name().to_string(), + }, + args: vec![], + }; + + cfg.add(vartab, sub_invocations_map_new); + + let sub_invocations_map_put = Instr::Call { + res: vec![sub_invocations_map], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::MapPut.name().to_string(), + }, + args: vec![ + sub_invocations_map_var.clone(), + keys[0].clone(), + context_map_var, + ], + }; + + cfg.add(vartab, sub_invocations_map_put); + + let empy_vec_var = vartab.temp_anonymous(&Type::Uint(64)); + let empty_vec_expr = Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + var_no: empy_vec_var, + }; + let empty_vec = Instr::Call { + res: vec![empy_vec_var], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::VectorNew.name().to_string(), + }, + args: vec![], + }; + + cfg.add(vartab, empty_vec); + + let sub_invocations_map_put_2 = Instr::Call { + res: vec![sub_invocations_map], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::MapPut.name().to_string(), + }, + args: vec![ + sub_invocations_map_var.clone(), + keys[1].clone(), + empty_vec_expr, + ], + }; + + cfg.add(vartab, sub_invocations_map_put_2); + + // now forming the enum. The enum is a VecObject[Symbol("Contract"), sub invokations map]. + // FIXME: This should use VecNewFromLinearMemory to create the enum in one go. + + let contract_capitalized = Expression::BytesLiteral { + loc: pt::Loc::Codegen, + ty: Type::String, + value: "Contract".as_bytes().to_vec(), + }; + + let contract_capitalized = + soroban_encode(loc, vec![contract_capitalized], ns, vartab, cfg, false).2[0] + .clone(); + + let enum_vec = vartab.temp_anonymous(&Type::Uint(64)); + let enum_vec_var = Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + var_no: enum_vec, + }; + + let enum_vec_new = Instr::Call { + res: vec![enum_vec], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::VectorNew.name().to_string(), + }, + args: vec![], + }; + + cfg.add(vartab, enum_vec_new); + + let enum_vec_put = Instr::Call { + res: vec![enum_vec], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::VecPushBack.name().to_string(), + }, + args: vec![enum_vec_var.clone(), contract_capitalized], + }; + + cfg.add(vartab, enum_vec_put); + + let enum_vec_put_2 = Instr::Call { + res: vec![enum_vec], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::VecPushBack.name().to_string(), + }, + args: vec![enum_vec_var.clone(), sub_invocations_map_var], + }; + + cfg.add(vartab, enum_vec_put_2); + + let vec = vartab.temp_anonymous(&Type::Uint(64)); + let vec_var = Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + var_no: vec, + }; + + let vec_new = Instr::Call { + res: vec![vec], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::VectorNew.name().to_string(), + }, + args: vec![], + }; + + cfg.add(vartab, vec_new); + + let vec_push_back = Instr::Call { + res: vec![vec], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::VecPushBack.name().to_string(), + }, + args: vec![vec_var.clone(), enum_vec_var], + }; + + cfg.add(vartab, vec_push_back); + + let call_res = vartab.temp_anonymous(&Type::Uint(64)); + let call_res_var = Expression::Variable { + loc: pt::Loc::Codegen, + ty: Type::Uint(64), + var_no: call_res, + }; + + let auth_call = Instr::Call { + res: vec![call_res], + return_tys: vec![Type::Void], + call: InternalCallTy::HostFunction { + name: HostFunctions::AuthAsCurrContract.name().to_string(), + }, + args: vec![vec_var], + }; + + cfg.add(vartab, auth_call); + + Some(call_res_var) + } + ast::Builtin::ExtendTtl => { + let mut arguments: Vec = args + .iter() + .map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, self)) + .collect(); + + let var_no = match arguments[0].clone() { + Expression::NumberLiteral { value, .. } => value, + _ => panic!("First argument of extendTtl() must be a number literal"), + } + .to_usize() + .expect("Unable to convert var_no to usize"); + let var = ns.contracts[contract_no].variables.get(var_no).unwrap(); + let storage_type_usize = match var + .storage_type + .clone() + .expect("Unable to get storage type") + { + pt::StorageType::Temporary(_) => 0, + pt::StorageType::Persistent(_) => 1, + pt::StorageType::Instance(_) => panic!( + "Calling extendTtl() on instance storage is not allowed. Use `extendInstanceTtl()` instead." + ), + }; + + arguments.push(Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(32), + value: BigInt::from(storage_type_usize), + }); + + Some(Expression::Builtin { + loc: *loc, + tys: vec![Type::Int(64)], + kind: (&builtin).into(), + args: arguments, + }) + } _ => None, } } From 9dad537e08487d084cb7c5711dbb5d8e6a2ed023 Mon Sep 17 00:00:00 2001 From: Islam-Imad Date: Mon, 22 Jun 2026 19:27:44 +0300 Subject: [PATCH 15/15] codegen: add EvmTarget, trim TargetCodegen defaults Signed-off-by: Islam-Imad --- src/codegen/interface.rs | 79 +++-- src/codegen/targets/mod.rs | 6 +- src/codegen/targets/polkadot/mod.rs | 427 ++++++++++++++-------------- src/codegen/targets/solana/mod.rs | 135 --------- 4 files changed, 284 insertions(+), 363 deletions(-) diff --git a/src/codegen/interface.rs b/src/codegen/interface.rs index d1503cbfd..79c97e6c4 100644 --- a/src/codegen/interface.rs +++ b/src/codegen/interface.rs @@ -5,6 +5,7 @@ use crate::codegen::vartable::Vartable; use crate::codegen::{Expression, Options}; use crate::sema::ast::{self, Function, Namespace, StructType, Type}; use num_bigint::BigInt; +use num_traits::Zero; use solang_parser::pt::{self, Loc}; pub(crate) trait EventEmitter { @@ -23,10 +24,10 @@ pub(crate) trait EventEmitter { pub(crate) trait TargetCodegen { /// Pre-CFG validation. Runs after storage layout, before any CFG is built. - fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace); + fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace) {} /// Post-CFG validation; needs the freshly built CFGs. - fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace); + fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace) {} /// Build the dispatcher CFG(s) appended after every function CFG is generated. fn function_dispatch( @@ -37,9 +38,11 @@ pub(crate) trait TargetCodegen { opt: &Options, ) -> Vec; - fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options); + fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {} - fn selector_hash_algorithm(&self) -> ast::Builtin; + fn selector_hash_algorithm(&self) -> ast::Builtin { + ast::Builtin::Keccak256 + } fn lower_storage_array_length( &self, @@ -52,13 +55,21 @@ pub(crate) trait TargetCodegen { ns: &Namespace, ) -> Expression; - fn initial_storage_slot(&self) -> BigInt; + fn initial_storage_slot(&self) -> BigInt { + BigInt::zero() + } - fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt; + fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt { + slot + } - fn default_gas_builtin(&self) -> BigInt; + fn default_gas_builtin(&self) -> BigInt { + BigInt::zero() + } - fn lower_print_expr(&self, expr: Expression) -> Expression; + fn lower_print_expr(&self, expr: Expression) -> Expression { + expr + } fn lower_mapping_subscript( &self, @@ -67,7 +78,15 @@ pub(crate) trait TargetCodegen { array_ty: &Type, array: Expression, index: Expression, - ) -> Expression; + ) -> Expression { + Expression::Subscript { + loc: *loc, + ty: elem_ty.clone(), + array_ty: array_ty.clone(), + expr: Box::new(array), + index: Box::new(index), + } + } fn lower_builtin( &self, @@ -80,7 +99,9 @@ pub(crate) trait TargetCodegen { _ns: &Namespace, _vartab: &mut Vartable, _opt: &Options, - ) -> Option; + ) -> Option { + None + } fn lower_load( &self, @@ -88,7 +109,9 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, _ns: &Namespace, - ) -> Expression; + ) -> Expression { + load + } fn prepare_storage_value( &self, @@ -97,7 +120,9 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, _ns: &Namespace, - ) -> Expression; + ) -> Expression { + value + } fn default_storage_value( &self, @@ -106,7 +131,9 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, _ns: &Namespace, - ) -> Option; + ) -> Option { + None + } fn abi_encode( &self, @@ -116,7 +143,9 @@ pub(crate) trait TargetCodegen { vartab: &mut Vartable, cfg: &mut ControlFlowGraph, packed: bool, - ) -> (Expression, Expression); + ) -> (Expression, Expression) { + crate::codegen::encoding::abi_encode(loc, args, ns, vartab, cfg, packed) + } fn abi_decode( &self, @@ -127,7 +156,9 @@ pub(crate) trait TargetCodegen { vartab: &mut Vartable, cfg: &mut ControlFlowGraph, buffer_size_expr: Option, - ) -> Vec; + ) -> Vec { + crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) + } fn storage_array_push( &self, @@ -172,7 +203,19 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, ns: &Namespace, - ) -> Expression; + ) -> Expression { + crate::codegen::storage::array_offset( + loc, + Expression::Keccak256 { + loc: *loc, + ty: slot_ty.clone(), + exprs: vec![var_expr.clone()], + }, + index, + elem_ty.clone(), + ns, + ) + } fn lower_storage_struct_member( &self, @@ -191,5 +234,7 @@ pub(crate) trait TargetCodegen { _cfg: &mut ControlFlowGraph, _vartab: &mut Vartable, _ns: &Namespace, - ) -> Expression; + ) -> Expression { + value + } } diff --git a/src/codegen/targets/mod.rs b/src/codegen/targets/mod.rs index f7386d267..9eb2a2673 100644 --- a/src/codegen/targets/mod.rs +++ b/src/codegen/targets/mod.rs @@ -10,7 +10,7 @@ use crate::codegen::interface::TargetCodegen; use crate::sema::ast::Namespace; use crate::Target; -use self::polkadot::PolkadotTarget; +use self::polkadot::{EvmTarget, PolkadotTarget}; use self::solana::SolanaTarget; use self::soroban::SorobanTarget; @@ -18,7 +18,7 @@ pub(crate) fn make_target(ns: &Namespace) -> Box { match &ns.target { Target::Soroban => Box::new(SorobanTarget), Target::Solana => Box::new(SolanaTarget), - Target::Polkadot { .. } => Box::new(PolkadotTarget { is_evm: false }), - Target::EVM => Box::new(PolkadotTarget { is_evm: true }), + Target::Polkadot { .. } => Box::new(PolkadotTarget), + Target::EVM => Box::new(EvmTarget(PolkadotTarget)), } } diff --git a/src/codegen/targets/polkadot/mod.rs b/src/codegen/targets/polkadot/mod.rs index a6ec59147..5e3b9e42d 100644 --- a/src/codegen/targets/polkadot/mod.rs +++ b/src/codegen/targets/polkadot/mod.rs @@ -19,14 +19,11 @@ use crate::sema::ast::{ CallTy, ExternalCallAccounts, Function, Namespace, RetrieveType, StructType, Type, }; use num_bigint::BigInt; -use num_traits::Zero; use solang_parser::pt::{self, Loc}; use self::events::PolkadotEventEmitter; -pub(crate) struct PolkadotTarget { - pub(crate) is_evm: bool, -} +pub(crate) struct PolkadotTarget; impl TargetCodegen for PolkadotTarget { fn function_dispatch( @@ -109,44 +106,20 @@ impl TargetCodegen for PolkadotTarget { Box::new(PolkadotEventEmitter { args, ns, event_no }) } - fn default_gas_builtin(&self) -> BigInt { - if self.is_evm { - BigInt::from(i64::MAX) - } else { - BigInt::zero() - } - } - - fn lower_print_expr(&self, expr: Expression) -> Expression { - if self.is_evm { - expr - } else { - crate::codegen::expression::add_prefix_and_delimiter_to_print(expr) - } - } - + // Polkadot hashes (array, index) with keccak256 to form the storage key. + // All other targets use a direct Subscript (trait default). fn lower_mapping_subscript( &self, loc: &Loc, - elem_ty: &Type, + _elem_ty: &Type, array_ty: &Type, array: Expression, index: Expression, ) -> Expression { - if self.is_evm { - Expression::Subscript { - loc: *loc, - ty: elem_ty.clone(), - array_ty: array_ty.clone(), - expr: Box::new(array), - index: Box::new(index), - } - } else { - Expression::Keccak256 { - loc: *loc, - ty: array_ty.clone(), - exprs: vec![array, index], - } + Expression::Keccak256 { + loc: *loc, + ty: array_ty.clone(), + exprs: vec![array, index], } } @@ -163,19 +136,6 @@ impl TargetCodegen for PolkadotTarget { opt: &Options, ) -> Option { match builtin { - ast::Builtin::Gasprice if self.is_evm && args.len() == 1 => { - Some(crate::codegen::expression::builtin_evm_gasprice( - loc, - args, - cfg, - contract_no, - func, - ns, - vartab, - opt, - self, - )) - } ast::Builtin::PayableSend => { Some(self.payable_send(loc, args, cfg, contract_no, func, ns, vartab, opt)) } @@ -215,14 +175,9 @@ impl TargetCodegen for PolkadotTarget { } } - fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace) {} - - fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace) {} - - fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {} - - fn selector_hash_algorithm(&self) -> ast::Builtin { - ast::Builtin::Keccak256 + // Polkadot prepends a `print:`/`,\n` delimiter; EVM and others pass through (trait default). + fn lower_print_expr(&self, expr: Expression) -> Expression { + crate::codegen::expression::add_prefix_and_delimiter_to_print(expr) } fn lower_storage_array_length( @@ -237,110 +192,200 @@ impl TargetCodegen for PolkadotTarget { ) -> Expression { load_storage(loc, &ns.storage_type(), array, cfg, vartab, None, ns, self) } +} - fn initial_storage_slot(&self) -> BigInt { - BigInt::zero() - } - - fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt { - slot - } - - fn lower_load( +impl PolkadotTarget { + fn payable_send( &self, - load: Expression, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - _ns: &Namespace, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, ) -> Expression { - load + let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); + let success = vartab.temp( + &pt::Identifier { + loc: *loc, + name: "success".to_owned(), + }, + &Type::Uint(32), + ); + cfg.add( + vartab, + Instr::ValueTransfer { + success: Some(success), + address, + value, + }, + ); + return_code::check_transfer_ret(loc, success, cfg, ns, opt, vartab, false).unwrap() } - fn prepare_storage_value( + fn payable_transfer( &self, - value: Expression, - _dest: &Expression, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - _ns: &Namespace, + loc: &Loc, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, + ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, ) -> Expression { - value + let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); + let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); + let success = vartab.temp_name("success", &Type::Uint(32)); + cfg.add( + vartab, + Instr::ValueTransfer { + success: Some(success), + address, + value, + }, + ); + return_code::check_transfer_ret(loc, success, cfg, ns, opt, vartab, true); + Expression::Poison } +} - fn default_storage_value( - &self, - _loc: &Loc, - _ty: &Type, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - _ns: &Namespace, - ) -> Option { - None +pub(crate) struct EvmTarget(pub(crate) PolkadotTarget); + +impl TargetCodegen for EvmTarget { + fn default_gas_builtin(&self) -> BigInt { + BigInt::from(i64::MAX) } - fn abi_encode( + fn lower_builtin( &self, loc: &Loc, - args: Vec, + builtin: ast::Builtin, + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, ns: &Namespace, vartab: &mut Vartable, - cfg: &mut ControlFlowGraph, - packed: bool, - ) -> (Expression, Expression) { - crate::codegen::encoding::abi_encode(loc, args, ns, vartab, cfg, packed) + opt: &Options, + ) -> Option { + match builtin { + ast::Builtin::Gasprice if args.len() == 1 => { + Some(crate::codegen::expression::builtin_evm_gasprice( + loc, + args, + cfg, + contract_no, + func, + ns, + vartab, + opt, + self, + )) + } + ast::Builtin::PayableSend => { + Some(self.payable_send(loc, args, cfg, contract_no, func, ns, vartab, opt)) + } + ast::Builtin::PayableTransfer => { + Some(self.payable_transfer(loc, args, cfg, contract_no, func, ns, vartab, opt)) + } + _ => None, + } } - fn abi_decode( + fn function_dispatch( + &self, + contract_no: usize, + all_cfg: &mut [ControlFlowGraph], + ns: &mut Namespace, + opt: &Options, + ) -> Vec { + self.0.function_dispatch(contract_no, all_cfg, ns, opt) + } + + fn storage_array_push( &self, loc: &Loc, - buffer: &Expression, - types: &[Type], + args: &[ast::Expression], + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, ns: &Namespace, vartab: &mut Vartable, - cfg: &mut ControlFlowGraph, - buffer_size_expr: Option, - ) -> Vec { - crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) + opt: &Options, + ) -> Expression { + self.0 + .storage_array_push(loc, args, cfg, contract_no, func, ns, vartab, opt) } - fn storage_array_entry_offset( + fn storage_array_pop( &self, loc: &Loc, - var_expr: &Expression, - index: Expression, - elem_ty: &Type, - slot_ty: &Type, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, + args: &[ast::Expression], + return_ty: &Type, + cfg: &mut ControlFlowGraph, + contract_no: usize, + func: Option<&Function>, ns: &Namespace, + vartab: &mut Vartable, + opt: &Options, ) -> Expression { - crate::codegen::storage::array_offset( + self.0.storage_array_pop( loc, - Expression::Keccak256 { - loc: *loc, - ty: slot_ty.clone(), - exprs: vec![var_expr.clone()], - }, - index, - elem_ty.clone(), + args, + return_ty, + cfg, + contract_no, + func, ns, + vartab, + opt, ) } - fn lower_load_storage( + fn event_emitter<'a>( &self, - value: Expression, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - _ns: &Namespace, + loc: &pt::Loc, + event_no: usize, + args: &'a [ast::Expression], + ns: &'a Namespace, + ) -> Box { + self.0.event_emitter(loc, event_no, args, ns) + } + + fn lower_storage_struct_member( + &self, + loc: &Loc, + var_expr: Expression, + struct_ty: &StructType, + field_no: usize, + ns: &Namespace, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, ) -> Expression { - value + self.0 + .lower_storage_struct_member(loc, var_expr, struct_ty, field_no, ns, cfg, vartab) + } + + fn lower_storage_array_length( + &self, + loc: &Loc, + ty: &Type, + array: Expression, + elem_ty: &Type, + cfg: &mut ControlFlowGraph, + vartab: &mut Vartable, + ns: &Namespace, + ) -> Expression { + self.0 + .lower_storage_array_length(loc, ty, array, elem_ty, cfg, vartab, ns) } } -impl PolkadotTarget { - /// Lower `address.send(value)` (`Builtin::PayableSend`). EVM routes through an external - /// call with an empty payload; Polkadot emits a `ValueTransfer` and checks the return code. +impl EvmTarget { fn payable_send( &self, loc: &Loc, @@ -361,59 +406,42 @@ impl PolkadotTarget { }, &Type::Uint(32), ); - - // Ethereum can only transfer via external call - if self.is_evm { - cfg.add( - vartab, - Instr::ExternalCall { - loc: *loc, - success: Some(success), - address: Some(address), - accounts: ExternalCallAccounts::AbsentArgument, - seeds: None, - payload: Expression::AllocDynamicBytes { - loc: *loc, - ty: Type::DynamicBytes, - size: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(32), - value: BigInt::from(0), - }), - initializer: Some(vec![]), - }, - value, - gas: Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(64), - value: BigInt::from(i64::MAX), - }, - callty: CallTy::Regular, - contract_function_no: None, - flags: None, - }, - ); - return Expression::Variable { - loc: *loc, - ty: Type::Bool, - var_no: success, - }; - } - cfg.add( vartab, - Instr::ValueTransfer { + Instr::ExternalCall { + loc: *loc, success: Some(success), - address, + address: Some(address), + accounts: ExternalCallAccounts::AbsentArgument, + seeds: None, + payload: Expression::AllocDynamicBytes { + loc: *loc, + ty: Type::DynamicBytes, + size: Box::new(Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(32), + value: BigInt::from(0), + }), + initializer: Some(vec![]), + }, value, + gas: Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(64), + value: BigInt::from(i64::MAX), + }, + callty: CallTy::Regular, + contract_function_no: None, + flags: None, }, ); - - return_code::check_transfer_ret(loc, success, cfg, ns, opt, vartab, false).unwrap() + Expression::Variable { + loc: *loc, + ty: Type::Bool, + var_no: success, + } } - /// Lower `address.transfer(value)` (`Builtin::PayableTransfer`). EVM routes through an - /// external call; Polkadot emits a `ValueTransfer` and reverts on a non-zero return code. fn payable_transfer( &self, loc: &Loc, @@ -427,52 +455,35 @@ impl PolkadotTarget { ) -> Expression { let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self); let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self); - if self.is_evm { - // Ethereum can only transfer via external call - cfg.add( - vartab, - Instr::ExternalCall { + cfg.add( + vartab, + Instr::ExternalCall { + loc: *loc, + success: None, + accounts: ExternalCallAccounts::AbsentArgument, + seeds: None, + address: Some(address), + payload: Expression::AllocDynamicBytes { loc: *loc, - success: None, - accounts: ExternalCallAccounts::AbsentArgument, - seeds: None, - address: Some(address), - payload: Expression::AllocDynamicBytes { + ty: Type::DynamicBytes, + size: Box::new(Expression::NumberLiteral { loc: *loc, - ty: Type::DynamicBytes, - size: Box::new(Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(32), - value: BigInt::from(0), - }), - initializer: Some(vec![]), - }, - value, - gas: Expression::NumberLiteral { - loc: *loc, - ty: Type::Uint(64), - value: BigInt::from(i64::MAX), - }, - callty: CallTy::Regular, - contract_function_no: None, - flags: None, + ty: Type::Uint(32), + value: BigInt::from(0), + }), + initializer: Some(vec![]), }, - ); - return Expression::Poison; - } - - let success = vartab.temp_name("success", &Type::Uint(32)); - cfg.add( - vartab, - Instr::ValueTransfer { - success: Some(success), - address, value, + gas: Expression::NumberLiteral { + loc: *loc, + ty: Type::Uint(64), + value: BigInt::from(i64::MAX), + }, + callty: CallTy::Regular, + contract_function_no: None, + flags: None, }, ); - - return_code::check_transfer_ret(loc, success, cfg, ns, opt, vartab, true); - Expression::Poison } } diff --git a/src/codegen/targets/solana/mod.rs b/src/codegen/targets/solana/mod.rs index bfc018a28..b496fb34a 100644 --- a/src/codegen/targets/solana/mod.rs +++ b/src/codegen/targets/solana/mod.rs @@ -167,139 +167,4 @@ impl TargetCodegen for SolanaTarget { }), } } - - fn validate_contract(&self, _contract_no: usize, _ns: &mut Namespace) {} - - fn validate_cfgs(&self, _all_cfg: &[ControlFlowGraph], _ns: &mut Namespace) {} - - fn default_gas_builtin(&self) -> BigInt { - BigInt::zero() - } - - fn lower_print_expr(&self, expr: Expression) -> Expression { - expr - } - - fn lower_mapping_subscript( - &self, - loc: &Loc, - elem_ty: &Type, - array_ty: &Type, - array: Expression, - index: Expression, - ) -> Expression { - Expression::Subscript { - loc: *loc, - ty: elem_ty.clone(), - array_ty: array_ty.clone(), - expr: Box::new(array), - index: Box::new(index), - } - } - - fn lower_builtin( - &self, - _loc: &Loc, - _builtin: ast::Builtin, - _args: &[ast::Expression], - _cfg: &mut ControlFlowGraph, - _contract_no: usize, - _func: Option<&Function>, - _ns: &Namespace, - _vartab: &mut Vartable, - _opt: &Options, - ) -> Option { - None - } - - fn lower_load( - &self, - load: Expression, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - _ns: &Namespace, - ) -> Expression { - load - } - - fn prepare_storage_value( - &self, - value: Expression, - _dest: &Expression, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - _ns: &Namespace, - ) -> Expression { - value - } - - fn default_storage_value( - &self, - _loc: &Loc, - _ty: &Type, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - _ns: &Namespace, - ) -> Option { - None - } - - fn abi_encode( - &self, - loc: &Loc, - args: Vec, - ns: &Namespace, - vartab: &mut Vartable, - cfg: &mut ControlFlowGraph, - packed: bool, - ) -> (Expression, Expression) { - crate::codegen::encoding::abi_encode(loc, args, ns, vartab, cfg, packed) - } - - fn abi_decode( - &self, - loc: &Loc, - buffer: &Expression, - types: &[Type], - ns: &Namespace, - vartab: &mut Vartable, - cfg: &mut ControlFlowGraph, - buffer_size_expr: Option, - ) -> Vec { - crate::codegen::encoding::abi_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr) - } - - fn storage_array_entry_offset( - &self, - loc: &Loc, - var_expr: &Expression, - index: Expression, - elem_ty: &Type, - slot_ty: &Type, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - ns: &Namespace, - ) -> Expression { - crate::codegen::storage::array_offset( - loc, - Expression::Keccak256 { - loc: *loc, - ty: slot_ty.clone(), - exprs: vec![var_expr.clone()], - }, - index, - elem_ty.clone(), - ns, - ) - } - - fn lower_load_storage( - &self, - value: Expression, - _cfg: &mut ControlFlowGraph, - _vartab: &mut Vartable, - _ns: &Namespace, - ) -> Expression { - value - } }