diff --git a/selfhost/codegen.jakt b/selfhost/codegen.jakt index cea054375..b724268f7 100644 --- a/selfhost/codegen.jakt +++ b/selfhost/codegen.jakt @@ -230,6 +230,9 @@ struct CodeGenerator { yield_method: YieldMethod? = None // Whether the latest output needs to use ErrorOr as return value. Used in .codegen_value_match. yields_erroror: bool = false + // caches expressions and statements to avoid re-traversing them to look up + // whether they have control flow inside them or not. + restricted(codegen_match, codegen_function) match_control_flow_cache: ControlFlowCache = ControlFlowCache() // `forward_error_with_try` controls whether errors are handled through the TRY() macro or through an external mechanism put by the caller. // noreturn functions may not throw, so let them crash instead. @@ -3512,7 +3515,7 @@ struct CodeGenerator { return .codegen_void_match(expr, match_cases, type_id, output) } - if not has_control_flow(match_cases) { + if not has_control_flow(match_cases, cfcache: &mut .match_control_flow_cache) { return .codegen_value_match(expr, match_cases, type_id, forward_error_with_try, output) } @@ -5353,6 +5356,7 @@ struct CodeGenerator { if function.is_comptime { return } + defer .match_control_flow_cache.clear() .codegen_function_in_namespace(function, containing_struct: None, as_method, &mut output) } @@ -5930,37 +5934,88 @@ fn count_match_cases(anon cases: &[CheckedMatchCase]) -> usize { fn has_cpp_value(anon type_id: TypeId) -> bool => type_id != void_type_id() and type_id != never_type_id() -fn has_control_flow(anon any_of: [T], include_loop_control_flow: bool = true) -> bool { + +struct ControlFlowCache { + stmts: [HashStmtByPtr:bool] = [:] + exprs: [HashExprByPtr:bool] = [:] + + fn clear(mut this) { + .stmts.clear() + .exprs.clear() + } + + fn stmt(mut this, anon stmt: CheckedStatement, anon else_fn: &fn() -> bool) -> bool { + let key = HashStmtByPtr(stmt) + if .stmts.get(key) is Some(val) { return val } + let val = else_fn() + .stmts[key] = val + return val + } + fn expr(mut this, anon expr: CheckedExpression, anon else_fn: &fn() -> bool) -> bool { + let key = HashExprByPtr(expr) + if .exprs.get(key) is Some(val) { return val } + let val = else_fn() + .exprs[key] = val + return val + } +} + +struct HashStmtByPtr implements(Equal,Hashable) { + stmt: CheckedStatement + + fn address(this) -> usize { + mut val = 0uz + unsafe { cpp { "val = size_t(stmt.ptr());" } } + return val + } + + fn equals(this, anon rhs: HashStmtByPtr) -> bool => .address() == rhs.address() + fn hash(this) -> u32 => .address().hash() +} +struct HashExprByPtr implements(Equal,Hashable) { + expr: CheckedExpression + + fn address(this) -> usize { + mut val = 0uz + unsafe { cpp { "val = size_t(expr.ptr());" } } + return val + } + + fn equals(this, anon rhs: HashExprByPtr) -> bool => .address() == rhs.address() + fn hash(this) -> u32 => .address().hash() +} + +fn has_control_flow(anon any_of: [T], cfcache: &mut ControlFlowCache, include_loop_control_flow: bool = true) -> bool { for t in any_of { - if has_control_flow(t, include_loop_control_flow) { return true } + if has_control_flow(t, cfcache, include_loop_control_flow) { return true } } return false } -fn has_control_flow(anon maybe_v: T?, include_loop_control_flow: bool = true) -> bool => maybe_v is Some and has_control_flow(maybe_v!, include_loop_control_flow) +fn has_control_flow(anon maybe_v: T?, cfcache: &mut ControlFlowCache, include_loop_control_flow: bool = true) -> bool => maybe_v is Some and has_control_flow(maybe_v!, cfcache, include_loop_control_flow) -fn has_control_flow(anon match_case: CheckedMatchCase, include_loop_control_flow: bool = true) -> bool => match match_case.body { - Block(t) | Expression(t) => has_control_flow(t, include_loop_control_flow) +fn has_control_flow(anon match_case: CheckedMatchCase, cfcache: &mut ControlFlowCache, include_loop_control_flow: bool = true) -> bool => match match_case.body { + Block(t) | Expression(t) => has_control_flow(t, cfcache, include_loop_control_flow) } -fn has_control_flow(anon block: CheckedBlock, include_loop_control_flow: bool = true) -> bool => has_control_flow(block.statements, include_loop_control_flow) +fn has_control_flow(anon block: CheckedBlock, cfcache: &mut ControlFlowCache, include_loop_control_flow: bool = true) -> bool => has_control_flow(block.statements, cfcache, include_loop_control_flow) -fn has_control_flow(anon stmt: CheckedStatement, include_loop_control_flow: bool = true) -> bool => match stmt { - Expression(expr) => has_control_flow(expr, include_loop_control_flow) +fn has_control_flow(anon stmt: CheckedStatement, cfcache: &mut ControlFlowCache, include_loop_control_flow: bool = true) -> bool => cfcache.stmt(stmt, &fn[stmt, include_loop_control_flow, &mut cfcache]() -> bool => match stmt { + Expression(expr) => has_control_flow(expr, &mut cfcache, include_loop_control_flow) // throw is not considered control flow because it uses the return value to forward errors, which is declared by the surrounding IIFE. Defer | Throw | Yield | Garbage => false - DestructuringAssignment(var_decl) => has_control_flow(var_decl, include_loop_control_flow) - VarDecl(init) => has_control_flow(init, include_loop_control_flow) - If(condition, then_block, else_statement) => has_control_flow(condition, include_loop_control_flow) or has_control_flow(then_block, include_loop_control_flow) or has_control_flow(else_statement, include_loop_control_flow) - Block(block) => has_control_flow(block, include_loop_control_flow) - Loop(block) => has_control_flow(block, include_loop_control_flow: false) - While(condition, block) => has_control_flow(condition, include_loop_control_flow) or has_control_flow(block, include_loop_control_flow: false) + DestructuringAssignment(var_decl) => has_control_flow(var_decl, &mut cfcache, include_loop_control_flow) + VarDecl(init) => has_control_flow(init, &mut cfcache, include_loop_control_flow) + If(condition, then_block, else_statement) => has_control_flow(condition, &mut cfcache, include_loop_control_flow) or has_control_flow(then_block, &mut cfcache, include_loop_control_flow) or has_control_flow(else_statement, &mut cfcache, include_loop_control_flow) + Block(block) => has_control_flow(block, &mut cfcache, include_loop_control_flow) + Loop(block) => has_control_flow(block, &mut cfcache, include_loop_control_flow: false) + While(condition, block) => has_control_flow(condition, &mut cfcache, include_loop_control_flow) or has_control_flow(block, &mut cfcache, include_loop_control_flow: false) // we want to keep `InlineCpp` as flexible as possible, so we'll assume that it could have control flow. As long as it knows that it's inside a match // and it has to forward things through EVOCF it can get the behavior it wants. Return | InlineCpp => true Break | Continue => include_loop_control_flow -} -fn has_control_flow(anon expr: CheckedExpression, include_loop_control_flow: bool = true) -> bool => match expr { +}) +fn has_control_flow(anon expr: CheckedExpression, cfcache: &mut ControlFlowCache, include_loop_control_flow: bool = true) -> bool => cfcache.expr(expr, &fn[expr, &mut cfcache, include_loop_control_flow]() -> bool => match expr { Boolean | NumericConstant | QuotedString @@ -5984,26 +6039,26 @@ fn has_control_flow(anon expr: CheckedExpression, include_loop_control_flow: boo | OptionalSome(expr) | ForcedUnwrap(expr) | Must(expr) - => has_control_flow(expr, include_loop_control_flow) + => has_control_flow(expr, &mut cfcache, include_loop_control_flow) BinaryOp(lhs, rhs) | IndexedExpression(expr: lhs, index: rhs) | IndexedDictionary(expr: lhs, index: rhs) | ComptimeIndex(expr: lhs, index: rhs) - => has_control_flow(lhs, include_loop_control_flow) or has_control_flow(rhs, include_loop_control_flow) + => has_control_flow(lhs, &mut cfcache, include_loop_control_flow) or has_control_flow(rhs, &mut cfcache, include_loop_control_flow) - JaktTuple(vals) | JaktArray(vals) | JaktSet(vals) => has_control_flow(vals, include_loop_control_flow) - Range(from, to) => has_control_flow(from, include_loop_control_flow) or has_control_flow(to, include_loop_control_flow) - JaktDictionary(vals) => has_control_flow(vals, include_loop_control_flow) + JaktTuple(vals) | JaktArray(vals) | JaktSet(vals) => has_control_flow(vals, &mut cfcache, include_loop_control_flow) + Range(from, to) => has_control_flow(from, &mut cfcache, include_loop_control_flow) or has_control_flow(to, &mut cfcache, include_loop_control_flow) + JaktDictionary(vals) => has_control_flow(vals, &mut cfcache, include_loop_control_flow) // We want to force loop control flow checks on inner matches because that means that they will use EVOCF, which use `return` to forward a possible // request to return from the Jakt function, independent of what control flow the inner IIFE actually produces. - Match(expr, match_cases) => has_control_flow(expr, include_loop_control_flow) or has_control_flow(match_cases, include_loop_control_flow: true) - Call(call) => has_control_flow(call.args, include_loop_control_flow) - MethodCall(expr, call) => has_control_flow(expr, include_loop_control_flow) or has_control_flow(call.args, include_loop_control_flow) - Block(block) => has_control_flow(block, include_loop_control_flow) - Try(expr, catch_block) => has_control_flow(expr, include_loop_control_flow) or has_control_flow(catch_block, include_loop_control_flow) - TryBlock(stmt, catch_block) => has_control_flow(stmt, include_loop_control_flow) or has_control_flow(catch_block, include_loop_control_flow) -} - -fn has_control_flow(anon dict_pair: (CheckedExpression, CheckedExpression), include_loop_control_flow: bool = true) -> bool => has_control_flow(dict_pair.0, include_loop_control_flow) or has_control_flow(dict_pair.1, include_loop_control_flow) -fn has_control_flow(anon call_arg: (String, CheckedExpression), include_loop_control_flow: bool = true) -> bool => has_control_flow(call_arg.1, include_loop_control_flow) + Match(expr, match_cases) => has_control_flow(expr, &mut cfcache, include_loop_control_flow) or has_control_flow(match_cases, &mut cfcache, include_loop_control_flow: true) + Call(call) => has_control_flow(call.args, &mut cfcache, include_loop_control_flow) + MethodCall(expr, call) => has_control_flow(expr, &mut cfcache, include_loop_control_flow) or has_control_flow(call.args, &mut cfcache, include_loop_control_flow) + Block(block) => has_control_flow(block, &mut cfcache, include_loop_control_flow) + Try(expr, catch_block) => has_control_flow(expr, &mut cfcache, include_loop_control_flow) or has_control_flow(catch_block, &mut cfcache, include_loop_control_flow) + TryBlock(stmt, catch_block) => has_control_flow(stmt, &mut cfcache, include_loop_control_flow) or has_control_flow(catch_block, &mut cfcache, include_loop_control_flow) +}) + +fn has_control_flow(anon dict_pair: (CheckedExpression, CheckedExpression), cfcache: &mut ControlFlowCache, include_loop_control_flow: bool = true) -> bool => has_control_flow(dict_pair.0, &mut cfcache, include_loop_control_flow) or has_control_flow(dict_pair.1, &mut cfcache, include_loop_control_flow) +fn has_control_flow(anon call_arg: (String, CheckedExpression), cfcache: &mut ControlFlowCache, include_loop_control_flow: bool = true) -> bool => has_control_flow(call_arg.1, cfcache, include_loop_control_flow)