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::{
18    Backtrace, Context, Function, InstOp, InstructionInserter, IrError, Kind, Module, Type, Value,
19};
20use sway_types::{span::Span, Ident};
21
22pub(crate) use purity::{check_function_purity, PurityEnv};
23
24use crate::{
25    engine_threading::HashWithEngines,
26    ir_generation::function::FnCompiler,
27    language::ty::{self, TyCodeBlock, TyExpression, TyFunctionDecl, TyReassignmentTarget},
28    metadata::MetadataManager,
29    types::{LogId, MessageId},
30    Engines, PanicOccurrences, PanickingCallOccurrences, TypeId,
31};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub(crate) struct FnKey(u64);
35
36impl FnKey {
37    fn new(decl: &TyFunctionDecl, engines: &Engines) -> Self {
38        let mut hasher = DefaultHasher::default();
39        decl.hash(&mut hasher, engines);
40        let key = hasher.finish();
41
42        Self(key)
43    }
44}
45
46/// Groups a [TyFunctionDecl] with its [FnKey].
47pub(crate) struct KeyedTyFunctionDecl<'a> {
48    key: FnKey,
49    decl: &'a TyFunctionDecl,
50}
51
52impl<'a> KeyedTyFunctionDecl<'a> {
53    fn new(decl: &'a TyFunctionDecl, engines: &'a Engines) -> Self {
54        Self {
55            key: FnKey::new(decl, engines),
56            decl,
57        }
58    }
59}
60
61/// Every compiled function needs to go through this cache for two reasons:
62/// 1. to have its IR name unique;
63/// 2. to avoid being compiled twice.
64#[derive(Default)]
65pub(crate) struct CompiledFunctionCache {
66    cache: HashMap<FnKey, Function>,
67}
68
69impl CompiledFunctionCache {
70    #[allow(clippy::too_many_arguments)]
71    fn get_compiled_function(
72        &mut self,
73        engines: &Engines,
74        context: &mut Context,
75        module: Module,
76        md_mgr: &mut MetadataManager,
77        keyed_decl: &KeyedTyFunctionDecl,
78        logged_types_map: &HashMap<TypeId, LogId>,
79        messages_types_map: &HashMap<TypeId, MessageId>,
80        panic_occurrences: &mut PanicOccurrences,
81        panicking_call_occurrences: &mut PanickingCallOccurrences,
82        panicking_fn_cache: &mut PanickingFunctionCache,
83    ) -> Result<Function, CompileError> {
84        let fn_key = keyed_decl.key;
85        let decl = keyed_decl.decl;
86
87        let new_callee = match self.cache.get(&fn_key) {
88            Some(func) => *func,
89            None => {
90                let name = Ident::new(Span::from_string(format!(
91                    "{}_{}",
92                    decl.name,
93                    context.get_unique_symbol_id()
94                )));
95                let callee_fn_decl = ty::TyFunctionDecl {
96                    type_parameters: Vec::new(),
97                    name,
98                    parameters: decl.parameters.clone(),
99                    ..decl.clone()
100                };
101                // Entry functions are already compiled at the top level
102                // when compiling scripts, predicates, contracts, and libraries.
103                let is_entry = false;
104                let is_original_entry = callee_fn_decl.is_main() || callee_fn_decl.is_test();
105                let new_func = compile::compile_function(
106                    engines,
107                    context,
108                    md_mgr,
109                    module,
110                    &callee_fn_decl,
111                    &decl.name,
112                    FnCompiler::fn_abi_errors_display(decl, engines),
113                    logged_types_map,
114                    messages_types_map,
115                    panic_occurrences,
116                    panicking_call_occurrences,
117                    panicking_fn_cache,
118                    is_entry,
119                    is_original_entry,
120                    None,
121                    self,
122                )
123                .map_err(|mut x| x.pop().unwrap())?
124                .unwrap();
125
126                self.cache.insert(fn_key, new_func);
127
128                new_func
129            }
130        };
131
132        Ok(new_callee)
133    }
134}
135
136#[derive(Default)]
137pub(crate) struct PanickingFunctionCache {
138    cache: HashMap<FnKey, bool>,
139}
140
141impl PanickingFunctionCache {
142    /// Returns `true` if the function represented by `keyed_decl` can panic.
143    ///
144    /// By definition, a function can panic, and have the `__backtrace` argument
145    /// added, *if it is not an entry or original entry* and if it contains a
146    /// `panic` expression, or calls functions that contain `panic` expressions,
147    /// recursively.
148    ///
149    /// Note that "can panic" is purely an IR concept that does not exist in the AST.
150    /// The reason is, because we don't have a language, frontend, concept of "can panic",
151    /// that we can check during the type checking phase. This would require an attribute
152    /// or a similar mechanism to mark functions as "can panic", which we do not want to
153    /// have.
154    ///
155    /// Because of this, we can cannot check during the type checking phase if a
156    /// generic function can panic. E.g., in the below example, `foo` needs to be
157    /// monomorphized to check if it can panic, and "can panic" can be different
158    /// for different monomorphized versions of the function:
159    ///
160    /// ```sway
161    /// fn foo<T>() where T: DoSomething {
162    ///     T::do_something();
163    /// }
164    /// ```
165    pub(crate) fn can_panic(
166        &mut self,
167        keyed_decl: &KeyedTyFunctionDecl,
168        engines: &Engines,
169    ) -> bool {
170        let fn_key = keyed_decl.key;
171        let decl = keyed_decl.decl;
172
173        // Function must not be an entry or original entry (test or main).
174        if !decl.is_default() {
175            return false;
176        }
177
178        match self.cache.get(&fn_key) {
179            Some(can_panic) => *can_panic,
180            None => {
181                let can_panic = self.can_code_block_panic(&decl.body, engines);
182                self.cache.insert(fn_key, can_panic);
183                can_panic
184            }
185        }
186    }
187
188    fn can_code_block_panic(&mut self, body: &TyCodeBlock, engines: &Engines) -> bool {
189        for node in body.contents.iter() {
190            use ty::TyAstNodeContent::*;
191            match &node.content {
192                Declaration(ty_decl) => {
193                    if let ty::TyDecl::VariableDecl(var_decl) = ty_decl {
194                        if self.can_expression_panic(&var_decl.body, engines) {
195                            return true;
196                        }
197                    }
198                }
199                Expression(expr) => {
200                    if self.can_expression_panic(expr, engines) {
201                        return true;
202                    }
203                }
204                SideEffect(_) | Error(_, _) => {}
205            }
206        }
207
208        false
209    }
210
211    fn can_expression_panic(&mut self, expr: &TyExpression, engines: &Engines) -> bool {
212        use ty::TyExpressionVariant::*;
213        match &expr.expression {
214            // `Panic` panics by definition.
215            Panic(_) => true,
216
217            // `FunctionApplication` can panic if the callee can panic.
218            FunctionApplication { fn_ref, .. } => {
219                let decl = engines.de().get_function(fn_ref.id());
220                let keyed_decl = KeyedTyFunctionDecl::new(&decl, engines);
221                // TODO: Add support for recursive functions once https://github.com/FuelLabs/sway/issues/3018 gets developed.
222                self.can_panic(&keyed_decl, engines)
223            }
224
225            // Expressions with a single expression that could panic.
226            MatchExp {
227                desugared: expr, ..
228            }
229            | StructFieldAccess { prefix: expr, .. }
230            | TupleElemAccess { prefix: expr, .. }
231            | AbiCast { address: expr, .. }
232            | EnumTag { exp: expr }
233            | UnsafeDowncast { exp: expr, .. }
234            | ForLoop { desugared: expr }
235            | ImplicitReturn(expr)
236            | Return(expr)
237            | Ref(expr)
238            | Deref(expr) => self.can_expression_panic(expr, engines),
239
240            // Expressions with multiple sub-expressions that could panic.
241            LazyOperator { lhs, rhs, .. } => {
242                self.can_expression_panic(lhs, engines) || self.can_expression_panic(rhs, engines)
243            }
244            Tuple { fields } => fields
245                .iter()
246                .any(|field| self.can_expression_panic(field, engines)),
247            ArrayExplicit { contents, .. } => contents
248                .iter()
249                .any(|elem| self.can_expression_panic(elem, engines)),
250            ArrayRepeat { value, length, .. } => {
251                self.can_expression_panic(value, engines)
252                    || self.can_expression_panic(length, engines)
253            }
254            ArrayIndex { prefix, index } => {
255                self.can_expression_panic(prefix, engines)
256                    || self.can_expression_panic(index, engines)
257            }
258            StructExpression { fields, .. } => fields
259                .iter()
260                .any(|field| self.can_expression_panic(&field.value, engines)),
261            IfExp {
262                condition,
263                then,
264                r#else,
265            } => {
266                self.can_expression_panic(condition, engines)
267                    || self.can_expression_panic(then, engines)
268                    || r#else
269                        .as_ref()
270                        .map_or(false, |r#else| self.can_expression_panic(r#else, engines))
271            }
272            AsmExpression { registers, .. } => registers.iter().any(|reg| {
273                reg.initializer
274                    .as_ref()
275                    .is_some_and(|init| self.can_expression_panic(init, engines))
276            }),
277            EnumInstantiation { contents, .. } => contents
278                .as_ref()
279                .is_some_and(|contents| self.can_expression_panic(contents, engines)),
280            WhileLoop { condition, body } => {
281                self.can_expression_panic(condition, engines)
282                    || self.can_code_block_panic(body, engines)
283            }
284            Reassignment(reassignment) => match &reassignment.lhs {
285                TyReassignmentTarget::ElementAccess { indices, .. } => {
286                    indices.iter().any(|index| match index {
287                        ty::ProjectionKind::StructField { .. }
288                        | ty::ProjectionKind::TupleField { .. } => false,
289                        ty::ProjectionKind::ArrayIndex { index, .. } => {
290                            self.can_expression_panic(index, engines)
291                        }
292                    })
293                }
294                TyReassignmentTarget::DerefAccess { exp, indices } => {
295                    self.can_expression_panic(exp, engines)
296                        || indices.iter().any(|index| match index {
297                            ty::ProjectionKind::StructField { .. }
298                            | ty::ProjectionKind::TupleField { .. } => false,
299                            ty::ProjectionKind::ArrayIndex { index, .. } => {
300                                self.can_expression_panic(index, engines)
301                            }
302                        })
303                }
304            },
305
306            CodeBlock(block) => self.can_code_block_panic(block, engines),
307
308            // Expressions that cannot panic.
309            Literal(_)
310            | ConstantExpression { .. }
311            | ConfigurableExpression { .. }
312            | ConstGenericExpression { .. }
313            | VariableExpression { .. }
314            | FunctionParameter
315            | StorageAccess(_)
316            | IntrinsicFunction(_)
317            | AbiName(_)
318            | Break
319            | Continue => false,
320        }
321    }
322}
323
324pub fn compile_program<'a>(
325    program: &ty::TyProgram,
326    panic_occurrences: &'a mut PanicOccurrences,
327    panicking_call_occurrences: &'a mut PanickingCallOccurrences,
328    include_tests: bool,
329    engines: &'a Engines,
330    experimental: ExperimentalFeatures,
331    backtrace: Backtrace,
332) -> Result<Context<'a>, Vec<CompileError>> {
333    let declaration_engine = engines.de();
334
335    let test_fns = match include_tests {
336        true => program.test_fns(declaration_engine).collect(),
337        false => vec![],
338    };
339
340    let ty::TyProgram {
341        kind,
342        namespace,
343        logged_types,
344        messages_types,
345        declarations,
346        ..
347    } = program;
348
349    let logged_types = logged_types
350        .iter()
351        .map(|(log_id, type_id)| (*type_id, *log_id))
352        .collect();
353
354    let messages_types = messages_types
355        .iter()
356        .map(|(message_id, type_id)| (*type_id, *message_id))
357        .collect();
358
359    let mut ctx = Context::new(engines.se(), experimental, backtrace);
360    ctx.program_kind = match kind {
361        ty::TyProgramKind::Script { .. } => Kind::Script,
362        ty::TyProgramKind::Predicate { .. } => Kind::Predicate,
363        ty::TyProgramKind::Contract { .. } => Kind::Contract,
364        ty::TyProgramKind::Library { .. } => Kind::Library,
365    };
366
367    let mut compiled_fn_cache = CompiledFunctionCache::default();
368    let mut panicking_fn_cache = PanickingFunctionCache::default();
369
370    match kind {
371        // Predicates and scripts have the same codegen, their only difference is static
372        // type-check time checks.
373        ty::TyProgramKind::Script { entry_function, .. } => compile::compile_script(
374            engines,
375            &mut ctx,
376            entry_function,
377            namespace,
378            &logged_types,
379            &messages_types,
380            panic_occurrences,
381            panicking_call_occurrences,
382            &mut panicking_fn_cache,
383            &test_fns,
384            &mut compiled_fn_cache,
385        ),
386        ty::TyProgramKind::Predicate { entry_function, .. } => compile::compile_predicate(
387            engines,
388            &mut ctx,
389            entry_function,
390            namespace,
391            &logged_types,
392            &messages_types,
393            panic_occurrences,
394            panicking_call_occurrences,
395            &mut panicking_fn_cache,
396            &test_fns,
397            &mut compiled_fn_cache,
398        ),
399        ty::TyProgramKind::Contract {
400            entry_function,
401            abi_entries,
402        } => compile::compile_contract(
403            &mut ctx,
404            entry_function.as_ref(),
405            abi_entries,
406            namespace,
407            declarations,
408            &logged_types,
409            &messages_types,
410            panic_occurrences,
411            panicking_call_occurrences,
412            &mut panicking_fn_cache,
413            &test_fns,
414            engines,
415            &mut compiled_fn_cache,
416        ),
417        ty::TyProgramKind::Library { .. } => compile::compile_library(
418            engines,
419            &mut ctx,
420            namespace,
421            &logged_types,
422            &messages_types,
423            panic_occurrences,
424            panicking_call_occurrences,
425            &mut panicking_fn_cache,
426            &test_fns,
427            &mut compiled_fn_cache,
428        ),
429    }?;
430
431    type_correction(&mut ctx).map_err(|ir_error: sway_ir::IrError| {
432        vec![CompileError::InternalOwned(
433            ir_error.to_string(),
434            Span::dummy(),
435        )]
436    })?;
437
438    ctx.verify().map_err(|ir_error: sway_ir::IrError| {
439        vec![CompileError::InternalOwned(
440            ir_error.to_string(),
441            Span::dummy(),
442        )]
443    })?;
444    Ok(ctx)
445}
446
447fn type_correction(ctx: &mut Context) -> Result<(), IrError> {
448    struct TypeCorrection {
449        actual_ty: sway_ir::Type,
450        expected_ty: sway_ir::Type,
451        use_instr: sway_ir::Value,
452        use_idx: usize,
453    }
454    // This is a copy of sway_core::asm_generation::fuel::fuel_asm_builder::FuelAsmBuilder::is_copy_type.
455    fn is_copy_type(ty: &Type, context: &Context) -> bool {
456        ty.is_unit(context)
457            || ty.is_never(context)
458            || ty.is_bool(context)
459            || ty.is_ptr(context)
460            || ty.get_uint_width(context).map(|x| x < 256).unwrap_or(false)
461    }
462
463    let mut instrs_to_fix = Vec::new();
464    for module in ctx.module_iter() {
465        for function in module.function_iter(ctx) {
466            for (_block, instr) in function.instruction_iter(ctx).collect::<Vec<_>>() {
467                match &instr.get_instruction(ctx).unwrap().op {
468                    InstOp::Call(callee, actual_params) => {
469                        let formal_params: Vec<_> = callee.args_iter(ctx).collect();
470                        for (param_idx, (actual_param, (_, formal_param))) in
471                            actual_params.iter().zip(formal_params.iter()).enumerate()
472                        {
473                            let actual_ty = actual_param.get_type(ctx).unwrap();
474                            let formal_ty = formal_param.get_type(ctx).unwrap();
475                            if actual_ty != formal_ty {
476                                instrs_to_fix.push(TypeCorrection {
477                                    actual_ty,
478                                    expected_ty: formal_ty,
479                                    use_instr: instr,
480                                    use_idx: param_idx,
481                                });
482                            }
483                        }
484                    }
485                    InstOp::AsmBlock(_block, _args) => {
486                        // Non copy type args to asm blocks are passed by reference.
487                        let op = &instr.get_instruction(ctx).unwrap().op;
488                        let args = op
489                            .get_operands()
490                            .iter()
491                            .enumerate()
492                            .map(|(idx, init)| (idx, init.get_type(ctx).unwrap()))
493                            .collect::<Vec<_>>();
494                        for (arg_idx, arg_ty) in args {
495                            if !is_copy_type(&arg_ty, ctx) {
496                                instrs_to_fix.push(TypeCorrection {
497                                    actual_ty: arg_ty,
498                                    expected_ty: Type::new_typed_pointer(ctx, arg_ty),
499                                    use_instr: instr,
500                                    use_idx: arg_idx,
501                                });
502                            }
503                        }
504                    }
505                    InstOp::GetElemPtr {
506                        base,
507                        elem_ptr_ty,
508                        indices,
509                    } => {
510                        let base_ty = base.get_type(ctx).unwrap();
511                        if let (Some(base_pointee_ty), Some(elem_inner_ty)) = (
512                            base_ty.get_pointee_type(ctx),
513                            elem_ptr_ty.get_pointee_type(ctx),
514                        ) {
515                            // The base is a pointer type. We need to see if it's a double pointer.
516                            if let Some(base_pointee_pointee_ty) =
517                                base_pointee_ty.get_pointee_type(ctx)
518                            {
519                                // We have a double pointer. If just loading once solves our problem, we do that.
520                                let indexed_ty =
521                                    base_pointee_pointee_ty.get_value_indexed_type(ctx, indices);
522                                if indexed_ty.is_some_and(|ty| ty == elem_inner_ty) {
523                                    instrs_to_fix.push(TypeCorrection {
524                                        actual_ty: base_ty,
525                                        expected_ty: base_pointee_ty,
526                                        use_instr: instr,
527                                        use_idx: indices.len(),
528                                    });
529                                }
530                            }
531                        } else {
532                            // The base is not a pointer type. If a pointer to base_ty works for us, do that.
533                            let elem_ptr_ty = *elem_ptr_ty;
534                            let indices = indices.clone(); // Cloning needed because of mutable and immutable borrow of `ctx`.
535                            let pointer_to_base = Type::new_typed_pointer(ctx, base_ty);
536                            if pointer_to_base.get_value_indexed_type(ctx, &indices)
537                                == Some(elem_ptr_ty)
538                            {
539                                instrs_to_fix.push(TypeCorrection {
540                                    actual_ty: base_ty,
541                                    expected_ty: pointer_to_base,
542                                    use_instr: instr,
543                                    use_idx: indices.len(),
544                                });
545                            }
546                        }
547                    }
548                    InstOp::Store {
549                        dst_val_ptr,
550                        stored_val,
551                    } => {
552                        let dst_ty = dst_val_ptr.get_type(ctx).unwrap();
553                        let stored_ty = stored_val.get_type(ctx).unwrap();
554                        if let Some(dst_pointee_ty) = dst_ty.get_pointee_type(ctx) {
555                            // The destination is a pointer type. We need to see if it's a double pointer.
556                            if let Some(dst_pointee_pointee_ty) =
557                                dst_pointee_ty.get_pointee_type(ctx)
558                            {
559                                // We have a double pointer. If just loading once solves our problem, we do that.
560                                if dst_pointee_pointee_ty == stored_ty {
561                                    instrs_to_fix.push(TypeCorrection {
562                                        actual_ty: dst_ty,
563                                        expected_ty: dst_pointee_ty,
564                                        use_instr: instr,
565                                        use_idx: 0,
566                                    });
567                                }
568                            } else if let Some(stored_pointee_ty) = stored_ty.get_pointee_type(ctx)
569                            {
570                                // The value being stored is a pointer to what should've been stored.
571                                // So we just load the value and store it.
572                                if dst_pointee_ty == stored_pointee_ty {
573                                    instrs_to_fix.push(TypeCorrection {
574                                        actual_ty: stored_ty,
575                                        expected_ty: stored_pointee_ty,
576                                        use_instr: instr,
577                                        use_idx: 1,
578                                    });
579                                }
580                            }
581                        } else {
582                            // The destination is not a pointer type, but should've been.
583                            let pointer_to_dst = Type::new_typed_pointer(ctx, dst_ty);
584                            if pointer_to_dst == stored_ty {
585                                instrs_to_fix.push(TypeCorrection {
586                                    actual_ty: dst_ty,
587                                    expected_ty: pointer_to_dst,
588                                    use_instr: instr,
589                                    use_idx: 0,
590                                });
591                            }
592                        }
593                    }
594                    InstOp::Ret(ret_val, ret_ty) => {
595                        if let Some(ret_val_pointee_ty) = ret_val
596                            .get_type(ctx)
597                            .and_then(|ret_val_ty| ret_val_ty.get_pointee_type(ctx))
598                        {
599                            if ret_val_pointee_ty == *ret_ty {
600                                instrs_to_fix.push(TypeCorrection {
601                                    actual_ty: ret_val.get_type(ctx).unwrap(),
602                                    expected_ty: *ret_ty,
603                                    use_instr: instr,
604                                    use_idx: 0,
605                                });
606                            }
607                        }
608                    }
609                    _ => (),
610                }
611            }
612        }
613    }
614
615    for TypeCorrection {
616        actual_ty,
617        expected_ty,
618        use_instr,
619        use_idx,
620    } in instrs_to_fix
621    {
622        let function = use_instr.get_instruction(ctx).unwrap().get_function(ctx);
623        if expected_ty
624            .get_pointee_type(ctx)
625            .is_some_and(|pointee| pointee == actual_ty)
626        {
627            // The expected type is a pointer to the actual type.
628            // If the actual value was just loaded, then we go to the source of the load,
629            // otherwise, we store it to a new local and pass the address of that local.
630            let actual_use = use_instr.get_instruction(ctx).unwrap().op.get_operands()[use_idx];
631            if let Some(InstOp::Load(src_ptr)) = actual_use.get_instruction(ctx).map(|i| &i.op) {
632                let src_ptr = *src_ptr;
633                use_instr
634                    .get_instruction_mut(ctx)
635                    .unwrap()
636                    .op
637                    .set_operand(src_ptr, use_idx);
638            } else {
639                let parent_block = use_instr.get_instruction(ctx).unwrap().parent;
640                let new_local = function.new_unique_local_var(
641                    ctx,
642                    "type_fix".to_string(),
643                    actual_ty,
644                    None,
645                    true,
646                );
647                let new_local =
648                    Value::new_instruction(ctx, parent_block, InstOp::GetLocal(new_local));
649                let store = Value::new_instruction(
650                    ctx,
651                    parent_block,
652                    InstOp::Store {
653                        dst_val_ptr: new_local,
654                        stored_val: actual_use,
655                    },
656                );
657                let mut inserter = InstructionInserter::new(
658                    ctx,
659                    parent_block,
660                    sway_ir::InsertionPosition::Before(use_instr),
661                );
662                inserter.insert_slice(&[new_local, store]);
663                // Update the use instruction to use the new local
664                use_instr
665                    .get_instruction_mut(ctx)
666                    .unwrap()
667                    .op
668                    .set_operand(new_local, use_idx);
669            }
670        } else if actual_ty
671            .get_pointee_type(ctx)
672            .is_some_and(|pointee| pointee == expected_ty)
673        {
674            // Just load the actual value.
675            let load = Value::new_instruction(
676                ctx,
677                use_instr.get_instruction(ctx).unwrap().parent,
678                InstOp::Load(use_instr.get_instruction(ctx).unwrap().op.get_operands()[use_idx]),
679            );
680            let mut inserter = InstructionInserter::new(
681                ctx,
682                use_instr.get_instruction(ctx).unwrap().parent,
683                sway_ir::InsertionPosition::Before(use_instr),
684            );
685            inserter.insert_slice(&[load]);
686            // Update the use instruction to use the new load
687            use_instr
688                .get_instruction_mut(ctx)
689                .unwrap()
690                .op
691                .set_operand(load, use_idx);
692        }
693    }
694    Ok(())
695}