diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 47cd3f768b4b..6057a00f9b30 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -226,7 +226,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { if (opcodes.size()) { function->code = opcodes; - function->_code_ptr = &function->code[0]; + function->_code_ptr = &function->code.write[0]; function->_code_size = opcodes.size(); } else { @@ -577,6 +577,12 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va append(Address()); append(p_target); append(p_operator); + append(0); // Signature storage. + append(0); // Return type storage. + constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*(opcodes.ptr())); + for (int i = 0; i < _pointer_size; i++) { + append(0); // Space for function pointer. + } } void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) { @@ -610,6 +616,12 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V append(p_right_operand); append(p_target); append(p_operator); + append(0); // Signature storage. + append(0); // Return type storage. + constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*(opcodes.ptr())); + for (int i = 0; i < _pointer_size; i++) { + append(0); // Space for function pointer. + } } void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) { diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index ec1d0af32973..438ec0274063 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -113,6 +113,7 @@ void GDScriptFunction::disassemble(const Vector &p_code_lines) const { switch (opcode) { case OPCODE_OPERATOR: { + constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*_code_ptr); int operation = _code_ptr[ip + 4]; text += "operator "; @@ -125,7 +126,7 @@ void GDScriptFunction::disassemble(const Vector &p_code_lines) const { text += " "; text += DADDR(2); - incr += 5; + incr += 7 + _pointer_size; } break; case OPCODE_OPERATOR_VALIDATED: { text += "validated operator "; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index dfe66e66881d..5230773c1307 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -473,7 +473,7 @@ private: MethodBind **_methods_ptr = nullptr; int _lambdas_count = 0; GDScriptFunction **_lambdas_ptr = nullptr; - const int *_code_ptr = nullptr; + int *_code_ptr = nullptr; int _code_size = 0; int _argument_count = 0; int _stack_size = 0; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 44c4cb0fc393..1ddd54b32364 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -685,7 +685,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_SWITCH(_code_ptr[ip]) { OPCODE(OPCODE_OPERATOR) { - CHECK_SPACE(5); + constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*_code_ptr); + CHECK_SPACE(7 + _pointer_size); bool valid; Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4]; @@ -694,28 +695,71 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_VARIANT_PTR(a, 0); GET_VARIANT_PTR(b, 1); GET_VARIANT_PTR(dst, 2); + // Compute signatures (types of operands) so it can be optimized when matching. + uint32_t op_signature = _code_ptr[ip + 5]; + uint32_t actual_signature = (a->get_type() << 8) | (b->get_type()); -#ifdef DEBUG_ENABLED + // Check if this is the first run. If so, store the current signature for the optimized path. + if (unlikely(op_signature == 0)) { + static Mutex initializer_mutex; + initializer_mutex.lock(); + Variant::Type a_type = (Variant::Type)((actual_signature >> 8) & 0xFF); + Variant::Type b_type = (Variant::Type)(actual_signature & 0xFF); - Variant ret; - Variant::evaluate(op, *a, *b, ret, valid); -#else - Variant::evaluate(op, *a, *b, *dst, valid); -#endif + Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(op, a_type, b_type); + + if (unlikely(!op_func)) { #ifdef DEBUG_ENABLED - if (!valid) { - if (ret.get_type() == Variant::STRING) { - //return a string when invalid with the error - err_text = ret; - err_text += " in operator '" + Variant::get_operator_name(op) + "'."; - } else { err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'."; - } - OPCODE_BREAK; - } - *dst = ret; #endif - ip += 5; + initializer_mutex.unlock(); + OPCODE_BREAK; + } else { + Variant::Type ret_type = Variant::get_operator_return_type(op, a_type, b_type); + VariantInternal::initialize(dst, ret_type); + op_func(a, b, dst); + + // Check again in case another thread already set it. + if (_code_ptr[ip + 5] == 0) { + _code_ptr[ip + 5] = actual_signature; + _code_ptr[ip + 6] = static_cast(ret_type); + Variant::ValidatedOperatorEvaluator *tmp = reinterpret_cast(&_code_ptr[ip + 7]); + *tmp = op_func; + } + } + initializer_mutex.unlock(); + } else if (likely(op_signature == actual_signature)) { + // If the signature matches, we can use the optimized path. + Variant::Type ret_type = static_cast(_code_ptr[ip + 6]); + Variant::ValidatedOperatorEvaluator op_func = *reinterpret_cast(&_code_ptr[ip + 7]); + + // Make sure the return value has the correct type. + VariantInternal::initialize(dst, ret_type); + op_func(a, b, dst); + } else { + // If the signature doesn't match, we have to use the slow path. +#ifdef DEBUG_ENABLED + + Variant ret; + Variant::evaluate(op, *a, *b, ret, valid); +#else + Variant::evaluate(op, *a, *b, *dst, valid); +#endif +#ifdef DEBUG_ENABLED + if (!valid) { + if (ret.get_type() == Variant::STRING) { + //return a string when invalid with the error + err_text = ret; + err_text += " in operator '" + Variant::get_operator_name(op) + "'."; + } else { + err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'."; + } + OPCODE_BREAK; + } + *dst = ret; +#endif + } + ip += 7 + _pointer_size; } DISPATCH_OPCODE;