From ca9cef6ca2e811e531e2d2205096091d447280c9 Mon Sep 17 00:00:00 2001 From: aurpine <12262539+aurpine@users.noreply.github.com> Date: Fri, 26 Jun 2026 03:49:39 -0400 Subject: [PATCH] GDScript: IF statement: Skip temporary assignment with logical binary operators --- modules/gdscript/gdscript_byte_codegen.cpp | 57 ++++++++++ modules/gdscript/gdscript_byte_codegen.h | 17 +++ modules/gdscript/gdscript_codegen.h | 9 ++ modules/gdscript/gdscript_compiler.cpp | 118 +++++++++++++++++++-- modules/gdscript/gdscript_compiler.h | 1 + 5 files changed, 195 insertions(+), 7 deletions(-) diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 1b5da0eac115..39f855b98a84 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -415,6 +415,63 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { return function; } +void GDScriptByteCodeGenerator::start_logic_op_cond_jump_success_buffer() { + logic_op_cond_jump_success_restore_point.push_back(logic_op_cond_jump_success.size()); +} + +void GDScriptByteCodeGenerator::start_logic_op_cond_jump_failure_buffer() { + logic_op_cond_jump_failure_restore_point.push_back(logic_op_cond_jump_failure.size()); +} + +void GDScriptByteCodeGenerator::flush_logic_op_cond_jump_success() { + int target = logic_op_cond_jump_success_restore_point.back()->get(); + + while (logic_op_cond_jump_success.size() > target) { + patch_jump(logic_op_cond_jump_success.back()->get()); + logic_op_cond_jump_success.pop_back(); + } + logic_op_cond_jump_success_restore_point.pop_back(); +} + +void GDScriptByteCodeGenerator::flush_logic_op_cond_jump_failure() { + int target = logic_op_cond_jump_failure_restore_point.back()->get(); + + while (logic_op_cond_jump_failure.size() > target) { + patch_jump(logic_op_cond_jump_failure.back()->get()); + logic_op_cond_jump_failure.pop_back(); + } + logic_op_cond_jump_failure_restore_point.pop_back(); +} + +void GDScriptByteCodeGenerator::write_logic_op_cond_jump_success(const Address &p_condition) { + if (p_condition.mode != Address::NIL) { + append_opcode(GDScriptFunction::OPCODE_JUMP_IF); + append(p_condition); + } else { + append_opcode(GDScriptFunction::OPCODE_JUMP); + } + logic_op_cond_jump_success.push_back(opcodes.size()); + append(0); +} + +void GDScriptByteCodeGenerator::write_logic_op_cond_jump_failure(const Address &p_condition) { + append_opcode(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(p_condition); + logic_op_cond_jump_failure.push_back(opcodes.size()); + append(0); +} + +void GDScriptByteCodeGenerator::write_custom_else() { + append_opcode(GDScriptFunction::OPCODE_JUMP); + custom_if_jmp_addrs.push_back(opcodes.size()); + append(0); +} + +void GDScriptByteCodeGenerator::write_custom_endif() { + patch_jump(custom_if_jmp_addrs.back()->get()); + custom_if_jmp_addrs.pop_back(); +} + #ifdef DEBUG_ENABLED void GDScriptByteCodeGenerator::set_signature(const String &p_signature) { function->profile.signature = p_signature; diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 3abd22589f83..fbf8eea0d63a 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -156,6 +156,14 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { List logic_op_jump_pos1; List logic_op_jump_pos2; + // Used to patch jumps for conditional statements with `and` and `or` operators with short-circuit. + // These jump directly to the conditional success/fail branches. + List logic_op_cond_jump_success; + List logic_op_cond_jump_failure; + List logic_op_cond_jump_success_restore_point; + List logic_op_cond_jump_failure_restore_point; + List custom_if_jmp_addrs; + List
ternary_result; List ternary_jump_fail_pos; List ternary_jump_skip_pos; @@ -476,6 +484,15 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Variant p_rpc_config, const GDScriptDataType &p_return_type) override; virtual GDScriptFunction *write_end() override; + virtual void start_logic_op_cond_jump_success_buffer() override; + virtual void start_logic_op_cond_jump_failure_buffer() override; + virtual void flush_logic_op_cond_jump_success() override; + virtual void flush_logic_op_cond_jump_failure() override; + virtual void write_logic_op_cond_jump_success(const Address &p_condition) override; + virtual void write_logic_op_cond_jump_failure(const Address &p_condition) override; + virtual void write_custom_else() override; + virtual void write_custom_endif() override; + #ifdef DEBUG_ENABLED virtual void set_signature(const String &p_signature) override; #endif diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 84e4551eac13..f427f31bfbaf 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -84,6 +84,15 @@ class GDScriptCodeGenerator { virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Variant p_rpc_config, const GDScriptDataType &p_return_type) = 0; virtual GDScriptFunction *write_end() = 0; + virtual void start_logic_op_cond_jump_success_buffer() = 0; + virtual void start_logic_op_cond_jump_failure_buffer() = 0; + virtual void flush_logic_op_cond_jump_success() = 0; + virtual void flush_logic_op_cond_jump_failure() = 0; + virtual void write_logic_op_cond_jump_success(const Address &p_condition) = 0; + virtual void write_logic_op_cond_jump_failure(const Address &p_condition) = 0; + virtual void write_custom_else() = 0; + virtual void write_custom_endif() = 0; + #ifdef DEBUG_ENABLED virtual void set_signature(const String &p_signature) = 0; #endif diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index af195d2684d4..ccc3f390a26d 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1433,6 +1433,94 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } +// This function is an optimized version of `_parse_expression` when the result will be used as a jump condition. +// It optimizes the expansion of logical operators to jump directly to where they need to go instead of setting a boolean value. +// It currently only supports `OP_LOGIC_AND` and `OP_LOGIC_OR`. All other expression types will defer to `_parse_expression`. +// +// Use `start_logic_op_cond_jump_success_buffer` and `start_logic_op_cond_jump_failure_buffer` before calling this. And then call +// `flush_logic_op_cond_jump_success` and `flush_logic_op_cond_jump_failure` to patch all the jump locations. +// +// When this function returns a valid Address, the result of the expression is stored there. Otherwise, success either jump or fall +// through to the next opcode and all failures take jumps. +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression_jump(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression) { + if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) { + return codegen.add_constant(p_expression->reduced_value); + } + + GDScriptCodeGenerator *gen = codegen.generator; + + if (p_expression->type == GDScriptParser::Node::BINARY_OPERATOR) { + const GDScriptParser::BinaryOpNode *binary = static_cast(p_expression); + + switch (binary->operation) { + case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { + // Short circuit on failure. + gen->start_logic_op_cond_jump_success_buffer(); + GDScriptCodeGenerator::Address left_addr = _parse_expression_jump(codegen, r_error, binary->left_operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + // Failure jumps to else branch, success falls through to next expression. + if (left_addr.mode != GDScriptCodeGenerator::Address::NIL) { + gen->write_logic_op_cond_jump_failure(left_addr); + if (left_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + // Check the right expression after left success, so patch all jumps. + gen->flush_logic_op_cond_jump_success(); + + GDScriptCodeGenerator::Address right_addr = _parse_expression_jump(codegen, r_error, binary->right_operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + // Failure jumps to else branch, success falls through. + if (right_addr.mode != GDScriptCodeGenerator::Address::NIL) { + gen->write_logic_op_cond_jump_failure(right_addr); + if (right_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + // No result stored, all handled through jumps. + return GDScriptCodeGenerator::Address(); + } break; + case GDScriptParser::BinaryOpNode::OP_LOGIC_OR: { + // Short circuit on success. + gen->start_logic_op_cond_jump_failure_buffer(); + GDScriptCodeGenerator::Address left_addr = _parse_expression_jump(codegen, r_error, binary->left_operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + // Failure falls through. Success jumps directly, so we do not patch it. + gen->write_logic_op_cond_jump_success(left_addr); + if (left_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + // Check the right expression after left failure, so patch all jumps. + gen->flush_logic_op_cond_jump_failure(); + + GDScriptCodeGenerator::Address right_addr = _parse_expression_jump(codegen, r_error, binary->right_operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + // Failure jumps to else branch, success falls through. + if (right_addr.mode != GDScriptCodeGenerator::Address::NIL) { + gen->write_logic_op_cond_jump_failure(right_addr); + if (right_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + // No result stored, all handled through jumps. + return GDScriptCodeGenerator::Address(); + } break; + default: + break; + } + } + // Unhandled case. + return _parse_expression(codegen, r_error, p_expression); +} + GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested) { switch (p_pattern->pattern_type) { case GDScriptParser::PatternNode::PT_LITERAL: { @@ -2008,32 +2096,48 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast(s); - GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, if_n->condition); + + gen->start_logic_op_cond_jump_success_buffer(); + gen->start_logic_op_cond_jump_failure_buffer(); + + GDScriptCodeGenerator::Address condition = _parse_expression_jump(codegen, err, if_n->condition); if (err) { return err; } - gen->write_if(condition); + if (condition.mode != GDScriptCodeGenerator::Address::NIL) { + // If condition is false, go to else block (or end if not exists). + // This jump will be patched later. + gen->write_logic_op_cond_jump_failure(condition); - if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - codegen.generator->pop_temporary(); + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } } + // Our success branch starts here. Patch all success jumps. + gen->flush_logic_op_cond_jump_success(); + err = _parse_block(codegen, if_n->true_block); if (err) { return err; } if (if_n->false_block) { - gen->write_else(); + // Success branch ends, jump to end. + gen->write_custom_else(); + // Our failure branch starts here. Patch all failure jumps. + gen->flush_logic_op_cond_jump_failure(); err = _parse_block(codegen, if_n->false_block); if (err) { return err; } + gen->write_custom_endif(); + } else { + // Patch all failure jumps to after the statement. + gen->flush_logic_op_cond_jump_failure(); } - - gen->write_endif(); } break; case GDScriptParser::Node::FOR: { const GDScriptParser::ForNode *for_n = static_cast(s); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index ce832f249d0a..56fc4d523fb1 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -150,6 +150,7 @@ class GDScriptCompiler { GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true); GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false); + GDScriptCodeGenerator::Address _parse_expression_jump(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression); GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); List _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); void _clear_block_locals(CodeGen &codegen, const List &p_locals);