Browse Source

Merge pull request #79990 from vnen/gdscript-assume-op-types

GDScript: Optimize operators by assuming the types
Yuri Sizov 2 years ago
parent
commit
f6e02dc437

+ 13 - 1
modules/gdscript/gdscript_byte_codegen.cpp

@@ -226,7 +226,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
 
 
 	if (opcodes.size()) {
 	if (opcodes.size()) {
 		function->code = opcodes;
 		function->code = opcodes;
-		function->_code_ptr = &function->code[0];
+		function->_code_ptr = &function->code.write[0];
 		function->_code_size = opcodes.size();
 		function->_code_size = opcodes.size();
 
 
 	} else {
 	} else {
@@ -577,6 +577,12 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va
 	append(Address());
 	append(Address());
 	append(p_target);
 	append(p_target);
 	append(p_operator);
 	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) {
 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_right_operand);
 	append(p_target);
 	append(p_target);
 	append(p_operator);
 	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) {
 void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) {

+ 2 - 1
modules/gdscript/gdscript_disassembler.cpp

@@ -113,6 +113,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 
 
 		switch (opcode) {
 		switch (opcode) {
 			case OPCODE_OPERATOR: {
 			case OPCODE_OPERATOR: {
+				constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*_code_ptr);
 				int operation = _code_ptr[ip + 4];
 				int operation = _code_ptr[ip + 4];
 
 
 				text += "operator ";
 				text += "operator ";
@@ -125,7 +126,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 				text += " ";
 				text += " ";
 				text += DADDR(2);
 				text += DADDR(2);
 
 
-				incr += 5;
+				incr += 7 + _pointer_size;
 			} break;
 			} break;
 			case OPCODE_OPERATOR_VALIDATED: {
 			case OPCODE_OPERATOR_VALIDATED: {
 				text += "validated operator ";
 				text += "validated operator ";

+ 1 - 1
modules/gdscript/gdscript_function.h

@@ -473,7 +473,7 @@ private:
 	MethodBind **_methods_ptr = nullptr;
 	MethodBind **_methods_ptr = nullptr;
 	int _lambdas_count = 0;
 	int _lambdas_count = 0;
 	GDScriptFunction **_lambdas_ptr = nullptr;
 	GDScriptFunction **_lambdas_ptr = nullptr;
-	const int *_code_ptr = nullptr;
+	int *_code_ptr = nullptr;
 	int _code_size = 0;
 	int _code_size = 0;
 	int _argument_count = 0;
 	int _argument_count = 0;
 	int _stack_size = 0;
 	int _stack_size = 0;

+ 59 - 15
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_SWITCH(_code_ptr[ip]) {
 			OPCODE(OPCODE_OPERATOR) {
 			OPCODE(OPCODE_OPERATOR) {
-				CHECK_SPACE(5);
+				constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*_code_ptr);
+				CHECK_SPACE(7 + _pointer_size);
 
 
 				bool valid;
 				bool valid;
 				Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4];
 				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(a, 0);
 				GET_VARIANT_PTR(b, 1);
 				GET_VARIANT_PTR(b, 1);
 				GET_VARIANT_PTR(dst, 2);
 				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());
+
+				// 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::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(op, a_type, b_type);
+
+					if (unlikely(!op_func)) {
+#ifdef DEBUG_ENABLED
+						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) + "'.";
+#endif
+						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<int>(ret_type);
+							Variant::ValidatedOperatorEvaluator *tmp = reinterpret_cast<Variant::ValidatedOperatorEvaluator *>(&_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<Variant::Type>(_code_ptr[ip + 6]);
+					Variant::ValidatedOperatorEvaluator op_func = *reinterpret_cast<Variant::ValidatedOperatorEvaluator *>(&_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
 #ifdef DEBUG_ENABLED
 
 
-				Variant ret;
-				Variant::evaluate(op, *a, *b, ret, valid);
+					Variant ret;
+					Variant::evaluate(op, *a, *b, ret, valid);
 #else
 #else
-				Variant::evaluate(op, *a, *b, *dst, valid);
+					Variant::evaluate(op, *a, *b, *dst, valid);
 #endif
 #endif
 #ifdef DEBUG_ENABLED
 #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) + "'.";
+					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;
 					}
 					}
-					OPCODE_BREAK;
-				}
-				*dst = ret;
+					*dst = ret;
 #endif
 #endif
-				ip += 5;
+				}
+				ip += 7 + _pointer_size;
 			}
 			}
 			DISPATCH_OPCODE;
 			DISPATCH_OPCODE;