fe_codegen/yul/isel/
contract.rs1use fe_analyzer::namespace::items::ContractId;
2use fe_mir::ir::{function::Linkage, FunctionId};
3use yultsur::{yul, *};
4
5use crate::{
6 db::CodegenDb,
7 yul::{runtime::AbiSrcLocation, YulVariable},
8};
9
10use super::context::Context;
11
12pub fn lower_contract_deployable(db: &dyn CodegenDb, contract: ContractId) -> yul::Object {
13 let mut context = Context::default();
14
15 let constructor = if let Some(init) = contract.init_function(db.upcast()) {
16 let init = db.mir_lowered_func_signature(init);
17 make_init(db, &mut context, contract, init)
18 } else {
19 statements! {}
20 };
21
22 let deploy_code = make_deploy(db, contract);
23
24 let dep_functions: Vec<_> = context
25 .resolve_function_dependency(db)
26 .into_iter()
27 .map(yul::Statement::FunctionDefinition)
28 .collect();
29 let runtime_funcs: Vec<_> = context
30 .runtime
31 .collect_definitions()
32 .into_iter()
33 .map(yul::Statement::FunctionDefinition)
34 .collect();
35
36 let deploy_block = block_statement! {
37 [constructor...]
38 [deploy_code...]
39 };
40
41 let code = code! {
42 [deploy_block]
43 [dep_functions...]
44 [runtime_funcs...]
45 };
46
47 let mut dep_contracts = context.resolve_contract_dependency(db);
48 dep_contracts.push(lower_contract(db, contract));
49 let dep_constants = context.resolve_constant_dependency(db);
50
51 let name = identifier! {(
52 db.codegen_contract_deployer_symbol_name(contract).as_ref()
53 )};
54 let object = yul::Object {
55 name,
56 code,
57 objects: dep_contracts,
58 data: dep_constants,
59 };
60
61 normalize_object(object)
62}
63
64pub fn lower_contract(db: &dyn CodegenDb, contract: ContractId) -> yul::Object {
65 let exported_funcs: Vec<_> = db
66 .mir_lower_contract_all_functions(contract)
67 .iter()
68 .filter_map(|fid| {
69 if fid.signature(db.upcast()).linkage == Linkage::Export {
70 Some(*fid)
71 } else {
72 None
73 }
74 })
75 .collect();
76
77 let mut context = Context::default();
78 let dispatcher = if let Some(call_fn) = contract.call_function(db.upcast()) {
79 let call_fn = db.mir_lowered_func_signature(call_fn);
80 context.function_dependency.insert(call_fn);
81 let call_symbol = identifier! { (db.codegen_function_symbol_name(call_fn)) };
82 statement! {
83 ([call_symbol]())
84 }
85 } else {
86 make_dispatcher(db, &mut context, &exported_funcs)
87 };
88
89 let dep_functions: Vec<_> = context
90 .resolve_function_dependency(db)
91 .into_iter()
92 .map(yul::Statement::FunctionDefinition)
93 .collect();
94 let runtime_funcs: Vec<_> = context
95 .runtime
96 .collect_definitions()
97 .into_iter()
98 .map(yul::Statement::FunctionDefinition)
99 .collect();
100
101 let code = code! {
102 ([dispatcher])
103 [dep_functions...]
104 [runtime_funcs...]
105 };
106
107 let dep_contracts = context.resolve_contract_dependency(db);
109
110 let dep_constants = context.resolve_constant_dependency(db);
112 let contract_symbol = identifier! { (db.codegen_contract_symbol_name(contract)) };
113
114 yul::Object {
115 name: contract_symbol,
116 code,
117 objects: dep_contracts,
118 data: dep_constants,
119 }
120}
121
122fn make_dispatcher(
123 db: &dyn CodegenDb,
124 context: &mut Context,
125 funcs: &[FunctionId],
126) -> yul::Statement {
127 let arms = funcs
128 .iter()
129 .map(|func| dispatch_arm(db, context, *func))
130 .collect::<Vec<_>>();
131
132 if arms.is_empty() {
133 statement! { return(0, 0) }
134 } else {
135 let selector = expression! {
136 and((shr((sub(256, 32)), (calldataload(0)))), 0xffffffff)
137 };
138 switch! {
139 switch ([selector])
140 [arms...]
141 (default { (return(0, 0)) })
142 }
143 }
144}
145
146fn dispatch_arm(db: &dyn CodegenDb, context: &mut Context, func: FunctionId) -> yul::Case {
147 context.function_dependency.insert(func);
148 let func_sig = db.codegen_legalized_signature(func);
149 let mut param_vars = Vec::with_capacity(func_sig.params.len());
150 let mut param_tys = Vec::with_capacity(func_sig.params.len());
151 func_sig.params.iter().for_each(|param| {
152 param_vars.push(YulVariable::new(param.name.as_str()));
153 param_tys.push(param.ty);
154 });
155
156 let decode_params = if func_sig.params.is_empty() {
157 statements! {}
158 } else {
159 let ident_params: Vec<_> = param_vars.iter().map(YulVariable::ident).collect();
160 let param_size = YulVariable::new("param_size");
161 statements! {
162 (let [param_size.ident()] := sub((calldatasize()), 4))
163 (let [ident_params...] := [context.runtime.abi_decode(db, expression! { 4 }, param_size.expr(), ¶m_tys, AbiSrcLocation::CallData)])
164 }
165 };
166
167 let call_and_encode_return = {
168 let name = identifier! { (db.codegen_function_symbol_name(func)) };
169 let call = expression! {[name]([(param_vars.iter().map(YulVariable::expr).collect::<Vec<_>>())...])};
171 if let Some(mut return_type) = func_sig.return_type {
172 if return_type.is_aggregate(db.upcast()) {
173 return_type = return_type.make_mptr(db.upcast());
174 }
175
176 let ret = YulVariable::new("ret");
177 let enc_start = YulVariable::new("enc_start");
178 let enc_size = YulVariable::new("enc_size");
179 let abi_encode = context.runtime.abi_encode_seq(
180 db,
181 &[ret.expr()],
182 enc_start.expr(),
183 &[return_type],
184 false,
185 );
186 statements! {
187 (let [ret.ident()] := [call])
188 (let [enc_start.ident()] := [context.runtime.avail(db)])
189 (let [enc_size.ident()] := [abi_encode])
190 (return([enc_start.expr()], [enc_size.expr()]))
191 }
192 } else {
193 statements! {
194 ([yul::Statement::Expression(call)])
195 (return(0, 0))
196 }
197 }
198 };
199
200 let abi_sig = db.codegen_abi_function(func);
201 let selector = literal! { (format!("0x{}", abi_sig.selector().hex())) };
202 case! {
203 case [selector] {
204 [decode_params...]
205 [call_and_encode_return...]
206 }
207 }
208}
209
210fn make_init(
211 db: &dyn CodegenDb,
212 context: &mut Context,
213 contract: ContractId,
214 init: FunctionId,
215) -> Vec<yul::Statement> {
216 context.function_dependency.insert(init);
217 let init_func_name = identifier! { (db.codegen_function_symbol_name(init)) };
218 let contract_name = identifier_expression! { (format!{r#""{}""#, db.codegen_contract_deployer_symbol_name(contract)}) };
219
220 let func_sig = db.codegen_legalized_signature(init);
221 let mut param_vars = Vec::with_capacity(func_sig.params.len());
222 let mut param_tys = Vec::with_capacity(func_sig.params.len());
223 let program_size = YulVariable::new("$program_size");
224 let arg_size = YulVariable::new("$arg_size");
225 let code_size = YulVariable::new("$code_size");
226 let memory_data_offset = YulVariable::new("$memory_data_offset");
227 func_sig.params.iter().for_each(|param| {
228 param_vars.push(YulVariable::new(param.name.as_str()));
229 param_tys.push(param.ty);
230 });
231
232 let decode_params = if func_sig.params.is_empty() {
233 statements! {}
234 } else {
235 let ident_params: Vec<_> = param_vars.iter().map(YulVariable::ident).collect();
236 statements! {
237 (let [ident_params...] := [context.runtime.abi_decode(db, memory_data_offset.expr(), arg_size.expr(), ¶m_tys, AbiSrcLocation::Memory)])
238 }
239 };
240
241 let call = expression! {[init_func_name]([(param_vars.iter().map(YulVariable::expr).collect::<Vec<_>>())...])};
242 statements! {
243 (let [program_size.ident()] := datasize([contract_name]))
244 (let [code_size.ident()] := codesize())
245 (let [arg_size.ident()] := sub([code_size.expr()], [program_size.expr()]))
246 (let [memory_data_offset.ident()] := [context.runtime.alloc(db, arg_size.expr())])
247 (codecopy([memory_data_offset.expr()], [program_size.expr()], [arg_size.expr()]))
248 [decode_params...]
249 ([yul::Statement::Expression(call)])
250 }
251}
252
253fn make_deploy(db: &dyn CodegenDb, contract: ContractId) -> Vec<yul::Statement> {
254 let contract_symbol =
255 identifier_expression! { (format!{r#""{}""#, db.codegen_contract_symbol_name(contract)}) };
256 let size = YulVariable::new("$$size");
257 statements! {
258 (let [size.ident()] := (datasize([contract_symbol.clone()])))
259 (datacopy(0, (dataoffset([contract_symbol])), [size.expr()]))
260 (return (0, [size.expr()]))
261 }
262}
263
264fn normalize_object(obj: yul::Object) -> yul::Object {
265 let data = obj
266 .data
267 .into_iter()
268 .map(|data| yul::Data {
269 name: data.name,
270 value: data
271 .value
272 .replace('\\', "\\\\\\\\")
273 .replace('\n', "\\\\n")
274 .replace('"', "\\\\\"")
275 .replace('\r', "\\\\r")
276 .replace('\t', "\\\\t"),
277 })
278 .collect::<Vec<_>>();
279 yul::Object {
280 name: obj.name,
281 code: obj.code,
282 objects: obj
283 .objects
284 .into_iter()
285 .map(normalize_object)
286 .collect::<Vec<_>>(),
287 data,
288 }
289}