LibJS: Consolidate TDZ check emission into Generator helper

Move the duplicated ThrowIfTDZ emission logic from three places in
ASTCodegen.cpp into a single Generator::emit_tdz_check_if_needed()
helper. This handles both argument TDZ (which requires a Mov to
empty first) and lexically-declared variable TDZ uniformly.

This avoids emitting some unnecessary ThrowIfTDZ instructions.
This commit is contained in:
Andreas Kling 2026-02-17 17:42:14 +01:00 committed by Andreas Kling
parent 9923745d34
commit 47e552e8fd
5 changed files with 36 additions and 36 deletions

View file

@ -555,17 +555,8 @@ Optional<ScopedOperand> Identifier::generate_bytecode(Bytecode::Generator& gener
Bytecode::Generator::SourceLocationScope scope(generator, *this);
if (is_local()) {
auto local_index = this->local_index();
auto local = generator.local(local_index);
if (!generator.is_local_initialized(local_index)) {
if (local_index.is_argument()) {
// Arguments are initialized to undefined by default, so here we need to replace it with the empty value to
// trigger the TDZ check.
generator.emit<Bytecode::Op::Mov>(local, generator.add_constant(js_special_empty_value()));
}
generator.emit<Bytecode::Op::ThrowIfTDZ>(local);
}
return local;
generator.emit_tdz_check_if_needed(*this);
return generator.local(local_index());
}
if (is_global()) {
@ -731,13 +722,8 @@ Optional<ScopedOperand> AssignmentExpression::generate_bytecode(Bytecode::Genera
// e. Perform ? PutValue(lref, rval).
if (is<Identifier>(*lhs)) {
auto& identifier = static_cast<Identifier const&>(*lhs);
if (identifier.is_local()) {
auto is_initialized = generator.is_local_initialized(identifier.local_index());
auto is_lexically_declared = generator.is_local_lexically_declared(identifier.local_index());
if (is_lexically_declared && !is_initialized) {
generator.emit<Bytecode::Op::ThrowIfTDZ>(generator.local(identifier.local_index()));
}
}
if (identifier.is_local())
generator.emit_tdz_check_if_needed(identifier);
generator.emit_set_variable(identifier, rval);
} else if (is<MemberExpression>(*lhs)) {
auto& expression = static_cast<MemberExpression const&>(*lhs);
@ -1933,11 +1919,8 @@ Optional<ScopedOperand> CallExpression::generate_bytecode(Bytecode::Generator& g
call_type = Bytecode::Op::CallType::DirectEval;
}
if (identifier.is_local()) {
auto local = generator.local(identifier.local_index());
if (!generator.is_local_initialized(local.operand().index())) {
generator.emit<Bytecode::Op::ThrowIfTDZ>(local);
}
original_callee = local;
generator.emit_tdz_check_if_needed(identifier);
original_callee = generator.local(identifier.local_index());
} else if (identifier.is_global()) {
original_callee = m_callee->generate_bytecode(generator).value();
} else {

View file

@ -1556,6 +1556,24 @@ bool Generator::is_local_lexically_declared(Identifier::Local const& local) cons
return m_local_variables[local.index].declaration_kind == LocalVariable::DeclarationKind::LetOrConst;
}
void Generator::emit_tdz_check_if_needed(Identifier const& identifier)
{
VERIFY(identifier.is_local());
auto local_index = identifier.local_index();
bool needs_tdz_check = local_index.is_argument()
? !is_local_initialized(local_index)
: is_local_lexically_declared(local_index) && !is_local_initialized(local_index);
if (needs_tdz_check) {
auto operand = local(local_index);
if (local_index.is_argument()) {
// Arguments are initialized to undefined by default, so here we need to replace it
// with the empty value to trigger the TDZ check.
emit<Bytecode::Op::Mov>(operand, add_constant(js_special_empty_value()));
}
emit<Bytecode::Op::ThrowIfTDZ>(operand);
}
}
ScopedOperand Generator::get_this(Optional<ScopedOperand> preferred_dst)
{
if (m_current_basic_block->has_resolved_this())

View file

@ -59,6 +59,8 @@ public:
[[nodiscard]] bool is_local_initialized(Identifier::Local const&) const;
[[nodiscard]] bool is_local_lexically_declared(Identifier::Local const& local) const;
void emit_tdz_check_if_needed(Identifier const&);
class SourceLocationScope {
public:
SourceLocationScope(Generator&, ASTNode const& node);

View file

@ -42,9 +42,8 @@ JS bytecode executable "forLetClosure"
JS bytecode executable ""
[ 0] 0: GetLexicalEnvironment dst:reg4
[ 8] ThrowIfTDZ src:arg0
[ 10] Call dst:reg5, callee:arg0, this_value:Undefined, f
[ 30] Return value:reg5
[ 8] Call dst:reg5, callee:arg0, this_value:Undefined, f
[ 28] Return value:reg5
JS bytecode executable ""
[ 0] 0: GetLexicalEnvironment dst:reg4

View file

@ -8,13 +8,11 @@ JS bytecode executable "isect"
[ 0] 0: GetLexicalEnvironment dst:reg4
[ 8] Mov dst:i~0, src:Undefined
[ 18] Mov dst:i~0, src:Int32(0)
[ 28] Jump target:@48
[ 30] 1: ThrowIfTDZ src:i~0
[ 38] PostfixIncrement dst:reg5, src:i~0
[ 48] 2: ThrowIfTDZ src:i~0
[ 50] JumpLessThan lhs:i~0, rhs:Int32(3), true_target:@30, false_target:@68
[ 68] 3: Mov dst:i~0, src:Int32(0)
[ 78] Jump target:@90
[ 80] 4: PostfixIncrement dst:reg5, src:i~0
[ 90] 5: JumpLessThan lhs:i~0, rhs:Int32(3), true_target:@80, false_target:@a8
[ a8] 6: End value:Undefined
[ 28] Jump target:@40
[ 30] 1: PostfixIncrement dst:reg5, src:i~0
[ 40] 2: JumpLessThan lhs:i~0, rhs:Int32(3), true_target:@30, false_target:@58
[ 58] 3: Mov dst:i~0, src:Int32(0)
[ 68] Jump target:@80
[ 70] 4: PostfixIncrement dst:reg5, src:i~0
[ 80] 5: JumpLessThan lhs:i~0, rhs:Int32(3), true_target:@70, false_target:@98
[ 98] 6: End value:Undefined