fe_codegen/yul/isel/
contract.rs

1use 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    // Lower dependant contracts.
108    let dep_contracts = context.resolve_contract_dependency(db);
109
110    // Collect string constants.
111    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(), &param_tys, AbiSrcLocation::CallData)])
164        }
165    };
166
167    let call_and_encode_return = {
168        let name = identifier! { (db.codegen_function_symbol_name(func)) };
169        // we pass in a `0` for the expected `Context` argument
170        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(), &param_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}