fe_codegen/yul/legalize/
body.rs

1use fe_mir::ir::{
2    body_cursor::{BodyCursor, CursorLocation},
3    inst::InstKind,
4    value::AssignableValue,
5    FunctionBody, Inst, InstId, TypeId, TypeKind, Value, ValueId,
6};
7
8use crate::db::CodegenDb;
9
10use super::critical_edge::CriticalEdgeSplitter;
11
12pub fn legalize_func_body(db: &dyn CodegenDb, body: &mut FunctionBody) {
13    CriticalEdgeSplitter::new().run(body);
14    legalize_func_arg(db, body);
15
16    let mut cursor = BodyCursor::new_at_entry(body);
17    loop {
18        match cursor.loc() {
19            CursorLocation::BlockTop(_) | CursorLocation::BlockBottom(_) => cursor.proceed(),
20            CursorLocation::Inst(inst) => {
21                legalize_inst(db, &mut cursor, inst);
22            }
23            CursorLocation::NoWhere => break,
24        }
25    }
26}
27
28fn legalize_func_arg(db: &dyn CodegenDb, body: &mut FunctionBody) {
29    for value in body.store.func_args_mut() {
30        let ty = value.ty();
31        if ty.is_contract(db.upcast()) {
32            let slot_ptr = make_storage_ptr(db, ty);
33            *value = slot_ptr;
34        } else if (ty.is_aggregate(db.upcast()) || ty.is_string(db.upcast()))
35            && !ty.is_zero_sized(db.upcast())
36        {
37            change_ty(value, ty.make_mptr(db.upcast()))
38        }
39    }
40}
41
42fn legalize_inst(db: &dyn CodegenDb, cursor: &mut BodyCursor, inst: InstId) {
43    if legalize_unit_construct(db, cursor, inst) {
44        return;
45    }
46    legalize_declared_ty(db, cursor.body_mut(), inst);
47    legalize_inst_arg(db, cursor.body_mut(), inst);
48    legalize_inst_result(db, cursor.body_mut(), inst);
49    cursor.proceed();
50}
51
52fn legalize_unit_construct(db: &dyn CodegenDb, cursor: &mut BodyCursor, inst: InstId) -> bool {
53    let should_remove = match &cursor.body().store.inst_data(inst).kind {
54        InstKind::Declare { local } => is_value_zst(db, cursor.body(), *local),
55        InstKind::AggregateConstruct { ty, .. } => ty.deref(db.upcast()).is_zero_sized(db.upcast()),
56        InstKind::AggregateAccess { .. } | InstKind::MapAccess { .. } | InstKind::Cast { .. } => {
57            let result_value = cursor.body().store.inst_result(inst).unwrap();
58            is_lvalue_zst(db, cursor.body(), result_value)
59        }
60
61        _ => false,
62    };
63
64    if should_remove {
65        cursor.remove_inst()
66    }
67
68    should_remove
69}
70
71fn legalize_declared_ty(db: &dyn CodegenDb, body: &mut FunctionBody, inst_id: InstId) {
72    let value = match &body.store.inst_data(inst_id).kind {
73        InstKind::Declare { local } => *local,
74        _ => return,
75    };
76
77    let value_ty = body.store.value_ty(value);
78    if value_ty.is_aggregate(db.upcast()) {
79        let new_ty = value_ty.make_mptr(db.upcast());
80        let value_data = body.store.value_data_mut(value);
81        change_ty(value_data, new_ty)
82    }
83}
84
85fn legalize_inst_arg(db: &dyn CodegenDb, body: &mut FunctionBody, inst_id: InstId) {
86    // Replace inst with dummy inst to avoid borrow checker complaining.
87    let dummy_inst = Inst::nop();
88    let mut inst = body.store.replace_inst(inst_id, dummy_inst);
89
90    for arg in inst.args() {
91        let ty = body.store.value_ty(arg);
92        if ty.is_string(db.upcast()) {
93            let string_ptr = ty.make_mptr(db.upcast());
94            change_ty(body.store.value_data_mut(arg), string_ptr)
95        }
96    }
97
98    match &mut inst.kind {
99        InstKind::AggregateConstruct { args, .. } => {
100            args.retain(|arg| !is_value_zst(db, body, *arg));
101        }
102
103        InstKind::Call { args, .. } => {
104            args.retain(|arg| !is_value_zst(db, body, *arg) && !is_value_contract(db, body, *arg))
105        }
106
107        InstKind::Return { arg } => {
108            if arg.map(|arg| is_value_zst(db, body, arg)).unwrap_or(false) {
109                *arg = None;
110            }
111        }
112
113        InstKind::MapAccess { key: arg, .. } | InstKind::Emit { arg } => {
114            let arg_ty = body.store.value_ty(*arg);
115            if arg_ty.is_zero_sized(db.upcast()) {
116                *arg = body.store.store_value(make_zst_ptr(db, arg_ty));
117            }
118        }
119
120        InstKind::Cast { value, to, .. } => {
121            if to.is_aggregate(db.upcast()) && !to.is_zero_sized(db.upcast()) {
122                let value_ty = body.store.value_ty(*value);
123                if value_ty.is_mptr(db.upcast()) {
124                    *to = to.make_mptr(db.upcast());
125                } else if value_ty.is_sptr(db.upcast()) {
126                    *to = to.make_sptr(db.upcast());
127                } else {
128                    unreachable!()
129                }
130            }
131        }
132
133        _ => {}
134    }
135
136    body.store.replace_inst(inst_id, inst);
137}
138
139fn legalize_inst_result(db: &dyn CodegenDb, body: &mut FunctionBody, inst_id: InstId) {
140    let result_value = if let Some(result) = body.store.inst_result(inst_id) {
141        result
142    } else {
143        return;
144    };
145
146    if is_lvalue_zst(db, body, result_value) {
147        body.store.remove_inst_result(inst_id);
148        return;
149    };
150
151    let value_id = if let Some(value_id) = result_value.value_id() {
152        value_id
153    } else {
154        return;
155    };
156    let result_ty = body.store.value_ty(value_id);
157    let new_ty = if result_ty.is_aggregate(db.upcast()) || result_ty.is_string(db.upcast()) {
158        match &body.store.inst_data(inst_id).kind {
159            InstKind::AggregateAccess { value, .. } => {
160                let value_ty = body.store.value_ty(*value);
161                match &value_ty.data(db.upcast()).kind {
162                    TypeKind::MPtr(..) => result_ty.make_mptr(db.upcast()),
163                    // Note: All SPtr aggregate access results should be SPtr already
164                    _ => unreachable!(),
165                }
166            }
167            _ => result_ty.make_mptr(db.upcast()),
168        }
169    } else {
170        return;
171    };
172
173    let value = body.store.value_data_mut(value_id);
174    change_ty(value, new_ty);
175}
176
177fn change_ty(value: &mut Value, new_ty: TypeId) {
178    match value {
179        Value::Local(val) => val.ty = new_ty,
180        Value::Immediate { ty, .. }
181        | Value::Temporary { ty, .. }
182        | Value::Unit { ty }
183        | Value::Constant { ty, .. } => *ty = new_ty,
184    }
185}
186
187fn make_storage_ptr(db: &dyn CodegenDb, ty: TypeId) -> Value {
188    debug_assert!(ty.is_contract(db.upcast()));
189    let ty = ty.make_sptr(db.upcast());
190
191    Value::Immediate { imm: 0.into(), ty }
192}
193
194fn make_zst_ptr(db: &dyn CodegenDb, ty: TypeId) -> Value {
195    debug_assert!(ty.is_zero_sized(db.upcast()));
196    let ty = ty.make_mptr(db.upcast());
197
198    Value::Immediate { imm: 0.into(), ty }
199}
200
201/// Returns `true` if a value has a zero sized type.
202fn is_value_zst(db: &dyn CodegenDb, body: &FunctionBody, value: ValueId) -> bool {
203    body.store
204        .value_ty(value)
205        .deref(db.upcast())
206        .is_zero_sized(db.upcast())
207}
208
209fn is_value_contract(db: &dyn CodegenDb, body: &FunctionBody, value: ValueId) -> bool {
210    let ty = body.store.value_ty(value);
211    ty.deref(db.upcast()).is_contract(db.upcast())
212}
213
214fn is_lvalue_zst(db: &dyn CodegenDb, body: &FunctionBody, lvalue: &AssignableValue) -> bool {
215    lvalue
216        .ty(db.upcast(), &body.store)
217        .deref(db.upcast())
218        .is_zero_sized(db.upcast())
219}