Преглед на файлове

GDScript: Add faster call instructions for native methods

George Marques преди 4 години
родител
ревизия
d8b22097f2

+ 78 - 7
modules/gdscript/gdscript_byte_codegen.cpp

@@ -242,11 +242,24 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
 		function->_indexed_getters_ptr = nullptr;
 	}
 
+	if (method_bind_map.size()) {
+		function->methods.resize(method_bind_map.size());
+		function->_methods_ptr = function->methods.ptrw();
+		function->_methods_count = method_bind_map.size();
+		for (const Map<MethodBind *, int>::Element *E = method_bind_map.front(); E; E = E->next()) {
+			function->methods.write[E->get()] = E->key();
+		}
+	} else {
+		function->_methods_ptr = nullptr;
+		function->_methods_count = 0;
+	}
+
 	if (debug_stack) {
 		function->stack_debug = stack_debug;
 	}
 	function->_stack_size = stack_max;
 	function->_instruction_args_size = instr_args_max;
+	function->_ptrcall_args_size = ptrcall_max;
 
 	ended = true;
 	return function;
@@ -634,26 +647,84 @@ void GDScriptByteCodeGenerator::write_call_builtin(const Address &p_target, GDSc
 	append(p_function);
 }
 
-void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) {
-	append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size());
+void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
+	append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size());
 	for (int i = 0; i < p_arguments.size(); i++) {
 		append(p_arguments[i]);
 	}
 	append(p_base);
 	append(p_target);
 	append(p_arguments.size());
-	append(p_method->get_name());
-}
+	append(p_method);
+}
+
+void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
+#define CASE_TYPE(m_type)                                                               \
+	case Variant::m_type:                                                               \
+		append(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \
+		break
+
+	bool is_ptrcall = true;
+
+	if (p_method->has_return()) {
+		MethodInfo info;
+		ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info);
+		switch (info.return_val.type) {
+			CASE_TYPE(BOOL);
+			CASE_TYPE(INT);
+			CASE_TYPE(FLOAT);
+			CASE_TYPE(STRING);
+			CASE_TYPE(VECTOR2);
+			CASE_TYPE(VECTOR2I);
+			CASE_TYPE(RECT2);
+			CASE_TYPE(RECT2I);
+			CASE_TYPE(VECTOR3);
+			CASE_TYPE(VECTOR3I);
+			CASE_TYPE(TRANSFORM2D);
+			CASE_TYPE(PLANE);
+			CASE_TYPE(AABB);
+			CASE_TYPE(BASIS);
+			CASE_TYPE(TRANSFORM);
+			CASE_TYPE(COLOR);
+			CASE_TYPE(STRING_NAME);
+			CASE_TYPE(NODE_PATH);
+			CASE_TYPE(RID);
+			CASE_TYPE(QUAT);
+			CASE_TYPE(OBJECT);
+			CASE_TYPE(CALLABLE);
+			CASE_TYPE(SIGNAL);
+			CASE_TYPE(DICTIONARY);
+			CASE_TYPE(ARRAY);
+			CASE_TYPE(PACKED_BYTE_ARRAY);
+			CASE_TYPE(PACKED_INT32_ARRAY);
+			CASE_TYPE(PACKED_INT64_ARRAY);
+			CASE_TYPE(PACKED_FLOAT32_ARRAY);
+			CASE_TYPE(PACKED_FLOAT64_ARRAY);
+			CASE_TYPE(PACKED_STRING_ARRAY);
+			CASE_TYPE(PACKED_VECTOR2_ARRAY);
+			CASE_TYPE(PACKED_VECTOR3_ARRAY);
+			CASE_TYPE(PACKED_COLOR_ARRAY);
+			default:
+				append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size());
+				is_ptrcall = false;
+				break;
+		}
+	} else {
+		append(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size());
+	}
 
-void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) {
-	append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size());
 	for (int i = 0; i < p_arguments.size(); i++) {
 		append(p_arguments[i]);
 	}
 	append(p_base);
 	append(p_target);
 	append(p_arguments.size());
-	append(p_method->get_name());
+	append(p_method);
+	if (is_ptrcall) {
+		alloc_ptrcall(p_arguments.size());
+	}
+
+#undef CASE_TYPE
 }
 
 void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) {

+ 22 - 2
modules/gdscript/gdscript_byte_codegen.h

@@ -54,6 +54,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 	int current_line = 0;
 	int stack_max = 0;
 	int instr_args_max = 0;
+	int ptrcall_max = 0;
 
 	HashMap<Variant, int, VariantHasher, VariantComparator> constant_map;
 	Map<StringName, int> name_map;
@@ -67,6 +68,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 	Map<Variant::ValidatedKeyedGetter, int> keyed_getters_map;
 	Map<Variant::ValidatedIndexedSetter, int> indexed_setters_map;
 	Map<Variant::ValidatedIndexedGetter, int> indexed_getters_map;
+	Map<MethodBind *, int> method_bind_map;
 
 	List<int> if_jmp_addrs; // List since this can be nested.
 	List<int> for_jmp_addrs;
@@ -199,6 +201,15 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 		return pos;
 	}
 
+	int get_method_bind_pos(MethodBind *p_method) {
+		if (method_bind_map.has(p_method)) {
+			return method_bind_map[p_method];
+		}
+		int pos = method_bind_map.size();
+		method_bind_map[p_method] = pos;
+		return pos;
+	}
+
 	void alloc_stack(int p_level) {
 		if (p_level >= stack_max)
 			stack_max = p_level + 1;
@@ -210,6 +221,11 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 		return top;
 	}
 
+	void alloc_ptrcall(int p_params) {
+		if (p_params >= ptrcall_max)
+			ptrcall_max = p_params;
+	}
+
 	int address_of(const Address &p_address) {
 		switch (p_address.mode) {
 			case Address::SELF:
@@ -282,6 +298,10 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 		opcodes.push_back(get_indexed_getter_pos(p_indexed_getter));
 	}
 
+	void append(MethodBind *p_method) {
+		opcodes.push_back(get_method_bind_pos(p_method));
+	}
+
 	void patch_jump(int p_address) {
 		opcodes.write[p_address] = opcodes.size();
 	}
@@ -337,8 +357,8 @@ public:
 	virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
 	virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
 	virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) override;
-	virtual void write_call_method_bind(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) override;
-	virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) override;
+	virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
+	virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
 	virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
 	virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
 	virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override;

+ 2 - 2
modules/gdscript/gdscript_codegen.h

@@ -126,8 +126,8 @@ public:
 	virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
 	virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
 	virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) = 0;
-	virtual void write_call_method_bind(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
-	virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
+	virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
+	virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
 	virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
 	virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
 	virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0;

+ 77 - 2
modules/gdscript/gdscript_compiler.cpp

@@ -158,6 +158,48 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
 	return result;
 }
 
+static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataType &p_arg_type) {
+	if (!p_arg_type.has_type) {
+		return false;
+	}
+	if (p_par_type.type == Variant::NIL) {
+		return false;
+	}
+	if (p_par_type.type == Variant::OBJECT) {
+		if (p_arg_type.kind == GDScriptDataType::BUILTIN) {
+			return false;
+		}
+		StringName class_name;
+		if (p_arg_type.kind == GDScriptDataType::NATIVE) {
+			class_name = p_arg_type.native_type;
+		} else {
+			class_name = p_arg_type.native_type == StringName() ? p_arg_type.script_type->get_instance_base_type() : p_arg_type.native_type;
+		}
+		return p_par_type.class_name == class_name || ClassDB::is_parent_class(class_name, p_par_type.class_name);
+	} else {
+		if (p_arg_type.kind != GDScriptDataType::BUILTIN) {
+			return false;
+		}
+		return p_par_type.type == p_arg_type.builtin_type;
+	}
+}
+
+static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) {
+	if (p_method->get_argument_count() != p_arguments.size()) {
+		// ptrcall won't work with default arguments.
+		return false;
+	}
+	MethodInfo info;
+	ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info);
+	for (int i = 0; i < p_arguments.size(); i++) {
+		const PropertyInfo &prop = info.arguments[i];
+		if (!_is_exact_type(prop, p_arguments[i].type)) {
+			return false;
+		}
+	}
+	return true;
+}
+
 GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) {
 	if (p_expression->is_constant) {
 		return codegen.add_constant(p_expression->reduced_value);
@@ -430,7 +472,20 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 				} else {
 					if (callee->type == GDScriptParser::Node::IDENTIFIER) {
 						// Self function call.
-						if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
+						if (ClassDB::has_method(codegen.script->native->get_name(), call->function_name)) {
+							// Native method, use faster path.
+							GDScriptCodeGenerator::Address self;
+							self.mode = GDScriptCodeGenerator::Address::SELF;
+							MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name);
+
+							if (_have_exact_arguments(method, arguments)) {
+								// Exact arguments, use ptrcall.
+								gen->write_call_ptrcall(result, self, method, arguments);
+							} else {
+								// Not exact arguments, but still can use method bind call.
+								gen->write_call_method_bind(result, self, method, arguments);
+							}
+						} else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
 							GDScriptCodeGenerator::Address self;
 							self.mode = GDScriptCodeGenerator::Address::CLASS;
 							gen->write_call(result, self, call->function_name, arguments);
@@ -447,6 +502,26 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 							}
 							if (within_await) {
 								gen->write_call_async(result, base, call->function_name, arguments);
+							} else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) {
+								// Native method, use faster path.
+								StringName class_name;
+								if (base.type.kind == GDScriptDataType::NATIVE) {
+									class_name = base.type.native_type;
+								} else {
+									class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type;
+								}
+								if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
+									MethodBind *method = ClassDB::get_method(class_name, call->function_name);
+									if (_have_exact_arguments(method, arguments)) {
+										// Exact arguments, use ptrcall.
+										gen->write_call_ptrcall(result, base, method, arguments);
+									} else {
+										// Not exact arguments, but still can use method bind call.
+										gen->write_call_method_bind(result, base, method, arguments);
+									}
+								} else {
+									gen->write_call(result, base, call->function_name, arguments);
+								}
 							} else {
 								gen->write_call(result, base, call->function_name, arguments);
 							}
@@ -493,7 +568,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 			GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype()));
 
 			MethodBind *get_node_method = ClassDB::get_method("Node", "get_node");
-			gen->write_call_method_bind(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
+			gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
 
 			return result;
 		} break;

+ 106 - 0
modules/gdscript/gdscript_disassembler.cpp

@@ -466,6 +466,112 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 
 				incr = 5 + argc;
 			} break;
+			case OPCODE_CALL_METHOD_BIND:
+			case OPCODE_CALL_METHOD_BIND_RET: {
+				bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET;
+
+				if (ret) {
+					text += "call-method_bind-ret ";
+				} else {
+					text += "call-method_bind ";
+				}
+
+				MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
+
+				int argc = _code_ptr[ip + 1 + instr_var_args];
+				if (ret) {
+					text += DADDR(2 + argc) + " = ";
+				}
+
+				text += DADDR(1 + argc) + ".";
+				text += method->get_name();
+				text += "(";
+
+				for (int i = 0; i < argc; i++) {
+					if (i > 0)
+						text += ", ";
+					text += DADDR(1 + i);
+				}
+				text += ")";
+
+				incr = 5 + argc;
+			} break;
+			case OPCODE_CALL_PTRCALL_NO_RETURN: {
+				text += "call-ptrcall (no return) ";
+
+				MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
+
+				int argc = _code_ptr[ip + 1 + instr_var_args];
+
+				text += DADDR(1 + argc) + ".";
+				text += method->get_name();
+				text += "(";
+
+				for (int i = 0; i < argc; i++) {
+					if (i > 0)
+						text += ", ";
+					text += DADDR(1 + i);
+				}
+				text += ")";
+
+				incr = 5 + argc;
+			} break;
+
+#define DISASSEMBLE_PTRCALL(m_type)                                            \
+	case OPCODE_CALL_PTRCALL_##m_type: {                                       \
+		text += "call-ptrcall (return ";                                       \
+		text += #m_type;                                                       \
+		text += ") ";                                                          \
+		MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; \
+		int argc = _code_ptr[ip + 1 + instr_var_args];                         \
+		text += DADDR(2 + argc) + " = ";                                       \
+		text += DADDR(1 + argc) + ".";                                         \
+		text += method->get_name();                                            \
+		text += "(";                                                           \
+		for (int i = 0; i < argc; i++) {                                       \
+			if (i > 0)                                                         \
+				text += ", ";                                                  \
+			text += DADDR(1 + i);                                              \
+		}                                                                      \
+		text += ")";                                                           \
+		incr = 5 + argc;                                                       \
+	} break
+
+				DISASSEMBLE_PTRCALL(BOOL);
+				DISASSEMBLE_PTRCALL(INT);
+				DISASSEMBLE_PTRCALL(FLOAT);
+				DISASSEMBLE_PTRCALL(STRING);
+				DISASSEMBLE_PTRCALL(VECTOR2);
+				DISASSEMBLE_PTRCALL(VECTOR2I);
+				DISASSEMBLE_PTRCALL(RECT2);
+				DISASSEMBLE_PTRCALL(RECT2I);
+				DISASSEMBLE_PTRCALL(VECTOR3);
+				DISASSEMBLE_PTRCALL(VECTOR3I);
+				DISASSEMBLE_PTRCALL(TRANSFORM2D);
+				DISASSEMBLE_PTRCALL(PLANE);
+				DISASSEMBLE_PTRCALL(AABB);
+				DISASSEMBLE_PTRCALL(BASIS);
+				DISASSEMBLE_PTRCALL(TRANSFORM);
+				DISASSEMBLE_PTRCALL(COLOR);
+				DISASSEMBLE_PTRCALL(STRING_NAME);
+				DISASSEMBLE_PTRCALL(NODE_PATH);
+				DISASSEMBLE_PTRCALL(RID);
+				DISASSEMBLE_PTRCALL(QUAT);
+				DISASSEMBLE_PTRCALL(OBJECT);
+				DISASSEMBLE_PTRCALL(CALLABLE);
+				DISASSEMBLE_PTRCALL(SIGNAL);
+				DISASSEMBLE_PTRCALL(DICTIONARY);
+				DISASSEMBLE_PTRCALL(ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_BYTE_ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_INT32_ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_INT64_ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_FLOAT32_ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_FLOAT64_ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_STRING_ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_VECTOR2_ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_VECTOR3_ARRAY);
+				DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY);
+
 			case OPCODE_CALL_BUILT_IN: {
 				text += "call-built-in ";
 

+ 43 - 0
modules/gdscript/gdscript_function.h

@@ -191,6 +191,44 @@ public:
 		OPCODE_CALL_ASYNC,
 		OPCODE_CALL_BUILT_IN,
 		OPCODE_CALL_SELF_BASE,
+		OPCODE_CALL_METHOD_BIND,
+		OPCODE_CALL_METHOD_BIND_RET,
+		// ptrcall have one instruction per return type.
+		OPCODE_CALL_PTRCALL_NO_RETURN,
+		OPCODE_CALL_PTRCALL_BOOL,
+		OPCODE_CALL_PTRCALL_INT,
+		OPCODE_CALL_PTRCALL_FLOAT,
+		OPCODE_CALL_PTRCALL_STRING,
+		OPCODE_CALL_PTRCALL_VECTOR2,
+		OPCODE_CALL_PTRCALL_VECTOR2I,
+		OPCODE_CALL_PTRCALL_RECT2,
+		OPCODE_CALL_PTRCALL_RECT2I,
+		OPCODE_CALL_PTRCALL_VECTOR3,
+		OPCODE_CALL_PTRCALL_VECTOR3I,
+		OPCODE_CALL_PTRCALL_TRANSFORM2D,
+		OPCODE_CALL_PTRCALL_PLANE,
+		OPCODE_CALL_PTRCALL_QUAT,
+		OPCODE_CALL_PTRCALL_AABB,
+		OPCODE_CALL_PTRCALL_BASIS,
+		OPCODE_CALL_PTRCALL_TRANSFORM,
+		OPCODE_CALL_PTRCALL_COLOR,
+		OPCODE_CALL_PTRCALL_STRING_NAME,
+		OPCODE_CALL_PTRCALL_NODE_PATH,
+		OPCODE_CALL_PTRCALL_RID,
+		OPCODE_CALL_PTRCALL_OBJECT,
+		OPCODE_CALL_PTRCALL_CALLABLE,
+		OPCODE_CALL_PTRCALL_SIGNAL,
+		OPCODE_CALL_PTRCALL_DICTIONARY,
+		OPCODE_CALL_PTRCALL_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY,
+		OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY,
 		OPCODE_AWAIT,
 		OPCODE_AWAIT_RESUME,
 		OPCODE_JUMP,
@@ -262,11 +300,15 @@ private:
 	const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr;
 	int _indexed_getters_count = 0;
 	const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr;
+	int _methods_count = 0;
+	MethodBind **_methods_ptr = nullptr;
 	const int *_code_ptr = nullptr;
 	int _code_size = 0;
 	int _argument_count = 0;
 	int _stack_size = 0;
 	int _instruction_args_size = 0;
+	int _ptrcall_args_size = 0;
+
 	int _initial_line = 0;
 	bool _static = false;
 	MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
@@ -284,6 +326,7 @@ private:
 	Vector<Variant::ValidatedKeyedGetter> keyed_getters;
 	Vector<Variant::ValidatedIndexedSetter> indexed_setters;
 	Vector<Variant::ValidatedIndexedGetter> indexed_getters;
+	Vector<MethodBind *> methods;
 	Vector<int> code;
 	Vector<GDScriptDataType> argument_types;
 	GDScriptDataType return_type;

+ 406 - 49
modules/gdscript/gdscript_vm.cpp

@@ -185,55 +185,92 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
 }
 
 #if defined(__GNUC__)
-#define OPCODES_TABLE                         \
-	static const void *switch_table_ops[] = { \
-		&&OPCODE_OPERATOR,                    \
-		&&OPCODE_OPERATOR_VALIDATED,          \
-		&&OPCODE_EXTENDS_TEST,                \
-		&&OPCODE_IS_BUILTIN,                  \
-		&&OPCODE_SET_KEYED,                   \
-		&&OPCODE_SET_KEYED_VALIDATED,         \
-		&&OPCODE_SET_INDEXED_VALIDATED,       \
-		&&OPCODE_GET_KEYED,                   \
-		&&OPCODE_GET_KEYED_VALIDATED,         \
-		&&OPCODE_GET_INDEXED_VALIDATED,       \
-		&&OPCODE_SET_NAMED,                   \
-		&&OPCODE_SET_NAMED_VALIDATED,         \
-		&&OPCODE_GET_NAMED,                   \
-		&&OPCODE_GET_NAMED_VALIDATED,         \
-		&&OPCODE_SET_MEMBER,                  \
-		&&OPCODE_GET_MEMBER,                  \
-		&&OPCODE_ASSIGN,                      \
-		&&OPCODE_ASSIGN_TRUE,                 \
-		&&OPCODE_ASSIGN_FALSE,                \
-		&&OPCODE_ASSIGN_TYPED_BUILTIN,        \
-		&&OPCODE_ASSIGN_TYPED_NATIVE,         \
-		&&OPCODE_ASSIGN_TYPED_SCRIPT,         \
-		&&OPCODE_CAST_TO_BUILTIN,             \
-		&&OPCODE_CAST_TO_NATIVE,              \
-		&&OPCODE_CAST_TO_SCRIPT,              \
-		&&OPCODE_CONSTRUCT,                   \
-		&&OPCODE_CONSTRUCT_ARRAY,             \
-		&&OPCODE_CONSTRUCT_DICTIONARY,        \
-		&&OPCODE_CALL,                        \
-		&&OPCODE_CALL_RETURN,                 \
-		&&OPCODE_CALL_ASYNC,                  \
-		&&OPCODE_CALL_BUILT_IN,               \
-		&&OPCODE_CALL_SELF_BASE,              \
-		&&OPCODE_AWAIT,                       \
-		&&OPCODE_AWAIT_RESUME,                \
-		&&OPCODE_JUMP,                        \
-		&&OPCODE_JUMP_IF,                     \
-		&&OPCODE_JUMP_IF_NOT,                 \
-		&&OPCODE_JUMP_TO_DEF_ARGUMENT,        \
-		&&OPCODE_RETURN,                      \
-		&&OPCODE_ITERATE_BEGIN,               \
-		&&OPCODE_ITERATE,                     \
-		&&OPCODE_ASSERT,                      \
-		&&OPCODE_BREAKPOINT,                  \
-		&&OPCODE_LINE,                        \
-		&&OPCODE_END                          \
-	};                                        \
+#define OPCODES_TABLE                               \
+	static const void *switch_table_ops[] = {       \
+		&&OPCODE_OPERATOR,                          \
+		&&OPCODE_OPERATOR_VALIDATED,                \
+		&&OPCODE_EXTENDS_TEST,                      \
+		&&OPCODE_IS_BUILTIN,                        \
+		&&OPCODE_SET_KEYED,                         \
+		&&OPCODE_SET_KEYED_VALIDATED,               \
+		&&OPCODE_SET_INDEXED_VALIDATED,             \
+		&&OPCODE_GET_KEYED,                         \
+		&&OPCODE_GET_KEYED_VALIDATED,               \
+		&&OPCODE_GET_INDEXED_VALIDATED,             \
+		&&OPCODE_SET_NAMED,                         \
+		&&OPCODE_SET_NAMED_VALIDATED,               \
+		&&OPCODE_GET_NAMED,                         \
+		&&OPCODE_GET_NAMED_VALIDATED,               \
+		&&OPCODE_SET_MEMBER,                        \
+		&&OPCODE_GET_MEMBER,                        \
+		&&OPCODE_ASSIGN,                            \
+		&&OPCODE_ASSIGN_TRUE,                       \
+		&&OPCODE_ASSIGN_FALSE,                      \
+		&&OPCODE_ASSIGN_TYPED_BUILTIN,              \
+		&&OPCODE_ASSIGN_TYPED_NATIVE,               \
+		&&OPCODE_ASSIGN_TYPED_SCRIPT,               \
+		&&OPCODE_CAST_TO_BUILTIN,                   \
+		&&OPCODE_CAST_TO_NATIVE,                    \
+		&&OPCODE_CAST_TO_SCRIPT,                    \
+		&&OPCODE_CONSTRUCT,                         \
+		&&OPCODE_CONSTRUCT_ARRAY,                   \
+		&&OPCODE_CONSTRUCT_DICTIONARY,              \
+		&&OPCODE_CALL,                              \
+		&&OPCODE_CALL_RETURN,                       \
+		&&OPCODE_CALL_ASYNC,                        \
+		&&OPCODE_CALL_BUILT_IN,                     \
+		&&OPCODE_CALL_SELF_BASE,                    \
+		&&OPCODE_CALL_METHOD_BIND,                  \
+		&&OPCODE_CALL_METHOD_BIND_RET,              \
+		&&OPCODE_CALL_PTRCALL_NO_RETURN,            \
+		&&OPCODE_CALL_PTRCALL_BOOL,                 \
+		&&OPCODE_CALL_PTRCALL_INT,                  \
+		&&OPCODE_CALL_PTRCALL_FLOAT,                \
+		&&OPCODE_CALL_PTRCALL_STRING,               \
+		&&OPCODE_CALL_PTRCALL_VECTOR2,              \
+		&&OPCODE_CALL_PTRCALL_VECTOR2I,             \
+		&&OPCODE_CALL_PTRCALL_RECT2,                \
+		&&OPCODE_CALL_PTRCALL_RECT2I,               \
+		&&OPCODE_CALL_PTRCALL_VECTOR3,              \
+		&&OPCODE_CALL_PTRCALL_VECTOR3I,             \
+		&&OPCODE_CALL_PTRCALL_TRANSFORM2D,          \
+		&&OPCODE_CALL_PTRCALL_PLANE,                \
+		&&OPCODE_CALL_PTRCALL_QUAT,                 \
+		&&OPCODE_CALL_PTRCALL_AABB,                 \
+		&&OPCODE_CALL_PTRCALL_BASIS,                \
+		&&OPCODE_CALL_PTRCALL_TRANSFORM,            \
+		&&OPCODE_CALL_PTRCALL_COLOR,                \
+		&&OPCODE_CALL_PTRCALL_STRING_NAME,          \
+		&&OPCODE_CALL_PTRCALL_NODE_PATH,            \
+		&&OPCODE_CALL_PTRCALL_RID,                  \
+		&&OPCODE_CALL_PTRCALL_OBJECT,               \
+		&&OPCODE_CALL_PTRCALL_CALLABLE,             \
+		&&OPCODE_CALL_PTRCALL_SIGNAL,               \
+		&&OPCODE_CALL_PTRCALL_DICTIONARY,           \
+		&&OPCODE_CALL_PTRCALL_ARRAY,                \
+		&&OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY,    \
+		&&OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY,   \
+		&&OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY,   \
+		&&OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, \
+		&&OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, \
+		&&OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY,  \
+		&&OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, \
+		&&OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, \
+		&&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY,   \
+		&&OPCODE_AWAIT,                             \
+		&&OPCODE_AWAIT_RESUME,                      \
+		&&OPCODE_JUMP,                              \
+		&&OPCODE_JUMP_IF,                           \
+		&&OPCODE_JUMP_IF_NOT,                       \
+		&&OPCODE_JUMP_TO_DEF_ARGUMENT,              \
+		&&OPCODE_RETURN,                            \
+		&&OPCODE_ITERATE_BEGIN,                     \
+		&&OPCODE_ITERATE,                           \
+		&&OPCODE_ASSERT,                            \
+		&&OPCODE_BREAKPOINT,                        \
+		&&OPCODE_LINE,                              \
+		&&OPCODE_END                                \
+	};                                              \
 	static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum.");
 
 #define OPCODE(m_op) \
@@ -260,6 +297,41 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
 #define OPCODE_OUT break
 #endif
 
+// Helpers for VariantInternal methods in macros.
+#define OP_GET_BOOL get_bool
+#define OP_GET_INT get_int
+#define OP_GET_FLOAT get_float
+#define OP_GET_VECTOR2 get_vector2
+#define OP_GET_VECTOR2I get_vector2i
+#define OP_GET_VECTOR3 get_vector3
+#define OP_GET_VECTOR3I get_vector3i
+#define OP_GET_RECT2 get_rect2
+#define OP_GET_RECT2I get_rect2i
+#define OP_GET_QUAT get_quat
+#define OP_GET_COLOR get_color
+#define OP_GET_STRING get_string
+#define OP_GET_STRING_NAME get_string_name
+#define OP_GET_NODE_PATH get_node_path
+#define OP_GET_CALLABLE get_callable
+#define OP_GET_SIGNAL get_signal
+#define OP_GET_ARRAY get_array
+#define OP_GET_DICTIONARY get_dictionary
+#define OP_GET_PACKED_BYTE_ARRAY get_byte_array
+#define OP_GET_PACKED_INT32_ARRAY get_int32_array
+#define OP_GET_PACKED_INT64_ARRAY get_int64_array
+#define OP_GET_PACKED_FLOAT32_ARRAY get_float32_array
+#define OP_GET_PACKED_FLOAT64_ARRAY get_float64_array
+#define OP_GET_PACKED_STRING_ARRAY get_string_array
+#define OP_GET_PACKED_VECTOR2_ARRAY get_vector2_array
+#define OP_GET_PACKED_VECTOR3_ARRAY get_vector3_array
+#define OP_GET_PACKED_COLOR_ARRAY get_color_array
+#define OP_GET_TRANSFORM get_transform
+#define OP_GET_TRANSFORM2D get_transform2d
+#define OP_GET_PLANE get_plane
+#define OP_GET_AABB get_aabb
+#define OP_GET_BASIS get_basis
+#define OP_GET_RID get_rid
+
 Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) {
 	OPCODES_TABLE;
 
@@ -274,6 +346,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 	Variant retvalue;
 	Variant *stack = nullptr;
 	Variant **instruction_args;
+	const void **call_args_ptr = nullptr;
 	int defarg = 0;
 
 #ifdef DEBUG_ENABLED
@@ -371,6 +444,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 			script = _script;
 		}
 	}
+	if (_ptrcall_args_size) {
+		call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *));
+	} else {
+		call_args_ptr = nullptr;
+	}
 
 	static_ref = script;
 
@@ -1296,6 +1374,285 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 			}
 			DISPATCH_OPCODE;
 
+			OPCODE(OPCODE_CALL_METHOD_BIND)
+			OPCODE(OPCODE_CALL_METHOD_BIND_RET) {
+				CHECK_SPACE(3 + instr_arg_count);
+				bool call_ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET;
+
+				ip += instr_arg_count;
+
+				int argc = _code_ptr[ip + 1];
+				GD_ERR_BREAK(argc < 0);
+				GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
+				MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
+
+				GET_INSTRUCTION_ARG(base, argc);
+
+#ifdef DEBUG_ENABLED
+				bool freed = false;
+				Object *base_obj = base->get_validated_object_with_check(freed);
+				if (freed) {
+					err_text = "Trying to call a function on a previously freed instance.";
+					OPCODE_BREAK;
+				} else if (!base_obj) {
+					err_text = "Trying to call a function on a null value.";
+					OPCODE_BREAK;
+				}
+#else
+				Object *base_obj = base->operator Object *();
+#endif
+				Variant **argptrs = instruction_args;
+
+#ifdef DEBUG_ENABLED
+				uint64_t call_time = 0;
+
+				if (GDScriptLanguage::get_singleton()->profiling) {
+					call_time = OS::get_singleton()->get_ticks_usec();
+				}
+#endif
+
+				Callable::CallError err;
+				if (call_ret) {
+					GET_INSTRUCTION_ARG(ret, argc + 1);
+					*ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
+				} else {
+					method->call(base_obj, (const Variant **)argptrs, argc, err);
+				}
+
+#ifdef DEBUG_ENABLED
+				if (GDScriptLanguage::get_singleton()->profiling) {
+					function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+				}
+
+				if (err.error != Callable::CallError::CALL_OK) {
+					String methodstr = method->get_name();
+					String basestr = _get_var_type(base);
+
+					if (methodstr == "call") {
+						if (argc >= 1) {
+							methodstr = String(*argptrs[0]) + " (via call)";
+							if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
+								err.argument += 1;
+							}
+						}
+					} else if (methodstr == "free") {
+						if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
+							if (base->is_ref()) {
+								err_text = "Attempted to free a reference.";
+								OPCODE_BREAK;
+							} else if (base->get_type() == Variant::OBJECT) {
+								err_text = "Attempted to free a locked object (calling or emitting).";
+								OPCODE_BREAK;
+							}
+						}
+					}
+					err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
+					OPCODE_BREAK;
+				}
+#endif
+				ip += 3;
+			}
+			DISPATCH_OPCODE;
+
+#ifdef DEBUG_ENABLED
+#define OPCODE_CALL_PTR(m_type)                                                      \
+	OPCODE(OPCODE_CALL_PTRCALL_##m_type) {                                           \
+		CHECK_SPACE(3 + instr_arg_count);                                            \
+		ip += instr_arg_count;                                                       \
+		int argc = _code_ptr[ip + 1];                                                \
+		GD_ERR_BREAK(argc < 0);                                                      \
+		GET_INSTRUCTION_ARG(base, argc);                                             \
+		GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);  \
+		MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];                        \
+		bool freed = false;                                                          \
+		Object *base_obj = base->get_validated_object_with_check(freed);             \
+		if (freed) {                                                                 \
+			err_text = "Trying to call a function on a previously freed instance.";  \
+			OPCODE_BREAK;                                                            \
+		} else if (!base_obj) {                                                      \
+			err_text = "Trying to call a function on a null value.";                 \
+			OPCODE_BREAK;                                                            \
+		}                                                                            \
+		const void **argptrs = call_args_ptr;                                        \
+		for (int i = 0; i < argc; i++) {                                             \
+			GET_INSTRUCTION_ARG(v, i);                                               \
+			argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);    \
+		}                                                                            \
+		uint64_t call_time = 0;                                                      \
+		if (GDScriptLanguage::get_singleton()->profiling) {                          \
+			call_time = OS::get_singleton()->get_ticks_usec();                       \
+		}                                                                            \
+		GET_INSTRUCTION_ARG(ret, argc + 1);                                          \
+		VariantInternal::initialize(ret, Variant::m_type);                           \
+		void *ret_opaque = VariantInternal::OP_GET_##m_type(ret);                    \
+		method->ptrcall(base_obj, argptrs, ret_opaque);                              \
+		if (GDScriptLanguage::get_singleton()->profiling) {                          \
+			function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; \
+		}                                                                            \
+		ip += 3;                                                                     \
+	}                                                                                \
+	DISPATCH_OPCODE
+#else
+#define OPCODE_CALL_PTR(m_type)                                                   \
+	OPCODE(OPCODE_CALL_PTRCALL_##m_type) {                                        \
+		CHECK_SPACE(3 + instr_arg_count);                                         \
+		int argc = _code_ptr[ip + 1];                                             \
+		GET_INSTRUCTION_ARG(base, argc);                                          \
+		MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];                     \
+		Object *base_obj = *VariantInternal::get_object(base);                    \
+		const void **argptrs = call_args_ptr;                                     \
+		for (int i = 0; i < argc; i++) {                                          \
+			GET_INSTRUCTION_ARG(v, i);                                            \
+			argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
+		}                                                                         \
+		GET_INSTRUCTION_ARG(ret, argc + 1);                                       \
+		VariantInternal::initialize(ret, Variant::m_type);                        \
+		void *ret_opaque = VariantInternal::OP_GET_##m_type(ret);                 \
+		method->ptrcall(base_obj, argptrs, ret_opaque);                           \
+		ip += 3;                                                                  \
+	}                                                                             \
+	DISPATCH_OPCODE
+#endif
+
+			OPCODE_CALL_PTR(BOOL);
+			OPCODE_CALL_PTR(INT);
+			OPCODE_CALL_PTR(FLOAT);
+			OPCODE_CALL_PTR(STRING);
+			OPCODE_CALL_PTR(VECTOR2);
+			OPCODE_CALL_PTR(VECTOR2I);
+			OPCODE_CALL_PTR(RECT2);
+			OPCODE_CALL_PTR(RECT2I);
+			OPCODE_CALL_PTR(VECTOR3);
+			OPCODE_CALL_PTR(VECTOR3I);
+			OPCODE_CALL_PTR(TRANSFORM2D);
+			OPCODE_CALL_PTR(PLANE);
+			OPCODE_CALL_PTR(QUAT);
+			OPCODE_CALL_PTR(AABB);
+			OPCODE_CALL_PTR(BASIS);
+			OPCODE_CALL_PTR(TRANSFORM);
+			OPCODE_CALL_PTR(COLOR);
+			OPCODE_CALL_PTR(STRING_NAME);
+			OPCODE_CALL_PTR(NODE_PATH);
+			OPCODE_CALL_PTR(RID);
+			OPCODE_CALL_PTR(CALLABLE);
+			OPCODE_CALL_PTR(SIGNAL);
+			OPCODE_CALL_PTR(DICTIONARY);
+			OPCODE_CALL_PTR(ARRAY);
+			OPCODE_CALL_PTR(PACKED_BYTE_ARRAY);
+			OPCODE_CALL_PTR(PACKED_INT32_ARRAY);
+			OPCODE_CALL_PTR(PACKED_INT64_ARRAY);
+			OPCODE_CALL_PTR(PACKED_FLOAT32_ARRAY);
+			OPCODE_CALL_PTR(PACKED_FLOAT64_ARRAY);
+			OPCODE_CALL_PTR(PACKED_STRING_ARRAY);
+			OPCODE_CALL_PTR(PACKED_VECTOR2_ARRAY);
+			OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY);
+			OPCODE_CALL_PTR(PACKED_COLOR_ARRAY);
+			OPCODE(OPCODE_CALL_PTRCALL_OBJECT) {
+				CHECK_SPACE(3 + instr_arg_count);
+
+				ip += instr_arg_count;
+
+				int argc = _code_ptr[ip + 1];
+				GD_ERR_BREAK(argc < 0);
+
+				GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
+				MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
+
+				GET_INSTRUCTION_ARG(base, argc);
+#ifdef DEBUG_ENABLED
+				bool freed = false;
+				Object *base_obj = base->get_validated_object_with_check(freed);
+				if (freed) {
+					err_text = "Trying to call a function on a previously freed instance.";
+					OPCODE_BREAK;
+				} else if (!base_obj) {
+					err_text = "Trying to call a function on a null value.";
+					OPCODE_BREAK;
+				}
+#else
+				Object *base_obj = *VariantInternal::get_object(base);
+#endif
+
+				const void **argptrs = call_args_ptr;
+
+				for (int i = 0; i < argc; i++) {
+					GET_INSTRUCTION_ARG(v, i);
+					argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
+				}
+#ifdef DEBUG_ENABLED
+				uint64_t call_time = 0;
+
+				if (GDScriptLanguage::get_singleton()->profiling) {
+					call_time = OS::get_singleton()->get_ticks_usec();
+				}
+#endif
+
+				GET_INSTRUCTION_ARG(ret, argc + 1);
+				VariantInternal::initialize(ret, Variant::OBJECT);
+				Object **ret_opaque = VariantInternal::get_object(ret);
+				method->ptrcall(base_obj, argptrs, ret_opaque);
+				VariantInternal::set_object(ret, *ret_opaque);
+
+#ifdef DEBUG_ENABLED
+				if (GDScriptLanguage::get_singleton()->profiling) {
+					function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+				}
+#endif
+				ip += 3;
+			}
+			DISPATCH_OPCODE;
+			OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) {
+				CHECK_SPACE(3 + instr_arg_count);
+
+				ip += instr_arg_count;
+
+				int argc = _code_ptr[ip + 1];
+				GD_ERR_BREAK(argc < 0);
+
+				GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count);
+				MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
+
+				GET_INSTRUCTION_ARG(base, argc);
+#ifdef DEBUG_ENABLED
+				bool freed = false;
+				Object *base_obj = base->get_validated_object_with_check(freed);
+				if (freed) {
+					err_text = "Trying to call a function on a previously freed instance.";
+					OPCODE_BREAK;
+				} else if (!base_obj) {
+					err_text = "Trying to call a function on a null value.";
+					OPCODE_BREAK;
+				}
+#else
+				Object *base_obj = *VariantInternal::get_object(base);
+#endif
+				const void **argptrs = call_args_ptr;
+
+				for (int i = 0; i < argc; i++) {
+					GET_INSTRUCTION_ARG(v, i);
+					argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
+				}
+#ifdef DEBUG_ENABLED
+				uint64_t call_time = 0;
+
+				if (GDScriptLanguage::get_singleton()->profiling) {
+					call_time = OS::get_singleton()->get_ticks_usec();
+				}
+#endif
+
+				GET_INSTRUCTION_ARG(ret, argc + 1);
+				VariantInternal::initialize(ret, Variant::NIL);
+				method->ptrcall(base_obj, argptrs, nullptr);
+
+#ifdef DEBUG_ENABLED
+				if (GDScriptLanguage::get_singleton()->profiling) {
+					function_call_time += OS::get_singleton()->get_ticks_usec() - call_time;
+				}
+#endif
+				ip += 3;
+			}
+			DISPATCH_OPCODE;
+
 			OPCODE(OPCODE_CALL_BUILT_IN) {
 				CHECK_SPACE(3 + instr_arg_count);