fe_codegen/yul/isel/
function.rs

1#![allow(unused)]
2use std::thread::Scope;
3
4use super::{context::Context, inst_order::InstSerializer};
5use fe_common::numeric::to_hex_str;
6
7use fe_abi::function::{AbiFunction, AbiFunctionType};
8use fe_common::db::Upcast;
9use fe_mir::{
10    ir::{
11        self,
12        constant::ConstantValue,
13        inst::{BinOp, CallType, CastKind, InstKind, UnOp},
14        value::AssignableValue,
15        Constant, FunctionBody, FunctionId, FunctionSignature, InstId, Type, TypeId, TypeKind,
16        Value, ValueId,
17    },
18    pretty_print::PrettyPrint,
19};
20use fxhash::FxHashMap;
21use smol_str::SmolStr;
22use yultsur::{
23    yul::{self, Statement},
24    *,
25};
26
27use crate::{
28    db::CodegenDb,
29    yul::isel::inst_order::StructuralInst,
30    yul::slot_size::{function_hash_type, yul_primitive_type, SLOT_SIZE},
31    yul::{
32        runtime::{self, RuntimeProvider},
33        YulVariable,
34    },
35};
36
37pub fn lower_function(
38    db: &dyn CodegenDb,
39    ctx: &mut Context,
40    function: FunctionId,
41) -> yul::FunctionDefinition {
42    debug_assert!(!ctx.lowered_functions.contains(&function));
43    ctx.lowered_functions.insert(function);
44    let sig = &db.codegen_legalized_signature(function);
45    let body = &db.codegen_legalized_body(function);
46    FuncLowerHelper::new(db, ctx, function, sig, body).lower_func()
47}
48
49struct FuncLowerHelper<'db, 'a> {
50    db: &'db dyn CodegenDb,
51    ctx: &'a mut Context,
52    value_map: ScopedValueMap,
53    func: FunctionId,
54    sig: &'a FunctionSignature,
55    body: &'a FunctionBody,
56    ret_value: Option<yul::Identifier>,
57    sink: Vec<yul::Statement>,
58}
59
60impl<'db, 'a> FuncLowerHelper<'db, 'a> {
61    fn new(
62        db: &'db dyn CodegenDb,
63        ctx: &'a mut Context,
64        func: FunctionId,
65        sig: &'a FunctionSignature,
66        body: &'a FunctionBody,
67    ) -> Self {
68        let mut value_map = ScopedValueMap::default();
69        // Register arguments to value_map.
70        for &value in body.store.locals() {
71            match body.store.value_data(value) {
72                Value::Local(local) if local.is_arg => {
73                    let ident = YulVariable::new(local.name.as_str()).ident();
74                    value_map.insert(value, ident);
75                }
76                _ => {}
77            }
78        }
79
80        let ret_value = if sig.return_type.is_some() {
81            Some(YulVariable::new("$ret").ident())
82        } else {
83            None
84        };
85
86        Self {
87            db,
88            ctx,
89            value_map,
90            func,
91            sig,
92            body,
93            ret_value,
94            sink: Vec::new(),
95        }
96    }
97
98    fn lower_func(mut self) -> yul::FunctionDefinition {
99        let name = identifier! { (self.db.codegen_function_symbol_name(self.func)) };
100
101        let parameters = self
102            .sig
103            .params
104            .iter()
105            .map(|param| YulVariable::new(param.name.as_str()).ident())
106            .collect();
107
108        let ret = self
109            .ret_value
110            .clone()
111            .map(|value| vec![value])
112            .unwrap_or_default();
113
114        let body = self.lower_body();
115
116        yul::FunctionDefinition {
117            name,
118            parameters,
119            returns: ret,
120            block: body,
121        }
122    }
123
124    fn lower_body(mut self) -> yul::Block {
125        let inst_order = InstSerializer::new(self.body).serialize();
126
127        for inst in inst_order {
128            self.lower_structural_inst(inst)
129        }
130
131        yul::Block {
132            statements: self.sink,
133        }
134    }
135
136    fn lower_structural_inst(&mut self, inst: StructuralInst) {
137        match inst {
138            StructuralInst::Inst(inst) => self.lower_inst(inst),
139            StructuralInst::If { cond, then, else_ } => {
140                let if_block = self.lower_if(cond, then, else_);
141                self.sink.push(if_block)
142            }
143            StructuralInst::Switch {
144                scrutinee,
145                table,
146                default,
147            } => {
148                let switch_block = self.lower_switch(scrutinee, table, default);
149                self.sink.push(switch_block)
150            }
151            StructuralInst::For { body } => {
152                let for_block = self.lower_for(body);
153                self.sink.push(for_block)
154            }
155            StructuralInst::Break => self.sink.push(yul::Statement::Break),
156            StructuralInst::Continue => self.sink.push(yul::Statement::Continue),
157        };
158    }
159
160    fn lower_inst(&mut self, inst: InstId) {
161        if let Some(lhs) = self.body.store.inst_result(inst) {
162            self.declare_assignable_value(lhs)
163        }
164
165        match &self.body.store.inst_data(inst).kind {
166            InstKind::Declare { local } => self.declare_value(*local),
167
168            InstKind::Unary { op, value } => {
169                let inst_result = self.body.store.inst_result(inst).unwrap();
170                let inst_result_ty = inst_result.ty(self.db.upcast(), &self.body.store);
171                let result = self.lower_unary(*op, *value);
172                self.assign_inst_result(inst, result, inst_result_ty.deref(self.db.upcast()))
173            }
174
175            InstKind::Binary { op, lhs, rhs } => {
176                let inst_result = self.body.store.inst_result(inst).unwrap();
177                let inst_result_ty = inst_result.ty(self.db.upcast(), &self.body.store);
178                let result = self.lower_binary(*op, *lhs, *rhs, inst);
179                self.assign_inst_result(inst, result, inst_result_ty.deref(self.db.upcast()))
180            }
181
182            InstKind::Cast { kind, value, to } => {
183                let from_ty = self.body.store.value_ty(*value);
184                let result = match kind {
185                    CastKind::Primitive => {
186                        debug_assert!(
187                            from_ty.is_primitive(self.db.upcast())
188                                && to.is_primitive(self.db.upcast())
189                        );
190                        let value = self.value_expr(*value);
191                        self.ctx.runtime.primitive_cast(self.db, value, from_ty)
192                    }
193                    CastKind::Untag => {
194                        let from_ty = from_ty.deref(self.db.upcast());
195                        debug_assert!(from_ty.is_enum(self.db.upcast()));
196                        let value = self.value_expr(*value);
197                        let offset = literal_expression! {(from_ty.enum_data_offset(self.db.upcast(), SLOT_SIZE))};
198                        expression! {add([value], [offset])}
199                    }
200                };
201
202                self.assign_inst_result(inst, result, *to)
203            }
204
205            InstKind::AggregateConstruct { ty, args } => {
206                let lhs = self.body.store.inst_result(inst).unwrap();
207                let ptr = self.lower_assignable_value(lhs);
208                let ptr_ty = lhs.ty(self.db.upcast(), &self.body.store);
209                let arg_values = args.iter().map(|arg| self.value_expr(*arg)).collect();
210                let arg_tys = args
211                    .iter()
212                    .map(|arg| self.body.store.value_ty(*arg))
213                    .collect();
214                self.sink.push(yul::Statement::Expression(
215                    self.ctx
216                        .runtime
217                        .aggregate_init(self.db, ptr, arg_values, ptr_ty, arg_tys),
218                ))
219            }
220
221            InstKind::Bind { src } => {
222                match self.body.store.value_data(*src) {
223                    Value::Constant { constant, .. } => {
224                        // Need special handling when rhs is the string literal because it needs ptr
225                        // copy.
226                        if let ConstantValue::Str(s) = &constant.data(self.db.upcast()).value {
227                            self.ctx.string_constants.insert(s.to_string());
228                            let size = self.value_ty_size_deref(*src);
229                            let lhs = self.body.store.inst_result(inst).unwrap();
230                            let ptr = self.lower_assignable_value(lhs);
231                            let inst_result_ty = lhs.ty(self.db.upcast(), &self.body.store);
232                            self.sink.push(yul::Statement::Expression(
233                                self.ctx.runtime.string_copy(
234                                    self.db,
235                                    ptr,
236                                    s,
237                                    inst_result_ty.is_sptr(self.db.upcast()),
238                                ),
239                            ))
240                        } else {
241                            let src_ty = self.body.store.value_ty(*src);
242                            let src = self.value_expr(*src);
243                            self.assign_inst_result(inst, src, src_ty)
244                        }
245                    }
246                    _ => {
247                        let src_ty = self.body.store.value_ty(*src);
248                        let src = self.value_expr(*src);
249                        self.assign_inst_result(inst, src, src_ty)
250                    }
251                }
252            }
253
254            InstKind::MemCopy { src } => {
255                let lhs = self.body.store.inst_result(inst).unwrap();
256                let dst_ptr = self.lower_assignable_value(lhs);
257                let dst_ptr_ty = lhs.ty(self.db.upcast(), &self.body.store);
258                let src_ptr = self.value_expr(*src);
259                let src_ptr_ty = self.body.store.value_ty(*src);
260                let ty_size = literal_expression! { (self.value_ty_size_deref(*src)) };
261                self.sink
262                    .push(yul::Statement::Expression(self.ctx.runtime.ptr_copy(
263                        self.db,
264                        src_ptr,
265                        dst_ptr,
266                        ty_size,
267                        src_ptr_ty.is_sptr(self.db.upcast()),
268                        dst_ptr_ty.is_sptr(self.db.upcast()),
269                    )))
270            }
271
272            InstKind::Load { src } => {
273                let src_ty = self.body.store.value_ty(*src);
274                let src = self.value_expr(*src);
275                debug_assert!(src_ty.is_ptr(self.db.upcast()));
276
277                let result = self.body.store.inst_result(inst).unwrap();
278                debug_assert!(!result
279                    .ty(self.db.upcast(), &self.body.store)
280                    .is_ptr(self.db.upcast()));
281                self.assign_inst_result(inst, src, src_ty)
282            }
283
284            InstKind::AggregateAccess { value, indices } => {
285                let base = self.value_expr(*value);
286                let mut ptr = base;
287                let mut inner_ty = self.body.store.value_ty(*value);
288                for &idx in indices {
289                    ptr = self.aggregate_elem_ptr(ptr, idx, inner_ty.deref(self.db.upcast()));
290                    inner_ty =
291                        inner_ty.projection_ty(self.db.upcast(), self.body.store.value_data(idx));
292                }
293
294                let result = self.body.store.inst_result(inst).unwrap();
295                self.assign_inst_result(inst, ptr, inner_ty)
296            }
297
298            InstKind::MapAccess { value, key } => {
299                let map_ty = self.body.store.value_ty(*value).deref(self.db.upcast());
300                let value_expr = self.value_expr(*value);
301                let key_expr = self.value_expr(*key);
302                let key_ty = self.body.store.value_ty(*key);
303                let ptr = self
304                    .ctx
305                    .runtime
306                    .map_value_ptr(self.db, value_expr, key_expr, key_ty);
307                let value_ty = match &map_ty.data(self.db.upcast()).kind {
308                    TypeKind::Map(def) => def.value_ty,
309                    _ => unreachable!(),
310                };
311
312                self.assign_inst_result(inst, ptr, value_ty.make_sptr(self.db.upcast()));
313            }
314
315            InstKind::Call {
316                func,
317                args,
318                call_type,
319            } => {
320                let args: Vec<_> = args.iter().map(|arg| self.value_expr(*arg)).collect();
321                let result = match call_type {
322                    CallType::Internal => {
323                        self.ctx.function_dependency.insert(*func);
324                        let func_name = identifier! {(self.db.codegen_function_symbol_name(*func))};
325                        expression! {[func_name]([args...])}
326                    }
327                    CallType::External => self.ctx.runtime.external_call(self.db, *func, args),
328                };
329                match self.db.codegen_legalized_signature(*func).return_type {
330                    Some(mut result_ty) => {
331                        if result_ty.is_aggregate(self.db.upcast())
332                            | result_ty.is_string(self.db.upcast())
333                        {
334                            result_ty = result_ty.make_mptr(self.db.upcast());
335                        }
336                        self.assign_inst_result(inst, result, result_ty)
337                    }
338                    _ => self.sink.push(Statement::Expression(result)),
339                }
340            }
341
342            InstKind::Revert { arg } => match arg {
343                Some(arg) => {
344                    let arg_ty = self.body.store.value_ty(*arg);
345                    let deref_ty = arg_ty.deref(self.db.upcast());
346                    let ty_data = deref_ty.data(self.db.upcast());
347                    let arg_expr = if deref_ty.is_zero_sized(self.db.upcast()) {
348                        None
349                    } else {
350                        Some(self.value_expr(*arg))
351                    };
352                    let name = match &ty_data.kind {
353                        ir::TypeKind::Struct(def) => &def.name,
354                        ir::TypeKind::String(def) => "Error",
355                        _ => "Panic",
356                    };
357                    self.sink.push(yul::Statement::Expression(
358                        self.ctx.runtime.revert(self.db, arg_expr, name, arg_ty),
359                    ));
360                }
361                None => self.sink.push(statement! {revert(0, 0)}),
362            },
363
364            InstKind::Emit { arg } => {
365                let event = self.value_expr(*arg);
366                let event_ty = self.body.store.value_ty(*arg);
367                let result = self.ctx.runtime.emit(self.db, event, event_ty);
368                let u256_ty = yul_primitive_type(self.db);
369                self.assign_inst_result(inst, result, u256_ty);
370            }
371
372            InstKind::Return { arg } => {
373                if let Some(arg) = arg {
374                    let arg = self.value_expr(*arg);
375                    let ret_value = self.ret_value.clone().unwrap();
376                    self.sink.push(statement! {[ret_value] := [arg]});
377                }
378                self.sink.push(yul::Statement::Leave)
379            }
380
381            InstKind::Keccak256 { arg } => {
382                let result = self.keccak256(*arg);
383                let u256_ty = yul_primitive_type(self.db);
384                self.assign_inst_result(inst, result, u256_ty);
385            }
386
387            InstKind::AbiEncode { arg } => {
388                let lhs = self.body.store.inst_result(inst).unwrap();
389                let ptr = self.lower_assignable_value(lhs);
390                let ptr_ty = lhs.ty(self.db.upcast(), &self.body.store);
391                let src_expr = self.value_expr(*arg);
392                let src_ty = self.body.store.value_ty(*arg);
393
394                let abi_encode = self.ctx.runtime.abi_encode(
395                    self.db,
396                    src_expr,
397                    ptr,
398                    src_ty,
399                    ptr_ty.is_sptr(self.db.upcast()),
400                );
401                self.sink.push(statement! {
402                    pop([abi_encode])
403                });
404            }
405
406            InstKind::Create { value, contract } => {
407                self.ctx.contract_dependency.insert(*contract);
408
409                let value_expr = self.value_expr(*value);
410                let result = self.ctx.runtime.create(self.db, *contract, value_expr);
411                let u256_ty = yul_primitive_type(self.db);
412                self.assign_inst_result(inst, result, u256_ty)
413            }
414
415            InstKind::Create2 {
416                value,
417                salt,
418                contract,
419            } => {
420                self.ctx.contract_dependency.insert(*contract);
421
422                let value_expr = self.value_expr(*value);
423                let salt_expr = self.value_expr(*salt);
424                let result = self
425                    .ctx
426                    .runtime
427                    .create2(self.db, *contract, value_expr, salt_expr);
428                let u256_ty = yul_primitive_type(self.db);
429                self.assign_inst_result(inst, result, u256_ty)
430            }
431
432            InstKind::YulIntrinsic { op, args } => {
433                let args: Vec<_> = args.iter().map(|arg| self.value_expr(*arg)).collect();
434                let op_name = identifier! { (format!("{op}").strip_prefix("__").unwrap()) };
435                let result = expression! { [op_name]([args...]) };
436                // Intrinsic operation never returns ptr type, so we can use u256_ty as a dummy
437                // type for the result.
438                let u256_ty = yul_primitive_type(self.db);
439                self.assign_inst_result(inst, result, u256_ty)
440            }
441
442            InstKind::Nop => {}
443
444            // These flow control instructions are already legalized.
445            InstKind::Jump { .. } | InstKind::Branch { .. } | InstKind::Switch { .. } => {
446                unreachable!()
447            }
448        }
449    }
450
451    fn lower_if(
452        &mut self,
453        cond: ValueId,
454        then: Vec<StructuralInst>,
455        else_: Vec<StructuralInst>,
456    ) -> yul::Statement {
457        let cond = self.value_expr(cond);
458
459        self.enter_scope();
460        let then_body = self.lower_branch_body(then);
461        self.leave_scope();
462
463        self.enter_scope();
464        let else_body = self.lower_branch_body(else_);
465        self.leave_scope();
466
467        switch! {
468            switch ([cond])
469            (case 1 {[then_body...]})
470            (case 0 {[else_body...]})
471        }
472    }
473
474    fn lower_switch(
475        &mut self,
476        scrutinee: ValueId,
477        table: Vec<(ValueId, Vec<StructuralInst>)>,
478        default: Option<Vec<StructuralInst>>,
479    ) -> yul::Statement {
480        let scrutinee = self.value_expr(scrutinee);
481
482        let mut cases = vec![];
483        for (value, insts) in table {
484            let value = self.value_expr(value);
485            let value = match value {
486                yul::Expression::Literal(lit) => lit,
487                _ => panic!("switch table values must be literal"),
488            };
489
490            self.enter_scope();
491            let body = self.lower_branch_body(insts);
492            self.leave_scope();
493            cases.push(yul::Case {
494                literal: Some(value),
495                block: block! { [body...] },
496            })
497        }
498
499        if let Some(insts) = default {
500            let block = self.lower_branch_body(insts);
501            cases.push(case! {
502                default {[block...]}
503            });
504        }
505
506        switch! {
507            switch ([scrutinee])
508            [cases...]
509        }
510    }
511
512    fn lower_branch_body(&mut self, insts: Vec<StructuralInst>) -> Vec<yul::Statement> {
513        let mut body = vec![];
514        std::mem::swap(&mut self.sink, &mut body);
515        for inst in insts {
516            self.lower_structural_inst(inst);
517        }
518        std::mem::swap(&mut self.sink, &mut body);
519        body
520    }
521
522    fn lower_for(&mut self, body: Vec<StructuralInst>) -> yul::Statement {
523        let mut body_stmts = vec![];
524        std::mem::swap(&mut self.sink, &mut body_stmts);
525        for inst in body {
526            self.lower_structural_inst(inst);
527        }
528        std::mem::swap(&mut self.sink, &mut body_stmts);
529
530        block_statement! {(
531            for {} (1) {}
532            {
533                [body_stmts...]
534            }
535        )}
536    }
537
538    fn lower_assign(&mut self, lhs: &AssignableValue, rhs: ValueId) -> yul::Statement {
539        match lhs {
540            AssignableValue::Value(value) => {
541                let lhs = self.value_ident(*value);
542                let rhs = self.value_expr(rhs);
543                statement! { [lhs] := [rhs] }
544            }
545            AssignableValue::Aggregate { .. } | AssignableValue::Map { .. } => {
546                let dst_ty = lhs.ty(self.db.upcast(), &self.body.store);
547                let src_ty = self.body.store.value_ty(rhs);
548                debug_assert_eq!(
549                    dst_ty.deref(self.db.upcast()),
550                    src_ty.deref(self.db.upcast())
551                );
552
553                let dst = self.lower_assignable_value(lhs);
554                let src = self.value_expr(rhs);
555
556                if src_ty.is_ptr(self.db.upcast()) {
557                    let ty_size = literal_expression! { (self.value_ty_size_deref(rhs)) };
558
559                    let expr = self.ctx.runtime.ptr_copy(
560                        self.db,
561                        src,
562                        dst,
563                        ty_size,
564                        src_ty.is_sptr(self.db.upcast()),
565                        dst_ty.is_sptr(self.db.upcast()),
566                    );
567                    yul::Statement::Expression(expr)
568                } else {
569                    let expr = self.ctx.runtime.ptr_store(self.db, dst, src, dst_ty);
570                    yul::Statement::Expression(expr)
571                }
572            }
573        }
574    }
575
576    fn lower_unary(&mut self, op: UnOp, value: ValueId) -> yul::Expression {
577        let value_expr = self.value_expr(value);
578        match op {
579            UnOp::Not => expression! { iszero([value_expr])},
580            UnOp::Neg => {
581                let zero = literal_expression! {0};
582                if self.body.store.value_data(value).is_imm() {
583                    // Literals are checked at compile time (e.g. -128) so there's no point
584                    // in adding a runtime check.
585                    expression! {sub([zero], [value_expr])}
586                } else {
587                    let value_ty = self.body.store.value_ty(value);
588                    self.ctx
589                        .runtime
590                        .safe_sub(self.db, zero, value_expr, value_ty)
591                }
592            }
593            UnOp::Inv => expression! { not([value_expr])},
594        }
595    }
596
597    fn lower_binary(
598        &mut self,
599        op: BinOp,
600        lhs: ValueId,
601        rhs: ValueId,
602        inst: InstId,
603    ) -> yul::Expression {
604        let lhs_expr = self.value_expr(lhs);
605        let rhs_expr = self.value_expr(rhs);
606        let is_result_signed = self
607            .body
608            .store
609            .inst_result(inst)
610            .map(|val| {
611                let ty = val.ty(self.db.upcast(), &self.body.store);
612                ty.is_signed(self.db.upcast())
613            })
614            .unwrap_or(false);
615        let is_lhs_signed = self.body.store.value_ty(lhs).is_signed(self.db.upcast());
616
617        let inst_result = self.body.store.inst_result(inst).unwrap();
618        let inst_result_ty = inst_result
619            .ty(self.db.upcast(), &self.body.store)
620            .deref(self.db.upcast());
621        match op {
622            BinOp::Add => self
623                .ctx
624                .runtime
625                .safe_add(self.db, lhs_expr, rhs_expr, inst_result_ty),
626            BinOp::Sub => self
627                .ctx
628                .runtime
629                .safe_sub(self.db, lhs_expr, rhs_expr, inst_result_ty),
630            BinOp::Mul => self
631                .ctx
632                .runtime
633                .safe_mul(self.db, lhs_expr, rhs_expr, inst_result_ty),
634            BinOp::Div => self
635                .ctx
636                .runtime
637                .safe_div(self.db, lhs_expr, rhs_expr, inst_result_ty),
638            BinOp::Mod => self
639                .ctx
640                .runtime
641                .safe_mod(self.db, lhs_expr, rhs_expr, inst_result_ty),
642            BinOp::Pow => self
643                .ctx
644                .runtime
645                .safe_pow(self.db, lhs_expr, rhs_expr, inst_result_ty),
646            BinOp::Shl => expression! {shl([rhs_expr], [lhs_expr])},
647            BinOp::Shr if is_result_signed => expression! {sar([rhs_expr], [lhs_expr])},
648            BinOp::Shr => expression! {shr([rhs_expr], [lhs_expr])},
649            BinOp::BitOr | BinOp::LogicalOr => expression! {or([lhs_expr], [rhs_expr])},
650            BinOp::BitXor => expression! {xor([lhs_expr], [rhs_expr])},
651            BinOp::BitAnd | BinOp::LogicalAnd => expression! {and([lhs_expr], [rhs_expr])},
652            BinOp::Eq => expression! {eq([lhs_expr], [rhs_expr])},
653            BinOp::Ne => expression! {iszero((eq([lhs_expr], [rhs_expr])))},
654            BinOp::Ge if is_lhs_signed => expression! {iszero((slt([lhs_expr], [rhs_expr])))},
655            BinOp::Ge => expression! {iszero((lt([lhs_expr], [rhs_expr])))},
656            BinOp::Gt if is_lhs_signed => expression! {sgt([lhs_expr], [rhs_expr])},
657            BinOp::Gt => expression! {gt([lhs_expr], [rhs_expr])},
658            BinOp::Le if is_lhs_signed => expression! {iszero((sgt([lhs_expr], [rhs_expr])))},
659            BinOp::Le => expression! {iszero((gt([lhs_expr], [rhs_expr])))},
660            BinOp::Lt if is_lhs_signed => expression! {slt([lhs_expr], [rhs_expr])},
661            BinOp::Lt => expression! {lt([lhs_expr], [rhs_expr])},
662        }
663    }
664
665    fn lower_cast(&mut self, value: ValueId, to: TypeId) -> yul::Expression {
666        let from_ty = self.body.store.value_ty(value);
667        debug_assert!(from_ty.is_primitive(self.db.upcast()));
668        debug_assert!(to.is_primitive(self.db.upcast()));
669
670        let value = self.value_expr(value);
671        self.ctx.runtime.primitive_cast(self.db, value, from_ty)
672    }
673
674    fn assign_inst_result(&mut self, inst: InstId, rhs: yul::Expression, rhs_ty: TypeId) {
675        // NOTE: We don't have `deref` feature yet, so need a heuristics for an
676        // assignment.
677        let stmt = if let Some(result) = self.body.store.inst_result(inst) {
678            let lhs = self.lower_assignable_value(result);
679            let lhs_ty = result.ty(self.db.upcast(), &self.body.store);
680            match result {
681                AssignableValue::Value(value) => {
682                    match (
683                        lhs_ty.is_ptr(self.db.upcast()),
684                        rhs_ty.is_ptr(self.db.upcast()),
685                    ) {
686                        (true, true) => {
687                            if lhs_ty.is_mptr(self.db.upcast()) == rhs_ty.is_mptr(self.db.upcast())
688                            {
689                                let rhs = self.extend_value(rhs, lhs_ty);
690                                let lhs_ident = self.value_ident(*value);
691                                statement! { [lhs_ident] := [rhs] }
692                            } else {
693                                let ty_size = rhs_ty
694                                    .deref(self.db.upcast())
695                                    .size_of(self.db.upcast(), SLOT_SIZE);
696                                yul::Statement::Expression(self.ctx.runtime.ptr_copy(
697                                    self.db,
698                                    rhs,
699                                    lhs,
700                                    literal_expression! { (ty_size) },
701                                    rhs_ty.is_sptr(self.db.upcast()),
702                                    lhs_ty.is_sptr(self.db.upcast()),
703                                ))
704                            }
705                        }
706                        (true, false) => yul::Statement::Expression(
707                            self.ctx.runtime.ptr_store(self.db, lhs, rhs, lhs_ty),
708                        ),
709
710                        (false, true) => {
711                            let rhs = self.ctx.runtime.ptr_load(self.db, rhs, rhs_ty);
712                            let rhs = self.extend_value(rhs, lhs_ty);
713                            let lhs_ident = self.value_ident(*value);
714                            statement! { [lhs_ident] := [rhs] }
715                        }
716                        (false, false) => {
717                            let rhs = self.extend_value(rhs, lhs_ty);
718                            let lhs_ident = self.value_ident(*value);
719                            statement! { [lhs_ident] := [rhs] }
720                        }
721                    }
722                }
723                AssignableValue::Aggregate { .. } | AssignableValue::Map { .. } => {
724                    let expr = if rhs_ty.is_ptr(self.db.upcast()) {
725                        let ty_size = rhs_ty
726                            .deref(self.db.upcast())
727                            .size_of(self.db.upcast(), SLOT_SIZE);
728                        self.ctx.runtime.ptr_copy(
729                            self.db,
730                            rhs,
731                            lhs,
732                            literal_expression! { (ty_size) },
733                            rhs_ty.is_sptr(self.db.upcast()),
734                            lhs_ty.is_sptr(self.db.upcast()),
735                        )
736                    } else {
737                        self.ctx.runtime.ptr_store(self.db, lhs, rhs, lhs_ty)
738                    };
739                    yul::Statement::Expression(expr)
740                }
741            }
742        } else {
743            yul::Statement::Expression(rhs)
744        };
745
746        self.sink.push(stmt);
747    }
748
749    /// Extend a value to 256 bits.
750    fn extend_value(&mut self, value: yul::Expression, ty: TypeId) -> yul::Expression {
751        if ty.is_primitive(self.db.upcast()) {
752            self.ctx.runtime.primitive_cast(self.db, value, ty)
753        } else {
754            value
755        }
756    }
757
758    fn declare_assignable_value(&mut self, value: &AssignableValue) {
759        match value {
760            AssignableValue::Value(value) if !self.value_map.contains(*value) => {
761                self.declare_value(*value);
762            }
763            _ => {}
764        }
765    }
766
767    fn declare_value(&mut self, value: ValueId) {
768        let var = YulVariable::new(format!("$tmp_{}", value.index()));
769        self.value_map.insert(value, var.ident());
770        let value_ty = self.body.store.value_ty(value);
771
772        // Allocate memory for a value if a value is a pointer type.
773        let init = if value_ty.is_mptr(self.db.upcast()) {
774            let deref_ty = value_ty.deref(self.db.upcast());
775            let ty_size = deref_ty.size_of(self.db.upcast(), SLOT_SIZE);
776            let size = literal_expression! { (ty_size) };
777            Some(self.ctx.runtime.alloc(self.db, size))
778        } else {
779            None
780        };
781
782        self.sink.push(yul::Statement::VariableDeclaration(
783            yul::VariableDeclaration {
784                identifiers: vec![var.ident()],
785                expression: init,
786            },
787        ))
788    }
789
790    fn value_expr(&mut self, value: ValueId) -> yul::Expression {
791        match self.body.store.value_data(value) {
792            Value::Local(_) | Value::Temporary { .. } => {
793                let ident = self.value_map.lookup(value).unwrap();
794                literal_expression! {(ident)}
795            }
796            Value::Immediate { imm, .. } => {
797                literal_expression! {(imm)}
798            }
799            Value::Constant { constant, .. } => match &constant.data(self.db.upcast()).value {
800                ConstantValue::Immediate(imm) => {
801                    // YUL does not support representing negative integers with leading minus (e.g.
802                    // `-1` in YUL would lead to an ICE). To mitigate that we
803                    // convert all numeric values into hexadecimal representation.
804                    literal_expression! {(to_hex_str(imm))}
805                }
806                ConstantValue::Str(s) => {
807                    self.ctx.string_constants.insert(s.to_string());
808                    self.ctx.runtime.string_construct(self.db, s, s.len())
809                }
810                ConstantValue::Bool(true) => {
811                    literal_expression! {1}
812                }
813                ConstantValue::Bool(false) => {
814                    literal_expression! {0}
815                }
816            },
817            Value::Unit { .. } => unreachable!(),
818        }
819    }
820
821    fn value_ident(&self, value: ValueId) -> yul::Identifier {
822        self.value_map.lookup(value).unwrap().clone()
823    }
824
825    fn make_tmp(&mut self, tmp: ValueId) -> yul::Identifier {
826        let ident = YulVariable::new(format! {"$tmp_{}", tmp.index()}).ident();
827        self.value_map.insert(tmp, ident.clone());
828        ident
829    }
830
831    fn keccak256(&mut self, value: ValueId) -> yul::Expression {
832        let value_ty = self.body.store.value_ty(value);
833        debug_assert!(value_ty.is_mptr(self.db.upcast()));
834
835        let value_size = value_ty
836            .deref(self.db.upcast())
837            .size_of(self.db.upcast(), SLOT_SIZE);
838        let value_size_expr = literal_expression! {(value_size)};
839        let value_expr = self.value_expr(value);
840        expression! {keccak256([value_expr], [value_size_expr])}
841    }
842
843    fn lower_assignable_value(&mut self, value: &AssignableValue) -> yul::Expression {
844        match value {
845            AssignableValue::Value(value) => self.value_expr(*value),
846
847            AssignableValue::Aggregate { lhs, idx } => {
848                let base_ptr = self.lower_assignable_value(lhs);
849                let ty = lhs
850                    .ty(self.db.upcast(), &self.body.store)
851                    .deref(self.db.upcast());
852                self.aggregate_elem_ptr(base_ptr, *idx, ty)
853            }
854            AssignableValue::Map { lhs, key } => {
855                let map_ptr = self.lower_assignable_value(lhs);
856                let key_ty = self.body.store.value_ty(*key);
857                let key = self.value_expr(*key);
858                self.ctx
859                    .runtime
860                    .map_value_ptr(self.db, map_ptr, key, key_ty)
861            }
862        }
863    }
864
865    fn aggregate_elem_ptr(
866        &mut self,
867        base_ptr: yul::Expression,
868        idx: ValueId,
869        base_ty: TypeId,
870    ) -> yul::Expression {
871        debug_assert!(base_ty.is_aggregate(self.db.upcast()));
872
873        match &base_ty.data(self.db.upcast()).kind {
874            TypeKind::Array(def) => {
875                let elem_size =
876                    literal_expression! {(base_ty.array_elem_size(self.db.upcast(), SLOT_SIZE))};
877                self.validate_array_indexing(def.len, idx);
878                let idx = self.value_expr(idx);
879                let offset = expression! {mul([elem_size], [idx])};
880                expression! { add([base_ptr], [offset]) }
881            }
882            _ => {
883                let elem_idx = match self.body.store.value_data(idx) {
884                    Value::Immediate { imm, .. } => imm,
885                    _ => panic!("only array type can use dynamic value indexing"),
886                };
887                let offset = literal_expression! {(base_ty.aggregate_elem_offset(self.db.upcast(), elem_idx.clone(), SLOT_SIZE))};
888                expression! {add([base_ptr], [offset])}
889            }
890        }
891    }
892
893    fn validate_array_indexing(&mut self, array_len: usize, idx: ValueId) {
894        const PANIC_OUT_OF_BOUNDS: usize = 0x32;
895
896        if let Value::Immediate { .. } = self.body.store.value_data(idx) {
897            return;
898        }
899
900        let idx = self.value_expr(idx);
901        let max_idx = literal_expression! {(array_len - 1)};
902        self.sink.push(statement!(if (gt([idx], [max_idx])) {
903            ([runtime::panic_revert_numeric(
904                self.ctx.runtime.as_mut(),
905                self.db,
906                literal_expression! {(PANIC_OUT_OF_BOUNDS)},
907            )])
908        }));
909    }
910
911    fn value_ty_size(&self, value: ValueId) -> usize {
912        self.body
913            .store
914            .value_ty(value)
915            .size_of(self.db.upcast(), SLOT_SIZE)
916    }
917
918    fn value_ty_size_deref(&self, value: ValueId) -> usize {
919        self.body
920            .store
921            .value_ty(value)
922            .deref(self.db.upcast())
923            .size_of(self.db.upcast(), SLOT_SIZE)
924    }
925
926    fn enter_scope(&mut self) {
927        let value_map = std::mem::take(&mut self.value_map);
928        self.value_map = ScopedValueMap::with_parent(value_map);
929    }
930
931    fn leave_scope(&mut self) {
932        let value_map = std::mem::take(&mut self.value_map);
933        self.value_map = value_map.into_parent();
934    }
935}
936
937#[derive(Debug, Default)]
938struct ScopedValueMap {
939    parent: Option<Box<ScopedValueMap>>,
940    map: FxHashMap<ValueId, yul::Identifier>,
941}
942
943impl ScopedValueMap {
944    fn lookup(&self, value: ValueId) -> Option<&yul::Identifier> {
945        match self.map.get(&value) {
946            Some(ident) => Some(ident),
947            None => self.parent.as_ref().and_then(|p| p.lookup(value)),
948        }
949    }
950
951    fn with_parent(parent: ScopedValueMap) -> Self {
952        Self {
953            parent: Some(parent.into()),
954            ..Self::default()
955        }
956    }
957
958    fn into_parent(self) -> Self {
959        *self.parent.unwrap()
960    }
961
962    fn insert(&mut self, value: ValueId, ident: yul::Identifier) {
963        self.map.insert(value, ident);
964    }
965
966    fn contains(&self, value: ValueId) -> bool {
967        self.lookup(value).is_some()
968    }
969}
970
971fn bit_mask(byte_size: usize) -> usize {
972    (1 << (byte_size * 8)) - 1
973}
974
975fn bit_mask_expr(byte_size: usize) -> yul::Expression {
976    let mask = format!("{:#x}", bit_mask(byte_size));
977    literal_expression! {(mask)}
978}