Skip to content

Commit 3166e4e

Browse files
committed
Use native sonatina struct types and GEP for memory place addressing
Migrate lower_place_address to emit IntToPtr + GEP + PtrToInt for memory-addressed struct/tuple/array field access instead of manual offset arithmetic (Add instructions). - Add fe_ty_to_sonatina type mapping: Fe TyId -> sonatina compound types (structs via declare_struct_type, arrays via declare_array_type, scalars as I256, enums return None to trigger fallback) - Cache type mappings at module level to avoid duplicate struct defs - GEP path activates for memory places with only Field/Index projections - Storage, transient storage, and enum paths unchanged (manual arithmetic) - Extract arithmetic path into lower_place_address_arithmetic
1 parent fd3e681 commit 3166e4e

3 files changed

Lines changed: 301 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/codegen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ rustc-hash.workspace = true
1414
sonatina-ir.workspace = true
1515
sonatina-triple.workspace = true
1616
sonatina-codegen.workspace = true
17+
smallvec1.workspace = true
1718
tracing.workspace = true
1819

1920
[dev-dependencies]

crates/codegen/src/sonatina/mod.rs

Lines changed: 299 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,17 @@ use mir::ir::{AddressSpaceKind, IntrinsicOp, Place, SyntheticValue};
1616
use mir::{MirModule, layout, layout::TargetDataLayout, lower_module};
1717
use num_bigint::BigUint;
1818
use rustc_hash::{FxHashMap, FxHashSet};
19+
use smallvec1::SmallVec;
1920
use sonatina_ir::{
2021
BlockId, I256, Module, Signature, Type, ValueId,
2122
builder::{ModuleBuilder, Variable},
2223
func_cursor::InstInserter,
2324
inst::{
2425
arith::{Add, Mul, Neg, Shl, Shr, Sub},
25-
cast::{Sext, Trunc, Zext},
26+
cast::{IntToPtr, PtrToInt, Sext, Trunc, Zext},
2627
cmp::{Eq, Gt, IsZero, Lt, Ne},
2728
control_flow::{Br, Call, Jump, Return},
28-
data::{Mload, Mstore, SymAddr, SymSize, SymbolRef},
29+
data::{Gep, Mload, Mstore, SymAddr, SymSize, SymbolRef},
2930
evm::{
3031
EvmAddress, EvmBaseFee, EvmBlockHash, EvmCall, EvmCallValue, EvmCalldataCopy,
3132
EvmCalldataLoad, EvmCalldataSize, EvmCaller, EvmChainId, EvmCodeCopy, EvmCodeSize,
@@ -261,6 +262,10 @@ struct ModuleLowerer<'db, 'a> {
261262
///
262263
/// These entry functions emit `evm_stop` instead of internal `Return`.
263264
entry_func_idxs: FxHashSet<usize>,
265+
/// Cache for Fe type → sonatina type mapping (GEP support).
266+
gep_type_cache: FxHashMap<String, Option<Type>>,
267+
/// Counter for generating unique sonatina struct type names.
268+
gep_name_counter: usize,
264269
}
265270

266271
impl<'db, 'a> ModuleLowerer<'db, 'a> {
@@ -282,6 +287,8 @@ impl<'db, 'a> ModuleLowerer<'db, 'a> {
282287
returns_value_map: FxHashMap::default(),
283288
runtime_param_masks: FxHashMap::default(),
284289
entry_func_idxs: FxHashSet::default(),
290+
gep_type_cache: FxHashMap::default(),
291+
gep_name_counter: 0,
285292
}
286293
}
287294

@@ -840,7 +847,7 @@ impl<'db, 'a> ModuleLowerer<'db, 'a> {
840847
/// If `is_entry` is true, this is the entry function executed directly by the EVM.
841848
/// Entry functions emit `evm_stop` instead of internal `Return` for their terminators.
842849
fn lower_function(
843-
&self,
850+
&mut self,
844851
func_ref: FuncRef,
845852
func: &mir::MirFunction<'db>,
846853
is_entry: bool,
@@ -938,6 +945,8 @@ impl<'db, 'a> ModuleLowerer<'db, 'a> {
938945
block_map: &block_map,
939946
is,
940947
is_entry,
948+
gep_type_cache: &mut self.gep_type_cache,
949+
gep_name_counter: &mut self.gep_name_counter,
941950
};
942951

943952
for (idx, block) in ctx.body.blocks.iter().enumerate() {
@@ -972,6 +981,10 @@ struct LowerCtx<'a, 'db, C: sonatina_ir::func_cursor::FuncCursor> {
972981
block_map: &'a FxHashMap<mir::BasicBlockId, BlockId>,
973982
is: &'a sonatina_ir::inst::evm::inst_set::EvmInstSet,
974983
is_entry: bool,
984+
/// Cache for Fe type → sonatina type mapping (GEP support).
985+
gep_type_cache: &'a mut FxHashMap<String, Option<Type>>,
986+
/// Counter for generating unique sonatina struct type names.
987+
gep_name_counter: &'a mut usize,
975988
}
976989

977990
/// Lower a MIR instruction.
@@ -2090,6 +2103,140 @@ fn apply_to_word<'db, C: sonatina_ir::func_cursor::FuncCursor>(
20902103
}
20912104
}
20922105

2106+
/// Maps a Fe type to a sonatina struct/array type for GEP-based addressing.
2107+
///
2108+
/// Returns `Some(Type)` for types that can be represented as sonatina compound types
2109+
/// (structs, tuples, arrays of such). Returns `None` for types where we should fall
2110+
/// back to manual offset arithmetic (enums, zero-sized types, plain scalars).
2111+
///
2112+
/// Uses a cache to avoid creating duplicate struct type definitions. The cache is keyed
2113+
/// by the Fe `TyId` debug representation (salsa-interned, so stable within a session).
2114+
fn fe_ty_to_sonatina<'db, C: sonatina_ir::func_cursor::FuncCursor>(
2115+
fb: &mut sonatina_ir::builder::FunctionBuilder<C>,
2116+
db: &'db DriverDataBase,
2117+
target_layout: &TargetDataLayout,
2118+
ty: hir::analysis::ty::ty_def::TyId<'db>,
2119+
cache: &mut FxHashMap<String, Option<Type>>,
2120+
name_counter: &mut usize,
2121+
) -> Option<Type> {
2122+
let cache_key = format!("{ty:?}");
2123+
if let Some(cached) = cache.get(&cache_key) {
2124+
return *cached;
2125+
}
2126+
2127+
let result = fe_ty_to_sonatina_inner(fb, db, target_layout, ty, cache, name_counter);
2128+
cache.insert(cache_key, result);
2129+
result
2130+
}
2131+
2132+
fn fe_ty_to_sonatina_inner<'db, C: sonatina_ir::func_cursor::FuncCursor>(
2133+
fb: &mut sonatina_ir::builder::FunctionBuilder<C>,
2134+
db: &'db DriverDataBase,
2135+
target_layout: &TargetDataLayout,
2136+
ty: hir::analysis::ty::ty_def::TyId<'db>,
2137+
cache: &mut FxHashMap<String, Option<Type>>,
2138+
name_counter: &mut usize,
2139+
) -> Option<Type> {
2140+
if is_erased_runtime_ty(db, target_layout, ty) {
2141+
return Some(Type::Unit);
2142+
}
2143+
2144+
let base_ty = ty.base_ty(db);
2145+
match base_ty.data(db) {
2146+
TyData::TyBase(TyBase::Prim(prim)) => match prim {
2147+
// Scalars: all map to I256 on EVM
2148+
PrimTy::Bool
2149+
| PrimTy::U8
2150+
| PrimTy::I8
2151+
| PrimTy::U16
2152+
| PrimTy::I16
2153+
| PrimTy::U32
2154+
| PrimTy::I32
2155+
| PrimTy::U64
2156+
| PrimTy::I64
2157+
| PrimTy::U128
2158+
| PrimTy::I128
2159+
| PrimTy::U256
2160+
| PrimTy::I256
2161+
| PrimTy::Usize
2162+
| PrimTy::Isize
2163+
| PrimTy::Ptr => Some(Type::I256),
2164+
PrimTy::String => None,
2165+
PrimTy::Tuple(_) => {
2166+
let field_tys = ty.field_types(db);
2167+
if field_tys.is_empty() {
2168+
return Some(Type::Unit);
2169+
}
2170+
let mut sonatina_fields = Vec::with_capacity(field_tys.len());
2171+
for ft in &field_tys {
2172+
sonatina_fields.push(fe_ty_to_sonatina(
2173+
fb,
2174+
db,
2175+
target_layout,
2176+
*ft,
2177+
cache,
2178+
name_counter,
2179+
)?);
2180+
}
2181+
let id = *name_counter;
2182+
*name_counter += 1;
2183+
Some(fb.declare_struct_type(&format!("__fe_tuple_{id}"), &sonatina_fields, false))
2184+
}
2185+
PrimTy::Array => {
2186+
let elem_ty = layout::array_elem_ty(db, ty)?;
2187+
let len = layout::array_len(db, ty)?;
2188+
let sonatina_elem =
2189+
fe_ty_to_sonatina(fb, db, target_layout, elem_ty, cache, name_counter)?;
2190+
Some(fb.declare_array_type(sonatina_elem, len))
2191+
}
2192+
},
2193+
TyData::TyBase(TyBase::Adt(adt_def)) => {
2194+
match adt_def.adt_ref(db) {
2195+
AdtRef::Struct(_) => {
2196+
let field_tys = ty.field_types(db);
2197+
let mut sonatina_fields = Vec::with_capacity(field_tys.len());
2198+
for ft in &field_tys {
2199+
sonatina_fields.push(fe_ty_to_sonatina(
2200+
fb,
2201+
db,
2202+
target_layout,
2203+
*ft,
2204+
cache,
2205+
name_counter,
2206+
)?);
2207+
}
2208+
let name = adt_def
2209+
.adt_ref(db)
2210+
.name(db)
2211+
.map(|id| id.data(db).to_string())
2212+
.unwrap_or_else(|| "anon".to_string());
2213+
let id = *name_counter;
2214+
*name_counter += 1;
2215+
Some(fb.declare_struct_type(
2216+
&format!("__fe_{name}_{id}"),
2217+
&sonatina_fields,
2218+
false,
2219+
))
2220+
}
2221+
// Enums: fall back to manual arithmetic
2222+
AdtRef::Enum(_) => None,
2223+
}
2224+
}
2225+
TyData::TyBase(TyBase::Contract(_)) | TyData::TyBase(TyBase::Func(_)) => Some(Type::Unit),
2226+
_ => None,
2227+
}
2228+
}
2229+
2230+
/// Checks whether a projection chain is eligible for GEP-based addressing.
2231+
///
2232+
/// Returns true when all projections are Field or Index (no VariantField, Discriminant, or Deref).
2233+
fn projections_eligible_for_gep(place: &Place<'_>) -> bool {
2234+
place
2235+
.projection
2236+
.iter()
2237+
.all(|p| matches!(p, Projection::Field(_) | Projection::Index(_)))
2238+
}
2239+
20932240
/// Computes the address for a place by walking the projection path.
20942241
///
20952242
/// For memory, computes byte offsets. For storage, computes slot offsets.
@@ -2098,24 +2245,170 @@ fn lower_place_address<'db, C: sonatina_ir::func_cursor::FuncCursor>(
20982245
ctx: &mut LowerCtx<'_, 'db, C>,
20992246
place: &Place<'db>,
21002247
) -> Result<ValueId, LowerError> {
2101-
let mut base_val = lower_value(ctx, place.base)?;
2248+
let base_val = lower_value(ctx, place.base)?;
21022249

21032250
if place.projection.is_empty() {
21042251
return Ok(base_val);
21052252
}
21062253

21072254
// Get the base value's type to navigate projections
21082255
let base_value = &ctx.body.values[place.base.index()];
2109-
let mut current_ty = base_value.ty;
2256+
let current_ty = base_value.ty;
21102257
if is_erased_runtime_ty(ctx.db, ctx.target_layout, current_ty) {
21112258
return Ok(base_val);
21122259
}
2113-
let mut total_offset: usize = 0;
2260+
21142261
let is_slot_addressed = matches!(
21152262
ctx.body.place_address_space(place),
21162263
AddressSpaceKind::Storage | AddressSpaceKind::TransientStorage
21172264
);
21182265

2266+
// Use GEP for memory-addressed places where all projections are Field or Index
2267+
if !is_slot_addressed
2268+
&& projections_eligible_for_gep(place)
2269+
&& let Some(sonatina_ty) = fe_ty_to_sonatina(
2270+
ctx.fb,
2271+
ctx.db,
2272+
ctx.target_layout,
2273+
current_ty,
2274+
ctx.gep_type_cache,
2275+
ctx.gep_name_counter,
2276+
)
2277+
{
2278+
return lower_place_address_gep(ctx, place, base_val, current_ty, sonatina_ty);
2279+
}
2280+
2281+
// Fall back to manual offset arithmetic
2282+
lower_place_address_arithmetic(ctx, place, base_val, current_ty, is_slot_addressed)
2283+
}
2284+
2285+
/// GEP-based place address computation for memory-addressed struct/array paths.
2286+
fn lower_place_address_gep<'db, C: sonatina_ir::func_cursor::FuncCursor>(
2287+
ctx: &mut LowerCtx<'_, 'db, C>,
2288+
place: &Place<'db>,
2289+
base_val: ValueId,
2290+
base_fe_ty: hir::analysis::ty::ty_def::TyId<'db>,
2291+
base_sonatina_ty: Type,
2292+
) -> Result<ValueId, LowerError> {
2293+
let ptr_ty = ctx.fb.ptr_type(base_sonatina_ty);
2294+
2295+
// IntToPtr: cast I256 base address to typed pointer
2296+
let typed_ptr = ctx
2297+
.fb
2298+
.insert_inst(IntToPtr::new(ctx.is, base_val, ptr_ty), ptr_ty);
2299+
2300+
// Build GEP index list, tracking types through the chain
2301+
let mut gep_values: SmallVec<[ValueId; 8]> = SmallVec::new();
2302+
gep_values.push(typed_ptr);
2303+
2304+
// Initial dereference index (standard GEP convention: index 0 dereferences the pointer)
2305+
let zero = ctx.fb.make_imm_value(I256::zero());
2306+
gep_values.push(zero);
2307+
2308+
let mut current_fe_ty = base_fe_ty;
2309+
let mut current_sonatina_ty = base_sonatina_ty;
2310+
2311+
for proj in place.projection.iter() {
2312+
match proj {
2313+
Projection::Field(field_idx) => {
2314+
let idx_val = ctx.fb.make_imm_value(I256::from(*field_idx as u64));
2315+
gep_values.push(idx_val);
2316+
2317+
// Navigate Fe type
2318+
let field_types = current_fe_ty.field_types(ctx.db);
2319+
current_fe_ty = *field_types.get(*field_idx).ok_or_else(|| {
2320+
LowerError::Unsupported(format!("gep: field {field_idx} out of bounds"))
2321+
})?;
2322+
2323+
// Navigate sonatina type to the field's type
2324+
current_sonatina_ty =
2325+
sonatina_struct_field_ty(ctx, current_sonatina_ty, *field_idx)?;
2326+
}
2327+
Projection::Index(idx_source) => {
2328+
let idx_val = match idx_source {
2329+
IndexSource::Constant(idx) => ctx.fb.make_imm_value(I256::from(*idx as u64)),
2330+
IndexSource::Dynamic(value_id) => lower_value(ctx, *value_id)?,
2331+
};
2332+
gep_values.push(idx_val);
2333+
2334+
// Navigate Fe type
2335+
current_fe_ty = layout::array_elem_ty(ctx.db, current_fe_ty).ok_or_else(|| {
2336+
LowerError::Unsupported("gep: array index on non-array type".to_string())
2337+
})?;
2338+
2339+
// Navigate sonatina type to array element
2340+
current_sonatina_ty = sonatina_array_elem_ty(ctx, current_sonatina_ty)?;
2341+
}
2342+
_ => unreachable!("projections_eligible_for_gep ensures only Field/Index"),
2343+
}
2344+
}
2345+
2346+
// The GEP result is a pointer to the final element type
2347+
let result_ptr_ty = ctx.fb.ptr_type(current_sonatina_ty);
2348+
let gep_result = ctx
2349+
.fb
2350+
.insert_inst(Gep::new(ctx.is, gep_values), result_ptr_ty);
2351+
2352+
// PtrToInt: cast back to I256 for mload/mstore
2353+
let result = ctx
2354+
.fb
2355+
.insert_inst(PtrToInt::new(ctx.is, gep_result, Type::I256), Type::I256);
2356+
2357+
Ok(result)
2358+
}
2359+
2360+
/// Resolves the sonatina type of a struct field by index.
2361+
fn sonatina_struct_field_ty<C: sonatina_ir::func_cursor::FuncCursor>(
2362+
ctx: &mut LowerCtx<'_, '_, C>,
2363+
struct_ty: Type,
2364+
field_idx: usize,
2365+
) -> Result<Type, LowerError> {
2366+
let fields = ctx
2367+
.fb
2368+
.module_builder
2369+
.ctx
2370+
.with_ty_store(|s| s.struct_def(struct_ty).map(|sd| sd.fields.clone()));
2371+
match fields {
2372+
Some(f) => f.get(field_idx).copied().ok_or_else(|| {
2373+
LowerError::Internal(format!(
2374+
"gep: sonatina struct field {field_idx} out of bounds"
2375+
))
2376+
}),
2377+
None => Err(LowerError::Internal(
2378+
"gep: expected sonatina struct type for Field projection".to_string(),
2379+
)),
2380+
}
2381+
}
2382+
2383+
/// Resolves the sonatina element type of an array type.
2384+
fn sonatina_array_elem_ty<C: sonatina_ir::func_cursor::FuncCursor>(
2385+
ctx: &mut LowerCtx<'_, '_, C>,
2386+
array_ty: Type,
2387+
) -> Result<Type, LowerError> {
2388+
let elem = ctx
2389+
.fb
2390+
.module_builder
2391+
.ctx
2392+
.with_ty_store(|s| s.array_def(array_ty).map(|(elem, _len)| elem));
2393+
match elem {
2394+
Some(e) => Ok(e),
2395+
None => Err(LowerError::Internal(
2396+
"gep: expected sonatina array type for Index projection".to_string(),
2397+
)),
2398+
}
2399+
}
2400+
2401+
/// Manual offset arithmetic path for place address computation.
2402+
/// Used for storage-addressed places and any memory path with enum projections.
2403+
fn lower_place_address_arithmetic<'db, C: sonatina_ir::func_cursor::FuncCursor>(
2404+
ctx: &mut LowerCtx<'_, 'db, C>,
2405+
place: &Place<'db>,
2406+
mut base_val: ValueId,
2407+
mut current_ty: hir::analysis::ty::ty_def::TyId<'db>,
2408+
is_slot_addressed: bool,
2409+
) -> Result<ValueId, LowerError> {
2410+
let mut total_offset: usize = 0;
2411+
21192412
for proj in place.projection.iter() {
21202413
match proj {
21212414
Projection::Field(field_idx) => {

0 commit comments

Comments
 (0)