Skip to content

Commit

Permalink
codegen: Cache control flow results to avoid duplication
Browse files Browse the repository at this point in the history
When a match A is inside another match B, we will already have calculated
the information we need for A by the time we reach it.
  • Loading branch information
cg-jl committed Oct 13, 2024
1 parent 5cb9fb8 commit f652f78
Showing 1 changed file with 87 additions and 32 deletions.
119 changes: 87 additions & 32 deletions selfhost/codegen.jakt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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<T>(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<HashStmtByPtr>,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<HashExprByPtr>,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<T>(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<T>(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<T>(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
Expand All @@ -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)

0 comments on commit f652f78

Please sign in to comment.