///|
fn Program::compact_branch(self : Program, id : Int32) -> Unit {
let branch = self.nodes[id]
guard branch.kind is If else { return }
let consequent = self.nodes[self.single_statement(branch.b)]
if branch.c < 0 && consequent.kind is If && consequent.c < 0 {
let condition = self.slot_logical(LogicalAnd, branch.a, consequent.a)
self.nodes[id] = Node::new(If, condition, consequent.b, -1, 0)
return
}
guard branch.c >= 0 else { return }
let alternative = self.nodes[self.single_statement(branch.c)]
guard consequent.kind == alternative.kind &&
consequent.kind is Return &&
consequent.a >= 0 &&
alternative.a >= 0 else {
return
}
let value = self.compact_choice(branch.a, consequent.a, alternative.a)
self.nodes[id] = Node::new(consequent.kind, value, 0, 0, 0)
}
///|
/// The only false path exits this exact loop. Keep the original true-branch
/// block so its lexical declarations still receive a fresh iteration scope.
fn Program::compact_loop_guard(self : Program, id : Int32) -> Unit {
let iteration = self.nodes[id]
guard iteration.kind is For &&
iteration.a < 0 &&
iteration.b < 0 &&
iteration.c < 0 else {
return
}
let branch = self.nodes[self.single_statement(iteration.d)]
guard branch.kind is If && branch.c >= 0 else { return }
let alternative = self.nodes[self.single_statement(branch.c)]
guard alternative.kind is Break && alternative.a < 0 else { return }
// A bare Annex B function belongs to its if clause. Making it a direct
// loop body would change both its grammar and declaration semantics.
let consequent = self.nodes[branch.b]
match consequent.kind {
FunctionDeclaration | ClassDeclaration => return
VariableDeclaration if consequent.a != DECLARATION_VAR => return
_ => ()
}
self.nodes[id] = { ..iteration, b: branch.a, d: branch.b, }
}
///|
fn Program::compact_statement_list(
self : Program,
offset : Int32,
count : Int32,
bindings : ValueBindings,
) -> Int32 {
// Work backwards so a chain of adjacent single-use values can collapse
// without rescanning the statement list to a fixed point.
for index = count - 2; index >= 0; index = index - 1 {
let statement = self.edges[offset + index]
let next = self.edges[offset + index + 1]
let branch = self.nodes[statement]
let following = self.nodes[next]
if branch.kind is Block &&
self.sink_result_slot(statement, next, offset, index, bindings) {
continue
}
if branch.kind is ExpressionStatement &&
following.kind is (Return | Throw | ExpressionStatement) &&
following.a >= 0 &&
!(self.nodes[branch.a].kind is StringLiteral) &&
!(following.kind is ExpressionStatement &&
self.nodes[following.a].kind is StringLiteral) {
let value = self.sequence_pair(branch.a, following.a)
self.nodes[next] = { ..following, a: value, }
self.nodes[statement] = Node::new(Elided, 0, 0, 0, 0)
self.edges[offset + index] = next
self.edges[offset + index + 1] = statement
continue
}
if branch.kind is If && self.compact_guarded_unit_return(statement, next) {
continue
}
if branch.kind is If &&
branch.c >= 0 &&
following.kind is Return &&
following.a >= 0 &&
(
self.fold_returning_result_slot(
statement, next, offset, index, bindings,
) ||
self.compact_value_branch_return(statement, next)
) {
self.nodes[statement] = Node::new(Elided, 0, 0, 0, 0)
self.edges[offset + index] = next
self.edges[offset + index + 1] = statement
continue
}
if branch.kind is If && branch.c < 0 && self.nodes[next].kind is Return {
let consequent = self.nodes[self.single_statement(branch.b)]
let alternative = self.nodes[next]
if consequent.kind is Return && consequent.a >= 0 && alternative.a >= 0 {
let value = self.compact_choice(branch.a, consequent.a, alternative.a)
self.nodes[next] = Node::new(Return, value, 0, 0, 0)
self.nodes[statement] = Node::new(Elided, 0, 0, 0, 0)
self.edges[offset + index] = next
self.edges[offset + index + 1] = statement
continue
}
}
if self.inline_adjacent_value(statement, next, bindings) {
self.nodes[statement] = Node::new(Elided, 0, 0, 0, 0)
self.edges[offset + index] = next
self.edges[offset + index + 1] = statement
}
}
self.live_statements(offset, count)
}
///|
fn Program::live_statements(
self : Program,
offset : Int32,
count : Int32,
) -> Int32 {
let mut source_directives = true
let mut output_directives = true
for read = 0, kept = 0 {
guard read < count else { break kept }
let statement = self.edges[offset + read]
let node = self.nodes[statement]
if node.kind is Elided {
source_directives = false
continue read + 1, kept
}
let is_directive = node.kind is ExpressionStatement &&
self.nodes[node.a].kind is StringLiteral
if !is_directive {
source_directives = false
output_directives = false
} else if !source_directives && output_directives {
let value = self.node(Parenthesized, node.a, 0, 0, 0)
self.nodes[statement] = { ..node, a: value, }
output_directives = false
}
self.edges[offset + kept] = statement
continue read + 1, kept + 1
}
}
///|
fn Program::sequence_pair(
self : Program,
first : Int32,
second : Int32,
) -> Int32 {
let offset = self.edges.length()
self.edges.push(first)
self.edges.push(second)
self.node(Sequence, offset, 2, 0, 0)
}
///|
fn Program::inline_adjacent_value(
self : Program,
declaration_id : Int32,
consumer_id : Int32,
bindings : ValueBindings,
) -> Bool {
let declaration = self.nodes[declaration_id]
guard declaration.kind is VariableDeclaration &&
declaration.a != DECLARATION_VAR &&
declaration.c == 1 else {
return false
}
let binding = self.nodes[self.edges[declaration.b]]
let pattern = self.nodes[binding.a]
guard pattern.kind is PatternIdentifier && binding.b >= 0 else {
return false
}
let symbol = self.references[pattern.a].symbol
let entry = self.symbols[symbol]
guard entry.scope > 0 &&
entry.flags == 0 &&
entry.link < 0 &&
entry.use_count == 2 &&
!self.preserved_names.contains(entry.name) &&
!bindings.has_writes[symbol] else {
return false
}
// Function-name changes are allowed. Class inferred names remain preserved.
let initializer = self.nodes[self.unwrapped_expression(binding.b)]
guard !(initializer.kind is ClassExpression) else { return false }
let expression = self.first_statement_expression(consumer_id)
let movement = ValueMovement::new(bindings, symbol, declaration_id, binding.b)
let reference = self.leading_value_read(expression, movement)
guard reference >= 0 else { return false }
// An expression replaces a primary identifier. Keep its grouping in every
// surrounding operator context, including member access and exponentiation.
self.nodes[reference] = if movement.detach_receiver &&
initializer.kind is (Identifier | Member | Index) {
let zero = self.node(ZeroLiteral, 0, 0, 0, 0)
let value = self.sequence_pair(zero, binding.b)
Node::new(Parenthesized, value, 0, 0, 0)
} else if reference == expression &&
self.nodes[consumer_id].kind is ExpressionStatement &&
initializer.kind is StringLiteral {
Node::new(Parenthesized, binding.b, 0, 0, 0)
} else if self.is_primary_value(binding.b) ||
(
reference == expression &&
self.nodes[consumer_id].kind is (Return | Throw | If | Switch)
) {
self.nodes[binding.b]
} else {
Node::new(Parenthesized, binding.b, 0, 0, 0)
}
self.symbols[symbol] = { ..entry, flags: entry.flags | SYMBOL_ELIDED, }
true
}
///|
fn Program::is_primary_value(self : Program, id : Int32) -> Bool {
let node = self.nodes[id]
match node.kind {
Identifier
| StringLiteral
| ArrayLiteral
| RegexLiteral
| TemplateNoSubstitution
| Parenthesized => true
Member | Index | Call => !self.has_optional_chain(id)
New => node.b >= 0
_ => false
}
}
///|
fn Program::has_optional_chain(self : Program, id : Int32) -> Bool {
guard id >= 0 else { return false }
let node = self.nodes[id]
match node.kind {
Member | Index | Call =>
node.d == CHAIN_OPTIONAL || self.has_optional_chain(node.a)
_ => false
}
}
///|
fn Program::first_statement_expression(self : Program, id : Int32) -> Int32 {
let node = self.nodes[id]
match node.kind {
Return | Throw | ExpressionStatement | If | Switch => node.a
VariableDeclaration if node.c > 0 => self.nodes[self.edges[node.b]].b
_ => -1
}
}
///|
/// Find a read evaluated before any other potentially observable operation.
/// Do not cross calls, coercions, short-circuit arms, scopes or loop boundaries.
fn Program::leading_value_read(
self : Program,
id : Int32,
movement : ValueMovement,
) -> Int32 {
guard id >= 0 else { return -1 }
let node = self.nodes[id]
match node.kind {
Identifier =>
if self.references[node.a].symbol == movement.symbol {
id
} else {
-1
}
Parenthesized | EvaluatedValue | Member | Conditional =>
self.leading_value_read(node.a, movement)
Index => {
let object = self.leading_value_read(node.a, movement)
guard object < 0 else { return object }
guard node.d != CHAIN_OPTIONAL && movement.can_cross_read(node.a) else {
return -1
}
self.leading_value_read(node.b, movement)
}
Binary => {
let left = self.leading_value_read(node.b, movement)
guard left < 0 else { return left }
guard movement.can_cross_read(node.b) else { return -1 }
self.leading_value_read(node.c, movement)
}
Logical => self.leading_value_read(node.b, movement)
Unary if !(self.operators[node.a] is (TypeOf | Delete)) =>
self.leading_value_read(node.b, movement)
Assignment if self.operators[node.a] is Operator::Assignment &&
self.stable_assignment_target(node.b, movement) =>
self.leading_value_read(node.c, movement)
Call | New => {
let callee = self.unwrapped_expression(node.a)
let target = self.nodes[callee]
if target.kind is Identifier &&
self.references[target.a].symbol == movement.symbol {
movement.detach_receiver = node.kind is Call
return callee
}
if target.kind is (Member | Index | EvaluatedValue) {
let reference = self.leading_value_read(callee, movement)
guard reference < 0 else { return reference }
}
guard node.b >= 0 && movement.can_cross_read(node.a) else { return -1 }
self.leading_list_read(node.b, node.c, movement)
}
ArrayLiteral | Sequence => self.leading_list_read(node.a, node.b, movement)
ObjectLiteral => {
for index in 0.. -1
}
}
///|
fn Program::stable_assignment_target(
self : Program,
id : Int32,
movement : ValueMovement,
) -> Bool {
let node = self.nodes[self.unwrapped_expression(id)]
match node.kind {
Identifier => self.references[node.a].symbol >= 0
Member if node.a >= 0 => movement.can_cross_read(node.a)
_ => false
}
}
///|
fn Program::leading_list_read(
self : Program,
offset : Int32,
count : Int32,
movement : ValueMovement,
) -> Int32 {
for index in 0..= 0 else { continue }
let reference = self.leading_value_read(expression, movement)
guard reference < 0 else { return reference }
guard movement.can_cross_read(expression) else { return -1 }
}
-1
}