Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions modules/gdscript/gdscript_byte_codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions modules/gdscript/gdscript_byte_codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
List<int> logic_op_jump_pos1;
List<int> 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<int> logic_op_cond_jump_success;
List<int> logic_op_cond_jump_failure;
List<int> logic_op_cond_jump_success_restore_point;
List<int> logic_op_cond_jump_failure_restore_point;
List<int> custom_if_jmp_addrs;

List<Address> ternary_result;
List<int> ternary_jump_fail_pos;
List<int> ternary_jump_skip_pos;
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions modules/gdscript/gdscript_codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 111 additions & 7 deletions modules/gdscript/gdscript_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const GDScriptParser::BinaryOpNode *>(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: {
Expand Down Expand Up @@ -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<const GDScriptParser::IfNode *>(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<const GDScriptParser::ForNode *>(s);
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<GDScriptCodeGenerator::Address> _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
void _clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals);
Expand Down