GDScript: Add faster call instructions for native methods

This commit is contained in:
George Marques 2020-11-17 10:44:52 -03:00
parent 5aeb390cd7
commit d8b22097f2
No known key found for this signature in database
GPG Key ID: 046BD46A3201E43D
7 changed files with 733 additions and 61 deletions

View File

@ -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, 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_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());
}
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) {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 ";

View File

@ -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;

View File

@ -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);