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 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 _ => 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
201fn 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}