diff --git a/Cargo.toml b/Cargo.toml index 1d8eebcb3..d1daf2c89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "compiler/vm", "compiler/vm/fuzz", ] +default-members = ["compiler/cli"] [workspace.package] edition = "2021" diff --git a/compiler/frontend/src/mir_optimize/current_expression.rs b/compiler/frontend/src/mir_optimize/current_expression.rs index 9c3dbc291..e82b62170 100644 --- a/compiler/frontend/src/mir_optimize/current_expression.rs +++ b/compiler/frontend/src/mir_optimize/current_expression.rs @@ -1,4 +1,4 @@ -use super::{pure::PurenessInsights, OptimizeMir}; +use super::{inlining::InliningState, pure::PurenessInsights, OptimizeMir}; use crate::{ error::CompilerError, id::IdGenerator, @@ -16,6 +16,7 @@ pub struct Context<'a> { pub visible: &'a mut VisibleExpressions, pub id_generator: &'a mut IdGenerator, pub pureness: &'a mut PurenessInsights, + pub inlining_state: InliningState, } pub struct CurrentExpression<'a> { diff --git a/compiler/frontend/src/mir_optimize/inlining.rs b/compiler/frontend/src/mir_optimize/inlining.rs index 29a1e9bd6..683fe40dd 100644 --- a/compiler/frontend/src/mir_optimize/inlining.rs +++ b/compiler/frontend/src/mir_optimize/inlining.rs @@ -44,6 +44,7 @@ use crate::{ mir::{Expression, Id}, }; use rustc_hash::FxHashMap; +use std::{collections::hash_map::Entry, num::NonZeroUsize}; const NAME: &str = "Inlining"; @@ -113,6 +114,16 @@ pub fn inline_calls_with_constant_arguments( } } +#[derive(Clone, Debug, Default)] +pub struct InliningState { + recursive_inlining_counts: FxHashMap, +} +impl InliningState { + /// To avoid infinite recursion, we limit the number of times a function can + /// be inlined into itself in a single module. + const MAX_RECURSION_INLINING_COUNT_IN_MODULE: usize = 1; +} + impl Context<'_> { fn inline_call(&mut self, expression: &mut CurrentExpression) { let Expression::Call { @@ -126,7 +137,22 @@ impl Context<'_> { }; if arguments.contains(function) { // Callee is used as an argument → recursion - return; + match self + .inlining_state + .recursive_inlining_counts + .entry(*function) + { + Entry::Occupied(mut entry) => { + let count = entry.get_mut(); + if count.get() >= InliningState::MAX_RECURSION_INLINING_COUNT_IN_MODULE { + return; + } + *count = count.saturating_add(1); + } + Entry::Vacant(entry) => { + entry.insert(NonZeroUsize::new(1).unwrap()); + } + } } let Expression::Function { diff --git a/compiler/frontend/src/mir_optimize/mod.rs b/compiler/frontend/src/mir_optimize/mod.rs index 518ac0aa5..a3b194d81 100644 --- a/compiler/frontend/src/mir_optimize/mod.rs +++ b/compiler/frontend/src/mir_optimize/mod.rs @@ -44,6 +44,7 @@ use self::{ current_expression::{Context, CurrentExpression}, + inlining::InliningState, log::OptimizationLogger, pure::PurenessInsights, }; @@ -155,6 +156,7 @@ impl Mir { visible: &mut VisibleExpressions::none_visible(), id_generator: &mut self.id_generator, pureness, + inlining_state: InliningState::default(), }; context.optimize_body(&mut self.body); if cfg!(debug_assertions) { diff --git a/packages/Core/result.candy b/packages/Core/result.candy index 396a8ca0b..a6ff3050b 100644 --- a/packages/Core/result.candy +++ b/packages/Core/result.candy @@ -77,8 +77,7 @@ and resultA resultB := needs (is resultB) resultA | flatMap { value -> resultB } -# TODO: find a better name -also result okSideEffect := +inspect result okSideEffect := needs (is result) needs (function.is1 okSideEffect) result % diff --git a/packages/FileSystem/file.candy b/packages/FileSystem/file.candy index e4614a757..95611458e 100644 --- a/packages/FileSystem/file.candy +++ b/packages/FileSystem/file.candy @@ -10,11 +10,11 @@ readBytes fileSystemFile path := needs (text.is path) path | fileSystemFile.open - | result.also { file -> needs (function.is0 file) } + | result.inspect { file -> needs (function.is0 file) } | result.flatMap { file -> file | fileSystemFile.readToEnd | result.map { bytes -> [file, bytes] } } - | result.also { [bytes] -> + | result.inspect { [bytes] -> needs (list.is bytes) needs (bytes | iterator.fromList | iterator.all { byte -> int.isUnsignedByte byte }) }