sway_core/
ir_generation.rs

1pub(crate) mod compile;
2pub mod const_eval;
3mod convert;
4mod function;
5mod lexical_map;
6mod purity;
7pub mod storage;
8mod types;
9
10use std::{
11    collections::HashMap,
12    hash::{DefaultHasher, Hasher},
13};
14
15use sway_error::error::CompileError;
16use sway_features::ExperimentalFeatures;
17use sway_ir::{Context, Function, InstOp, InstructionInserter, IrError, Kind, Module, Type, Value};
18use sway_types::{span::Span, Ident};
19
20pub(crate) use purity::{check_function_purity, PurityEnv};
21
22use crate::{
23    engine_threading::HashWithEngines,
24    language::ty,
25    metadata::MetadataManager,
26    types::{LogId, MessageId},
27    Engines, PanicOccurrences, TypeId,
28};
29
30type FnKey = u64;
31
32/// Every compiled function needs to go through this cache for two reasons:
33/// 1 - to have its IR name unique;
34/// 2 - to avoid being compiled twice.
35#[derive(Default)]
36pub(crate) struct CompiledFunctionCache {
37    recreated_fns: HashMap<FnKey, Function>,
38}
39
40impl CompiledFunctionCache {
41    #[allow(clippy::too_many_arguments)]
42    fn ty_function_decl_to_unique_function(
43        &mut self,
44        engines: &Engines,
45        context: &mut Context,
46        module: Module,
47        md_mgr: &mut MetadataManager,
48        decl: &ty::TyFunctionDecl,
49        logged_types_map: &HashMap<TypeId, LogId>,
50        messages_types_map: &HashMap<TypeId, MessageId>,
51        panic_occurrences: &mut PanicOccurrences,
52    ) -> Result<Function, CompileError> {
53        // The compiler inlines everything very lazily.  Function calls include the body of the
54        // callee (i.e., the callee_body arg above). Library functions are provided in an initial
55        // namespace from Forc and when the parser builds the AST (or is it during type checking?)
56        // these function bodies are embedded.
57        //
58        // Here we build little single-use instantiations of the callee and then call them.  Naming
59        // is not yet absolute so we must ensure the function names are unique.
60        //
61        // Eventually we need to Do It Properly and inline into the AST only when necessary, and
62        // compile the standard library to an actual module.
63        //
64        // Get the callee from the cache if we've already compiled it.  We can't insert it with
65        // .entry() since `compile_function()` returns a Result we need to handle.  The key to our
66        // cache, to uniquely identify a function instance, is the span and the type IDs of any
67        // args and type parameters.  It's using the Sway types rather than IR types, which would
68        // be more accurate but also more fiddly.
69
70        let mut hasher = DefaultHasher::default();
71        decl.hash(&mut hasher, engines);
72        let fn_key = hasher.finish();
73
74        let (fn_key, item) = (Some(fn_key), self.recreated_fns.get(&fn_key).copied());
75        let new_callee = match item {
76            Some(func) => func,
77            None => {
78                let name = Ident::new(Span::from_string(format!(
79                    "{}_{}",
80                    decl.name,
81                    context.get_unique_symbol_id()
82                )));
83                let callee_fn_decl = ty::TyFunctionDecl {
84                    type_parameters: Vec::new(),
85                    name,
86                    parameters: decl.parameters.clone(),
87                    ..decl.clone()
88                };
89                // Entry functions are already compiled at the top level
90                // when compiling scripts, predicates, contracts, and libraries.
91                let is_entry = false;
92                let is_original_entry = callee_fn_decl.is_main() || callee_fn_decl.is_test();
93                let new_func = compile::compile_function(
94                    engines,
95                    context,
96                    md_mgr,
97                    module,
98                    &callee_fn_decl,
99                    &decl.name,
100                    logged_types_map,
101                    messages_types_map,
102                    panic_occurrences,
103                    is_entry,
104                    is_original_entry,
105                    None,
106                    self,
107                )
108                .map_err(|mut x| x.pop().unwrap())?
109                .unwrap();
110
111                if let Some(fn_key) = fn_key {
112                    self.recreated_fns.insert(fn_key, new_func);
113                }
114
115                new_func
116            }
117        };
118
119        Ok(new_callee)
120    }
121}
122
123pub fn compile_program<'a>(
124    program: &ty::TyProgram,
125    panic_occurrences: &'a mut PanicOccurrences,
126    include_tests: bool,
127    engines: &'a Engines,
128    experimental: ExperimentalFeatures,
129) -> Result<Context<'a>, Vec<CompileError>> {
130    let declaration_engine = engines.de();
131
132    let test_fns = match include_tests {
133        true => program.test_fns(declaration_engine).collect(),
134        false => vec![],
135    };
136
137    let ty::TyProgram {
138        kind,
139        namespace,
140        logged_types,
141        messages_types,
142        declarations,
143        ..
144    } = program;
145
146    let logged_types = logged_types
147        .iter()
148        .map(|(log_id, type_id)| (*type_id, *log_id))
149        .collect();
150
151    let messages_types = messages_types
152        .iter()
153        .map(|(message_id, type_id)| (*type_id, *message_id))
154        .collect();
155
156    let mut ctx = Context::new(engines.se(), experimental);
157    ctx.program_kind = match kind {
158        ty::TyProgramKind::Script { .. } => Kind::Script,
159        ty::TyProgramKind::Predicate { .. } => Kind::Predicate,
160        ty::TyProgramKind::Contract { .. } => Kind::Contract,
161        ty::TyProgramKind::Library { .. } => Kind::Library,
162    };
163
164    let mut cache = CompiledFunctionCache::default();
165
166    match kind {
167        // Predicates and scripts have the same codegen, their only difference is static
168        // type-check time checks.
169        ty::TyProgramKind::Script { entry_function, .. } => compile::compile_script(
170            engines,
171            &mut ctx,
172            entry_function,
173            namespace,
174            &logged_types,
175            &messages_types,
176            panic_occurrences,
177            &test_fns,
178            &mut cache,
179        ),
180        ty::TyProgramKind::Predicate { entry_function, .. } => compile::compile_predicate(
181            engines,
182            &mut ctx,
183            entry_function,
184            namespace,
185            &logged_types,
186            &messages_types,
187            panic_occurrences,
188            &test_fns,
189            &mut cache,
190        ),
191        ty::TyProgramKind::Contract {
192            entry_function,
193            abi_entries,
194        } => compile::compile_contract(
195            &mut ctx,
196            entry_function.as_ref(),
197            abi_entries,
198            namespace,
199            declarations,
200            &logged_types,
201            &messages_types,
202            panic_occurrences,
203            &test_fns,
204            engines,
205            &mut cache,
206        ),
207        ty::TyProgramKind::Library { .. } => compile::compile_library(
208            engines,
209            &mut ctx,
210            namespace,
211            &logged_types,
212            &messages_types,
213            panic_occurrences,
214            &test_fns,
215            &mut cache,
216        ),
217    }?;
218
219    type_correction(&mut ctx).map_err(|ir_error: sway_ir::IrError| {
220        vec![CompileError::InternalOwned(
221            ir_error.to_string(),
222            Span::dummy(),
223        )]
224    })?;
225
226    ctx.verify().map_err(|ir_error: sway_ir::IrError| {
227        vec![CompileError::InternalOwned(
228            ir_error.to_string(),
229            Span::dummy(),
230        )]
231    })
232}
233
234fn type_correction(ctx: &mut Context) -> Result<(), IrError> {
235    struct TypeCorrection {
236        actual_ty: sway_ir::Type,
237        expected_ty: sway_ir::Type,
238        use_instr: sway_ir::Value,
239        use_idx: usize,
240    }
241    // This is a copy of sway_core::asm_generation::fuel::fuel_asm_builder::FuelAsmBuilder::is_copy_type.
242    fn is_copy_type(ty: &Type, context: &Context) -> bool {
243        ty.is_unit(context)
244            || ty.is_never(context)
245            || ty.is_bool(context)
246            || ty.is_ptr(context)
247            || ty.get_uint_width(context).map(|x| x < 256).unwrap_or(false)
248    }
249
250    let mut instrs_to_fix = Vec::new();
251    for module in ctx.module_iter() {
252        for function in module.function_iter(ctx) {
253            for (_block, instr) in function.instruction_iter(ctx).collect::<Vec<_>>() {
254                match &instr.get_instruction(ctx).unwrap().op {
255                    InstOp::Call(callee, actual_params) => {
256                        let formal_params: Vec<_> = callee.args_iter(ctx).collect();
257                        for (param_idx, (actual_param, (_, formal_param))) in
258                            actual_params.iter().zip(formal_params.iter()).enumerate()
259                        {
260                            let actual_ty = actual_param.get_type(ctx).unwrap();
261                            let formal_ty = formal_param.get_type(ctx).unwrap();
262                            if actual_ty != formal_ty {
263                                instrs_to_fix.push(TypeCorrection {
264                                    actual_ty,
265                                    expected_ty: formal_ty,
266                                    use_instr: instr,
267                                    use_idx: param_idx,
268                                });
269                            }
270                        }
271                    }
272                    InstOp::AsmBlock(_block, _args) => {
273                        // Non copy type args to asm blocks are passed by reference.
274                        let op = &instr.get_instruction(ctx).unwrap().op;
275                        let args = op
276                            .get_operands()
277                            .iter()
278                            .enumerate()
279                            .map(|(idx, init)| (idx, init.get_type(ctx).unwrap()))
280                            .collect::<Vec<_>>();
281                        for (arg_idx, arg_ty) in args {
282                            if !is_copy_type(&arg_ty, ctx) {
283                                instrs_to_fix.push(TypeCorrection {
284                                    actual_ty: arg_ty,
285                                    expected_ty: Type::new_ptr(ctx, arg_ty),
286                                    use_instr: instr,
287                                    use_idx: arg_idx,
288                                });
289                            }
290                        }
291                    }
292                    InstOp::GetElemPtr {
293                        base,
294                        elem_ptr_ty,
295                        indices,
296                    } => {
297                        let base_ty = base.get_type(ctx).unwrap();
298                        if let (Some(base_pointee_ty), Some(elem_inner_ty)) = (
299                            base_ty.get_pointee_type(ctx),
300                            elem_ptr_ty.get_pointee_type(ctx),
301                        ) {
302                            // The base is a pointer type. We need to see if it's a double pointer.
303                            if let Some(base_pointee_pointee_ty) =
304                                base_pointee_ty.get_pointee_type(ctx)
305                            {
306                                // We have a double pointer. If just loading once solves our problem, we do that.
307                                let indexed_ty =
308                                    base_pointee_pointee_ty.get_value_indexed_type(ctx, indices);
309                                if indexed_ty.is_some_and(|ty| ty == elem_inner_ty) {
310                                    instrs_to_fix.push(TypeCorrection {
311                                        actual_ty: base_ty,
312                                        expected_ty: base_pointee_ty,
313                                        use_instr: instr,
314                                        use_idx: indices.len(),
315                                    });
316                                }
317                            }
318                        } else {
319                            // The base is not a pointer type. If a pointer to base_ty works for us, do that.
320                            let elem_ptr_ty = *elem_ptr_ty;
321                            let indices = indices.clone(); // Cloning needed because of mutable and immutable borrow of `ctx`.
322                            let pointer_to_base = Type::new_ptr(ctx, base_ty);
323                            if pointer_to_base.get_value_indexed_type(ctx, &indices)
324                                == Some(elem_ptr_ty)
325                            {
326                                instrs_to_fix.push(TypeCorrection {
327                                    actual_ty: base_ty,
328                                    expected_ty: pointer_to_base,
329                                    use_instr: instr,
330                                    use_idx: indices.len(),
331                                });
332                            }
333                        }
334                    }
335                    InstOp::Store {
336                        dst_val_ptr,
337                        stored_val,
338                    } => {
339                        let dst_ty = dst_val_ptr.get_type(ctx).unwrap();
340                        let stored_ty = stored_val.get_type(ctx).unwrap();
341                        if let Some(dst_pointee_ty) = dst_ty.get_pointee_type(ctx) {
342                            // The destination is a pointer type. We need to see if it's a double pointer.
343                            if let Some(dst_pointee_pointee_ty) =
344                                dst_pointee_ty.get_pointee_type(ctx)
345                            {
346                                // We have a double pointer. If just loading once solves our problem, we do that.
347                                if dst_pointee_pointee_ty == stored_ty {
348                                    instrs_to_fix.push(TypeCorrection {
349                                        actual_ty: dst_ty,
350                                        expected_ty: dst_pointee_ty,
351                                        use_instr: instr,
352                                        use_idx: 0,
353                                    });
354                                }
355                            } else if let Some(stored_pointee_ty) = stored_ty.get_pointee_type(ctx)
356                            {
357                                // The value being stored is a pointer to what should've been stored.
358                                // So we just load the value and store it.
359                                if dst_pointee_ty == stored_pointee_ty {
360                                    instrs_to_fix.push(TypeCorrection {
361                                        actual_ty: stored_ty,
362                                        expected_ty: stored_pointee_ty,
363                                        use_instr: instr,
364                                        use_idx: 1,
365                                    });
366                                }
367                            }
368                        } else {
369                            // The destination is not a pointer type, but should've been.
370                            let pointer_to_dst = Type::new_ptr(ctx, dst_ty);
371                            if pointer_to_dst == stored_ty {
372                                instrs_to_fix.push(TypeCorrection {
373                                    actual_ty: dst_ty,
374                                    expected_ty: pointer_to_dst,
375                                    use_instr: instr,
376                                    use_idx: 0,
377                                });
378                            }
379                        }
380                    }
381                    InstOp::Ret(ret_val, ret_ty) => {
382                        if let Some(ret_val_pointee_ty) = ret_val
383                            .get_type(ctx)
384                            .and_then(|ret_val_ty| ret_val_ty.get_pointee_type(ctx))
385                        {
386                            if ret_val_pointee_ty == *ret_ty {
387                                instrs_to_fix.push(TypeCorrection {
388                                    actual_ty: ret_val.get_type(ctx).unwrap(),
389                                    expected_ty: *ret_ty,
390                                    use_instr: instr,
391                                    use_idx: 0,
392                                });
393                            }
394                        }
395                    }
396                    _ => (),
397                }
398            }
399        }
400    }
401
402    for TypeCorrection {
403        actual_ty,
404        expected_ty,
405        use_instr,
406        use_idx,
407    } in instrs_to_fix
408    {
409        let function = use_instr.get_instruction(ctx).unwrap().get_function(ctx);
410        if expected_ty
411            .get_pointee_type(ctx)
412            .is_some_and(|pointee| pointee == actual_ty)
413        {
414            // The expected type is a pointer to the actual type.
415            // If the actual value was just loaded, then we go to the source of the load,
416            // otherwise, we store it to a new local and pass the address of that local.
417            let actual_use = use_instr.get_instruction(ctx).unwrap().op.get_operands()[use_idx];
418            if let Some(InstOp::Load(src_ptr)) = actual_use.get_instruction(ctx).map(|i| &i.op) {
419                let src_ptr = *src_ptr;
420                use_instr
421                    .get_instruction_mut(ctx)
422                    .unwrap()
423                    .op
424                    .set_operand(src_ptr, use_idx);
425            } else {
426                let parent_block = use_instr.get_instruction(ctx).unwrap().parent;
427                let new_local = function.new_unique_local_var(
428                    ctx,
429                    "type_fix".to_string(),
430                    actual_ty,
431                    None,
432                    true,
433                );
434                let new_local =
435                    Value::new_instruction(ctx, parent_block, InstOp::GetLocal(new_local));
436                let store = Value::new_instruction(
437                    ctx,
438                    parent_block,
439                    InstOp::Store {
440                        dst_val_ptr: new_local,
441                        stored_val: actual_use,
442                    },
443                );
444                let mut inserter = InstructionInserter::new(
445                    ctx,
446                    parent_block,
447                    sway_ir::InsertionPosition::Before(use_instr),
448                );
449                inserter.insert_slice(&[new_local, store]);
450                // Update the use instruction to use the new local
451                use_instr
452                    .get_instruction_mut(ctx)
453                    .unwrap()
454                    .op
455                    .set_operand(new_local, use_idx);
456            }
457        } else if actual_ty
458            .get_pointee_type(ctx)
459            .is_some_and(|pointee| pointee == expected_ty)
460        {
461            // Just load the actual value.
462            let load = Value::new_instruction(
463                ctx,
464                use_instr.get_instruction(ctx).unwrap().parent,
465                InstOp::Load(use_instr.get_instruction(ctx).unwrap().op.get_operands()[use_idx]),
466            );
467            let mut inserter = InstructionInserter::new(
468                ctx,
469                use_instr.get_instruction(ctx).unwrap().parent,
470                sway_ir::InsertionPosition::Before(use_instr),
471            );
472            inserter.insert_slice(&[load]);
473            // Update the use instruction to use the new load
474            use_instr
475                .get_instruction_mut(ctx)
476                .unwrap()
477                .op
478                .set_operand(load, use_idx);
479        }
480    }
481    Ok(())
482}