diff options
-rw-r--r-- | core/variant/variant_internal.h | 233 | ||||
-rw-r--r-- | modules/gdscript/gdscript_byte_codegen.cpp | 645 | ||||
-rw-r--r-- | modules/gdscript/gdscript_byte_codegen.h | 166 | ||||
-rw-r--r-- | modules/gdscript/gdscript_codegen.h | 5 | ||||
-rw-r--r-- | modules/gdscript/gdscript_compiler.cpp | 81 | ||||
-rw-r--r-- | modules/gdscript/gdscript_disassembler.cpp | 813 | ||||
-rw-r--r-- | modules/gdscript/gdscript_function.cpp | 2090 | ||||
-rw-r--r-- | modules/gdscript/gdscript_function.h | 183 | ||||
-rw-r--r-- | modules/gdscript/gdscript_vm.cpp | 2863 |
9 files changed, 4827 insertions, 2252 deletions
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index 3c3be44ef7..bf7e46eed7 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -38,7 +38,82 @@ class VariantInternal { public: // Set type. - _FORCE_INLINE_ static void initialize(Variant *v, Variant::Type p_type) { v->type = p_type; } + _FORCE_INLINE_ static void initialize(Variant *v, Variant::Type p_type) { + v->clear(); + v->type = p_type; + + switch (p_type) { + case Variant::AABB: + init_aabb(v); + break; + case Variant::TRANSFORM2D: + init_transform2d(v); + break; + case Variant::TRANSFORM: + init_transform(v); + break; + case Variant::STRING: + init_string(v); + break; + case Variant::STRING_NAME: + init_string_name(v); + break; + case Variant::NODE_PATH: + init_node_path(v); + break; + case Variant::CALLABLE: + init_callable(v); + break; + case Variant::SIGNAL: + init_signal(v); + break; + case Variant::DICTIONARY: + init_dictionary(v); + break; + case Variant::ARRAY: + init_array(v); + break; + case Variant::PACKED_BYTE_ARRAY: + init_byte_array(v); + break; + case Variant::PACKED_INT32_ARRAY: + init_int32_array(v); + break; + case Variant::PACKED_INT64_ARRAY: + init_int64_array(v); + break; + case Variant::PACKED_FLOAT32_ARRAY: + init_float32_array(v); + break; + case Variant::PACKED_FLOAT64_ARRAY: + init_float64_array(v); + break; + case Variant::PACKED_STRING_ARRAY: + init_string_array(v); + break; + case Variant::PACKED_VECTOR2_ARRAY: + init_vector2_array(v); + break; + case Variant::PACKED_VECTOR3_ARRAY: + init_vector3_array(v); + break; + case Variant::PACKED_COLOR_ARRAY: + init_color_array(v); + break; + default: + break; + } + } + + _FORCE_INLINE_ static void set_object(Variant *v, Object *obj) { + if (obj) { + v->_get_obj().obj = obj; + v->_get_obj().id = obj->get_instance_id(); + } else { + v->_get_obj().obj = nullptr; + v->_get_obj().id = ObjectID(); + } + } // Atomic types. _FORCE_INLINE_ static bool *get_bool(Variant *v) { return &v->_data._bool; } @@ -216,6 +291,162 @@ public: v->_get_obj().obj = nullptr; v->_get_obj().id = ObjectID(); } + + _FORCE_INLINE_ static void *get_opaque_pointer(Variant *v) { + switch (v->type) { + case Variant::NIL: + return nullptr; + case Variant::BOOL: + return get_bool(v); + case Variant::INT: + return get_int(v); + case Variant::FLOAT: + return get_float(v); + case Variant::STRING: + return get_string(v); + case Variant::VECTOR2: + return get_vector2(v); + case Variant::VECTOR2I: + return get_vector2i(v); + case Variant::VECTOR3: + return get_vector3(v); + case Variant::VECTOR3I: + return get_vector3i(v); + case Variant::RECT2: + return get_rect2(v); + case Variant::RECT2I: + return get_rect2i(v); + case Variant::TRANSFORM: + return get_transform(v); + case Variant::TRANSFORM2D: + return get_transform2d(v); + case Variant::QUAT: + return get_quat(v); + case Variant::PLANE: + return get_plane(v); + case Variant::BASIS: + return get_basis(v); + case Variant::AABB: + return get_aabb(v); + case Variant::COLOR: + return get_color(v); + case Variant::STRING_NAME: + return get_string_name(v); + case Variant::NODE_PATH: + return get_node_path(v); + case Variant::RID: + return get_rid(v); + case Variant::CALLABLE: + return get_callable(v); + case Variant::SIGNAL: + return get_signal(v); + case Variant::DICTIONARY: + return get_dictionary(v); + case Variant::ARRAY: + return get_array(v); + case Variant::PACKED_BYTE_ARRAY: + return get_byte_array(v); + case Variant::PACKED_INT32_ARRAY: + return get_int32_array(v); + case Variant::PACKED_INT64_ARRAY: + return get_int64_array(v); + case Variant::PACKED_FLOAT32_ARRAY: + return get_float32_array(v); + case Variant::PACKED_FLOAT64_ARRAY: + return get_float64_array(v); + case Variant::PACKED_STRING_ARRAY: + return get_string_array(v); + case Variant::PACKED_VECTOR2_ARRAY: + return get_vector2_array(v); + case Variant::PACKED_VECTOR3_ARRAY: + return get_vector3_array(v); + case Variant::PACKED_COLOR_ARRAY: + return get_color_array(v); + case Variant::OBJECT: + return v->_get_obj().obj; + case Variant::VARIANT_MAX: + ERR_FAIL_V(nullptr); + } + ERR_FAIL_V(nullptr); + } + + _FORCE_INLINE_ static const void *get_opaque_pointer(const Variant *v) { + switch (v->type) { + case Variant::NIL: + return nullptr; + case Variant::BOOL: + return get_bool(v); + case Variant::INT: + return get_int(v); + case Variant::FLOAT: + return get_float(v); + case Variant::STRING: + return get_string(v); + case Variant::VECTOR2: + return get_vector2(v); + case Variant::VECTOR2I: + return get_vector2i(v); + case Variant::VECTOR3: + return get_vector3(v); + case Variant::VECTOR3I: + return get_vector3i(v); + case Variant::RECT2: + return get_rect2(v); + case Variant::RECT2I: + return get_rect2i(v); + case Variant::TRANSFORM: + return get_transform(v); + case Variant::TRANSFORM2D: + return get_transform2d(v); + case Variant::QUAT: + return get_quat(v); + case Variant::PLANE: + return get_plane(v); + case Variant::BASIS: + return get_basis(v); + case Variant::AABB: + return get_aabb(v); + case Variant::COLOR: + return get_color(v); + case Variant::STRING_NAME: + return get_string_name(v); + case Variant::NODE_PATH: + return get_node_path(v); + case Variant::RID: + return get_rid(v); + case Variant::CALLABLE: + return get_callable(v); + case Variant::SIGNAL: + return get_signal(v); + case Variant::DICTIONARY: + return get_dictionary(v); + case Variant::ARRAY: + return get_array(v); + case Variant::PACKED_BYTE_ARRAY: + return get_byte_array(v); + case Variant::PACKED_INT32_ARRAY: + return get_int32_array(v); + case Variant::PACKED_INT64_ARRAY: + return get_int64_array(v); + case Variant::PACKED_FLOAT32_ARRAY: + return get_float32_array(v); + case Variant::PACKED_FLOAT64_ARRAY: + return get_float64_array(v); + case Variant::PACKED_STRING_ARRAY: + return get_string_array(v); + case Variant::PACKED_VECTOR2_ARRAY: + return get_vector2_array(v); + case Variant::PACKED_VECTOR3_ARRAY: + return get_vector3_array(v); + case Variant::PACKED_COLOR_ARRAY: + return get_color_array(v); + case Variant::OBJECT: + return v->_get_obj().obj; + case Variant::VARIANT_MAX: + ERR_FAIL_V(nullptr); + } + ERR_FAIL_V(nullptr); + } }; template <class T> diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index cc9e87b882..5f1c738207 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -111,7 +111,7 @@ void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName } GDScriptFunction *GDScriptByteCodeGenerator::write_end() { - append(GDScriptFunction::OPCODE_END); + append(GDScriptFunction::OPCODE_END, 0); if (constant_map.size()) { function->_constant_count = constant_map.size(); @@ -158,11 +158,132 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->_default_arg_ptr = nullptr; } + if (operator_func_map.size()) { + function->operator_funcs.resize(operator_func_map.size()); + function->_operator_funcs_count = function->operator_funcs.size(); + function->_operator_funcs_ptr = function->operator_funcs.ptr(); + for (const Map<Variant::ValidatedOperatorEvaluator, int>::Element *E = operator_func_map.front(); E; E = E->next()) { + function->operator_funcs.write[E->get()] = E->key(); + } + } else { + function->_operator_funcs_count = 0; + function->_operator_funcs_ptr = nullptr; + } + + if (setters_map.size()) { + function->setters.resize(setters_map.size()); + function->_setters_count = function->setters.size(); + function->_setters_ptr = function->setters.ptr(); + for (const Map<Variant::ValidatedSetter, int>::Element *E = setters_map.front(); E; E = E->next()) { + function->setters.write[E->get()] = E->key(); + } + } else { + function->_setters_count = 0; + function->_setters_ptr = nullptr; + } + + if (getters_map.size()) { + function->getters.resize(getters_map.size()); + function->_getters_count = function->getters.size(); + function->_getters_ptr = function->getters.ptr(); + for (const Map<Variant::ValidatedGetter, int>::Element *E = getters_map.front(); E; E = E->next()) { + function->getters.write[E->get()] = E->key(); + } + } else { + function->_getters_count = 0; + function->_getters_ptr = nullptr; + } + + if (keyed_setters_map.size()) { + function->keyed_setters.resize(keyed_setters_map.size()); + function->_keyed_setters_count = function->keyed_setters.size(); + function->_keyed_setters_ptr = function->keyed_setters.ptr(); + for (const Map<Variant::ValidatedKeyedSetter, int>::Element *E = keyed_setters_map.front(); E; E = E->next()) { + function->keyed_setters.write[E->get()] = E->key(); + } + } else { + function->_keyed_setters_count = 0; + function->_keyed_setters_ptr = nullptr; + } + + if (keyed_getters_map.size()) { + function->keyed_getters.resize(keyed_getters_map.size()); + function->_keyed_getters_count = function->keyed_getters.size(); + function->_keyed_getters_ptr = function->keyed_getters.ptr(); + for (const Map<Variant::ValidatedKeyedGetter, int>::Element *E = keyed_getters_map.front(); E; E = E->next()) { + function->keyed_getters.write[E->get()] = E->key(); + } + } else { + function->_keyed_getters_count = 0; + function->_keyed_getters_ptr = nullptr; + } + + if (indexed_setters_map.size()) { + function->indexed_setters.resize(indexed_setters_map.size()); + function->_indexed_setters_count = function->indexed_setters.size(); + function->_indexed_setters_ptr = function->indexed_setters.ptr(); + for (const Map<Variant::ValidatedIndexedSetter, int>::Element *E = indexed_setters_map.front(); E; E = E->next()) { + function->indexed_setters.write[E->get()] = E->key(); + } + } else { + function->_indexed_setters_count = 0; + function->_indexed_setters_ptr = nullptr; + } + + if (indexed_getters_map.size()) { + function->indexed_getters.resize(indexed_getters_map.size()); + function->_indexed_getters_count = function->indexed_getters.size(); + function->_indexed_getters_ptr = function->indexed_getters.ptr(); + for (const Map<Variant::ValidatedIndexedGetter, int>::Element *E = indexed_getters_map.front(); E; E = E->next()) { + function->indexed_getters.write[E->get()] = E->key(); + } + } else { + function->_indexed_getters_count = 0; + function->_indexed_getters_ptr = nullptr; + } + + if (builtin_method_map.size()) { + function->builtin_methods.resize(builtin_method_map.size()); + function->_builtin_methods_ptr = function->builtin_methods.ptr(); + function->_builtin_methods_count = builtin_method_map.size(); + for (const Map<Variant::ValidatedBuiltInMethod, int>::Element *E = builtin_method_map.front(); E; E = E->next()) { + function->builtin_methods.write[E->get()] = E->key(); + } + } else { + function->_builtin_methods_ptr = nullptr; + function->_builtin_methods_count = 0; + } + + if (constructors_map.size()) { + function->constructors.resize(constructors_map.size()); + function->_constructors_ptr = function->constructors.ptr(); + function->_constructors_count = constructors_map.size(); + for (const Map<Variant::ValidatedConstructor, int>::Element *E = constructors_map.front(); E; E = E->next()) { + function->constructors.write[E->get()] = E->key(); + } + } else { + function->_constructors_ptr = nullptr; + function->_constructors_count = 0; + } + + 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->_call_size = call_max; + function->_instruction_args_size = instr_args_max; + function->_ptrcall_args_size = ptrcall_max; ended = true; return function; @@ -178,37 +299,56 @@ void GDScriptByteCodeGenerator::set_initial_line(int p_line) { function->_initial_line = p_line; } +#define HAS_BUILTIN_TYPE(m_var) \ + (m_var.type.has_type && m_var.type.kind == GDScriptDataType::BUILTIN) + +#define IS_BUILTIN_TYPE(m_var, m_type) \ + (m_var.type.has_type && m_var.type.kind == GDScriptDataType::BUILTIN && m_var.type.builtin_type == m_type) + void GDScriptByteCodeGenerator::write_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) { - append(GDScriptFunction::OPCODE_OPERATOR); - append(p_operator); + if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand)) { + // Gather specific operator. + Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type); + + append(GDScriptFunction::OPCODE_OPERATOR_VALIDATED, 3); + append(p_left_operand); + append(p_right_operand); + append(p_target); + append(op_func); + return; + } + + // No specific types, perform variant evaluation. + append(GDScriptFunction::OPCODE_OPERATOR, 3); append(p_left_operand); append(p_right_operand); append(p_target); + append(p_operator); } void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) { - append(GDScriptFunction::OPCODE_EXTENDS_TEST); + append(GDScriptFunction::OPCODE_EXTENDS_TEST, 3); append(p_source); append(p_type); append(p_target); } void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) { - append(GDScriptFunction::OPCODE_IS_BUILTIN); + append(GDScriptFunction::OPCODE_IS_BUILTIN, 3); append(p_source); - append(p_type); append(p_target); + append(p_type); } void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) { - append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); append(p_left_operand); logic_op_jump_pos1.push_back(opcodes.size()); append(0); // Jump target, will be patched. } void GDScriptByteCodeGenerator::write_and_right_operand(const Address &p_right_operand) { - append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); append(p_right_operand); logic_op_jump_pos2.push_back(opcodes.size()); append(0); // Jump target, will be patched. @@ -216,29 +356,29 @@ void GDScriptByteCodeGenerator::write_and_right_operand(const Address &p_right_o void GDScriptByteCodeGenerator::write_end_and(const Address &p_target) { // If here means both operands are true. - append(GDScriptFunction::OPCODE_ASSIGN_TRUE); + append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); append(p_target); // Jump away from the fail condition. - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); append(opcodes.size() + 3); // Here it means one of operands is false. patch_jump(logic_op_jump_pos1.back()->get()); patch_jump(logic_op_jump_pos2.back()->get()); logic_op_jump_pos1.pop_back(); logic_op_jump_pos2.pop_back(); - append(GDScriptFunction::OPCODE_ASSIGN_FALSE); + append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 0); append(p_target); } void GDScriptByteCodeGenerator::write_or_left_operand(const Address &p_left_operand) { - append(GDScriptFunction::OPCODE_JUMP_IF); + append(GDScriptFunction::OPCODE_JUMP_IF, 1); append(p_left_operand); logic_op_jump_pos1.push_back(opcodes.size()); append(0); // Jump target, will be patched. } void GDScriptByteCodeGenerator::write_or_right_operand(const Address &p_right_operand) { - append(GDScriptFunction::OPCODE_JUMP_IF); + append(GDScriptFunction::OPCODE_JUMP_IF, 1); append(p_right_operand); logic_op_jump_pos2.push_back(opcodes.size()); append(0); // Jump target, will be patched. @@ -246,17 +386,17 @@ void GDScriptByteCodeGenerator::write_or_right_operand(const Address &p_right_op void GDScriptByteCodeGenerator::write_end_or(const Address &p_target) { // If here means both operands are false. - append(GDScriptFunction::OPCODE_ASSIGN_FALSE); + append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); append(p_target); // Jump away from the success condition. - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); append(opcodes.size() + 3); // Here it means one of operands is false. patch_jump(logic_op_jump_pos1.back()->get()); patch_jump(logic_op_jump_pos2.back()->get()); logic_op_jump_pos1.pop_back(); logic_op_jump_pos2.pop_back(); - append(GDScriptFunction::OPCODE_ASSIGN_TRUE); + append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); append(p_target); } @@ -265,18 +405,18 @@ void GDScriptByteCodeGenerator::write_start_ternary(const Address &p_target) { } void GDScriptByteCodeGenerator::write_ternary_condition(const Address &p_condition) { - append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); append(p_condition); ternary_jump_fail_pos.push_back(opcodes.size()); append(0); // Jump target, will be patched. } void GDScriptByteCodeGenerator::write_ternary_true_expr(const Address &p_expr) { - append(GDScriptFunction::OPCODE_ASSIGN); + append(GDScriptFunction::OPCODE_ASSIGN, 2); append(ternary_result.back()->get()); append(p_expr); // Jump away from the false path. - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); ternary_jump_skip_pos.push_back(opcodes.size()); append(0); // Fail must jump here. @@ -285,7 +425,7 @@ void GDScriptByteCodeGenerator::write_ternary_true_expr(const Address &p_expr) { } void GDScriptByteCodeGenerator::write_ternary_false_expr(const Address &p_expr) { - append(GDScriptFunction::OPCODE_ASSIGN); + append(GDScriptFunction::OPCODE_ASSIGN, 2); append(ternary_result.back()->get()); append(p_expr); } @@ -296,43 +436,100 @@ void GDScriptByteCodeGenerator::write_end_ternary() { } void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address &p_index, const Address &p_source) { - append(GDScriptFunction::OPCODE_SET); + if (HAS_BUILTIN_TYPE(p_target)) { + if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_setter(p_target.type.builtin_type)) { + // Use indexed setter instead. + Variant::ValidatedIndexedSetter setter = Variant::get_member_validated_indexed_setter(p_target.type.builtin_type); + append(GDScriptFunction::OPCODE_SET_INDEXED_VALIDATED, 3); + append(p_target); + append(p_index); + append(p_source); + append(setter); + return; + } else if (Variant::get_member_validated_keyed_setter(p_target.type.builtin_type)) { + Variant::ValidatedKeyedSetter setter = Variant::get_member_validated_keyed_setter(p_target.type.builtin_type); + append(GDScriptFunction::OPCODE_SET_KEYED_VALIDATED, 3); + append(p_target); + append(p_index); + append(p_source); + append(setter); + return; + } + } + + append(GDScriptFunction::OPCODE_SET_KEYED, 3); append(p_target); append(p_index); append(p_source); } void GDScriptByteCodeGenerator::write_get(const Address &p_target, const Address &p_index, const Address &p_source) { - append(GDScriptFunction::OPCODE_GET); + if (HAS_BUILTIN_TYPE(p_source)) { + if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_getter(p_source.type.builtin_type)) { + // Use indexed getter instead. + Variant::ValidatedIndexedGetter getter = Variant::get_member_validated_indexed_getter(p_source.type.builtin_type); + append(GDScriptFunction::OPCODE_GET_INDEXED_VALIDATED, 3); + append(p_source); + append(p_index); + append(p_target); + append(getter); + return; + } else if (Variant::get_member_validated_keyed_getter(p_source.type.builtin_type)) { + Variant::ValidatedKeyedGetter getter = Variant::get_member_validated_keyed_getter(p_source.type.builtin_type); + append(GDScriptFunction::OPCODE_GET_KEYED_VALIDATED, 3); + append(p_source); + append(p_index); + append(p_target); + append(getter); + return; + } + } + append(GDScriptFunction::OPCODE_GET_KEYED, 3); append(p_source); append(p_index); append(p_target); } void GDScriptByteCodeGenerator::write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) { - append(GDScriptFunction::OPCODE_SET_NAMED); + if (HAS_BUILTIN_TYPE(p_target) && Variant::get_member_validated_setter(p_target.type.builtin_type, p_name)) { + Variant::ValidatedSetter setter = Variant::get_member_validated_setter(p_target.type.builtin_type, p_name); + append(GDScriptFunction::OPCODE_SET_NAMED_VALIDATED, 2); + append(p_target); + append(p_source); + append(setter); + return; + } + append(GDScriptFunction::OPCODE_SET_NAMED, 2); append(p_target); - append(p_name); append(p_source); + append(p_name); } void GDScriptByteCodeGenerator::write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) { - append(GDScriptFunction::OPCODE_GET_NAMED); + if (HAS_BUILTIN_TYPE(p_source) && Variant::get_member_validated_getter(p_source.type.builtin_type, p_name)) { + Variant::ValidatedGetter getter = Variant::get_member_validated_getter(p_source.type.builtin_type, p_name); + append(GDScriptFunction::OPCODE_GET_NAMED_VALIDATED, 2); + append(p_source); + append(p_target); + append(getter); + return; + } + append(GDScriptFunction::OPCODE_GET_NAMED, 2); append(p_source); - append(p_name); append(p_target); + append(p_name); } void GDScriptByteCodeGenerator::write_set_member(const Address &p_value, const StringName &p_name) { - append(GDScriptFunction::OPCODE_SET_MEMBER); - append(p_name); + append(GDScriptFunction::OPCODE_SET_MEMBER, 1); append(p_value); + append(p_name); } void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const StringName &p_name) { - append(GDScriptFunction::OPCODE_GET_MEMBER); - append(p_name); + append(GDScriptFunction::OPCODE_GET_MEMBER, 1); append(p_target); + append(p_name); } void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { @@ -340,34 +537,35 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr // Typed assignment. switch (p_target.type.kind) { case GDScriptDataType::BUILTIN: { - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); - append(p_target.type.builtin_type); + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); append(p_target); append(p_source); + append(p_target.type.builtin_type); } break; case GDScriptDataType::NATIVE: { int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type]; class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); - append(class_idx); + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3); append(p_target); append(p_source); + append(class_idx); } break; case GDScriptDataType::SCRIPT: case GDScriptDataType::GDSCRIPT: { Variant script = p_target.type.script_type; int idx = get_constant_pos(script); + idx |= (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); - append(idx); + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3); append(p_target); append(p_source); + append(idx); } break; default: { ERR_PRINT("Compiler bug: unresolved assign."); // Shouldn't get here, but fail-safe to a regular assignment - append(GDScriptFunction::OPCODE_ASSIGN); + append(GDScriptFunction::OPCODE_ASSIGN, 2); append(p_target); append(p_source); } @@ -375,13 +573,13 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr } else { if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { // Need conversion.. - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); - append(p_target.type.builtin_type); + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); append(p_target); append(p_source); + append(p_target.type.builtin_type); } else { // Either untyped assignment or already type-checked by the parser - append(GDScriptFunction::OPCODE_ASSIGN); + append(GDScriptFunction::OPCODE_ASSIGN, 2); append(p_target); append(p_source); } @@ -389,34 +587,37 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr } void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) { - append(GDScriptFunction::OPCODE_ASSIGN_TRUE); + append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); append(p_target); } void GDScriptByteCodeGenerator::write_assign_false(const Address &p_target) { - append(GDScriptFunction::OPCODE_ASSIGN_FALSE); + append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); append(p_target); } void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) { + int index = 0; + switch (p_type.kind) { case GDScriptDataType::BUILTIN: { - append(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); - append(p_type.builtin_type); + append(GDScriptFunction::OPCODE_CAST_TO_BUILTIN, 2); + index = p_type.builtin_type; } break; case GDScriptDataType::NATIVE: { int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_type.native_type]; class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_CAST_TO_NATIVE); - append(class_idx); + append(GDScriptFunction::OPCODE_CAST_TO_NATIVE, 3); + index = class_idx; } break; case GDScriptDataType::SCRIPT: case GDScriptDataType::GDSCRIPT: { Variant script = p_type.script_type; int idx = get_constant_pos(script); + idx |= (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); - append(idx); + append(GDScriptFunction::OPCODE_CAST_TO_SCRIPT, 3); + index = idx; } break; default: { return; @@ -425,147 +626,272 @@ void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Addres append(p_source); append(p_target); + append(index); } void GDScriptByteCodeGenerator::write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); - append(p_arguments.size()); - append(p_base); - append(p_function_name); + 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); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + append(p_function_name); } void GDScriptByteCodeGenerator::write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CALL_SELF_BASE); - append(p_function_name); - append(p_arguments.size()); + append(GDScriptFunction::OPCODE_CALL_SELF_BASE, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_target); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + append(p_function_name); } void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CALL_ASYNC); - append(p_arguments.size()); - append(p_base); - append(p_function_name); + append(GDScriptFunction::OPCODE_CALL_ASYNC, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } + append(p_base); append(p_target); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + append(p_function_name); } void GDScriptByteCodeGenerator::write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CALL_BUILT_IN); - append(p_function); - append(p_arguments.size()); + append(GDScriptFunction::OPCODE_CALL_BUILT_IN, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_target); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + 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); - append(p_arguments.size()); - append(p_base); - append(p_method->get_name()); +void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { + bool is_validated = false; + + // Check if all types are correct. + if (Variant::is_builtin_method_vararg(p_type, p_method)) { + is_validated = true; // Vararg works fine with any argument, since they can be any type. + } else if (p_arguments.size() == Variant::get_builtin_method_argument_count(p_type, p_method)) { + bool all_types_exact = true; + for (int i = 0; i < p_arguments.size(); i++) { + if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_builtin_method_argument_type(p_type, p_method, i))) { + all_types_exact = false; + break; + } + } + + is_validated = all_types_exact; + } + + if (!is_validated) { + // Perform regular call. + write_call(p_target, p_base, p_method, p_arguments); + return; + } + + append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } + append(p_base); append(p_target); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + append(Variant::get_validated_builtin_method(p_type, 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); - append(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_method->get_name()); + append(p_target); + append(p_arguments.size()); + 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()); + } + for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } + append(p_base); append(p_target); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + 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) { - append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); - append(p_arguments.size()); - append(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); - append(p_function_name); + 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(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); append(p_target); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + append(p_function_name); } void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); - append(p_arguments.size()); - append(p_base); - append(p_function_name); + 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); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + append(p_function_name); } void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CONSTRUCT); - append(p_type); - append(p_arguments.size()); + // Try to find an appropriate constructor. + bool all_have_type = true; + Vector<Variant::Type> arg_types; + for (int i = 0; i < p_arguments.size(); i++) { + if (!HAS_BUILTIN_TYPE(p_arguments[i])) { + all_have_type = false; + break; + } + arg_types.push_back(p_arguments[i].type.builtin_type); + } + if (all_have_type) { + int valid_constructor = -1; + for (int i = 0; i < Variant::get_constructor_count(p_type); i++) { + if (Variant::get_constructor_argument_count(p_type, i) != p_arguments.size()) { + continue; + } + int types_correct = true; + for (int j = 0; j < arg_types.size(); j++) { + if (arg_types[j] != Variant::get_constructor_argument_type(p_type, i, j)) { + types_correct = false; + break; + } + } + if (types_correct) { + valid_constructor = i; + break; + } + } + if (valid_constructor >= 0) { + append(GDScriptFunction::OPCODE_CONSTRUCT_VALIDATED, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(Variant::get_validated_constructor(p_type, valid_constructor)); + return; + } + } + + append(GDScriptFunction::OPCODE_CONSTRUCT, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_target); - alloc_call(p_arguments.size()); + append(p_arguments.size()); + append(p_type); } void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY); - append(p_arguments.size()); + append(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_target); + append(p_arguments.size()); } void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY); - append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. + append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_target); + append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. } void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { - append(GDScriptFunction::OPCODE_AWAIT); + append(GDScriptFunction::OPCODE_AWAIT, 1); append(p_operand); - append(GDScriptFunction::OPCODE_AWAIT_RESUME); + append(GDScriptFunction::OPCODE_AWAIT_RESUME, 1); append(p_target); } void GDScriptByteCodeGenerator::write_if(const Address &p_condition) { - append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); append(p_condition); if_jmp_addrs.push_back(opcodes.size()); append(0); // Jump destination, will be patched. } void GDScriptByteCodeGenerator::write_else() { - append(GDScriptFunction::OPCODE_JUMP); // Jump from true if block; + append(GDScriptFunction::OPCODE_JUMP, 0); // Jump from true if block; int else_jmp_addr = opcodes.size(); append(0); // Jump destination, will be patched. @@ -586,34 +912,121 @@ void GDScriptByteCodeGenerator::write_for(const Address &p_variable, const Addre current_breaks_to_patch.push_back(List<int>()); // Assign container. - append(GDScriptFunction::OPCODE_ASSIGN); + append(GDScriptFunction::OPCODE_ASSIGN, 2); append(container_pos); append(p_list); + GDScriptFunction::Opcode begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN; + GDScriptFunction::Opcode iterate_opcode = GDScriptFunction::OPCODE_ITERATE; + + if (p_list.type.has_type) { + if (p_list.type.kind == GDScriptDataType::BUILTIN) { + switch (p_list.type.builtin_type) { + case Variant::INT: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_INT; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_INT; + break; + case Variant::FLOAT: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_FLOAT; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_FLOAT; + break; + case Variant::VECTOR2: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR2; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR2; + break; + case Variant::VECTOR2I: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR2I; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR2I; + break; + case Variant::VECTOR3: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR3; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR3; + break; + case Variant::VECTOR3I: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR3I; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR3I; + break; + case Variant::STRING: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_STRING; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_STRING; + break; + case Variant::DICTIONARY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_DICTIONARY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_DICTIONARY; + break; + case Variant::ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_ARRAY; + break; + case Variant::PACKED_BYTE_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_BYTE_ARRAY; + break; + case Variant::PACKED_INT32_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_INT32_ARRAY; + break; + case Variant::PACKED_INT64_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_INT64_ARRAY; + break; + case Variant::PACKED_FLOAT32_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_FLOAT32_ARRAY; + break; + case Variant::PACKED_FLOAT64_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_FLOAT64_ARRAY; + break; + case Variant::PACKED_STRING_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_STRING_ARRAY; + break; + case Variant::PACKED_VECTOR2_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_VECTOR2_ARRAY; + break; + case Variant::PACKED_VECTOR3_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_VECTOR3_ARRAY; + break; + case Variant::PACKED_COLOR_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_COLOR_ARRAY; + break; + default: + break; + } + } else { + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_OBJECT; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_OBJECT; + } + } + // Begin loop. - append(GDScriptFunction::OPCODE_ITERATE_BEGIN); + append(begin_opcode, 3); append(counter_pos); append(container_pos); + append(p_variable); for_jmp_addrs.push_back(opcodes.size()); append(0); // End of loop address, will be patched. - append(p_variable); - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); append(opcodes.size() + 6); // Skip over 'continue' code. // Next iteration. int continue_addr = opcodes.size(); continue_addrs.push_back(continue_addr); - append(GDScriptFunction::OPCODE_ITERATE); + append(iterate_opcode, 3); append(counter_pos); append(container_pos); + append(p_variable); for_jmp_addrs.push_back(opcodes.size()); append(0); // Jump destination, will be patched. - append(p_variable); } void GDScriptByteCodeGenerator::write_endfor() { // Jump back to loop check. - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); append(continue_addrs.back()->get()); continue_addrs.pop_back(); @@ -641,7 +1054,7 @@ void GDScriptByteCodeGenerator::start_while_condition() { void GDScriptByteCodeGenerator::write_while(const Address &p_condition) { // Condition check. - append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); append(p_condition); while_jmp_addrs.push_back(opcodes.size()); append(0); // End of loop address, will be patched. @@ -649,7 +1062,7 @@ void GDScriptByteCodeGenerator::write_while(const Address &p_condition) { void GDScriptByteCodeGenerator::write_endwhile() { // Jump back to loop check. - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); append(continue_addrs.back()->get()); continue_addrs.pop_back(); @@ -687,39 +1100,39 @@ void GDScriptByteCodeGenerator::end_match() { } void GDScriptByteCodeGenerator::write_break() { - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); current_breaks_to_patch.back()->get().push_back(opcodes.size()); append(0); } void GDScriptByteCodeGenerator::write_continue() { - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); append(continue_addrs.back()->get()); } void GDScriptByteCodeGenerator::write_continue_match() { - append(GDScriptFunction::OPCODE_JUMP); + append(GDScriptFunction::OPCODE_JUMP, 0); match_continues_to_patch.back()->get().push_back(opcodes.size()); append(0); } void GDScriptByteCodeGenerator::write_breakpoint() { - append(GDScriptFunction::OPCODE_BREAKPOINT); + append(GDScriptFunction::OPCODE_BREAKPOINT, 0); } void GDScriptByteCodeGenerator::write_newline(int p_line) { - append(GDScriptFunction::OPCODE_LINE); + append(GDScriptFunction::OPCODE_LINE, 0); append(p_line); current_line = p_line; } void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { - append(GDScriptFunction::OPCODE_RETURN); + append(GDScriptFunction::OPCODE_RETURN, 1); append(p_return_value); } void GDScriptByteCodeGenerator::write_assert(const Address &p_test, const Address &p_message) { - append(GDScriptFunction::OPCODE_ASSERT); + append(GDScriptFunction::OPCODE_ASSERT, 2); append(p_test); append(p_message); } diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 62438b6dd2..a546920fb2 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -33,6 +33,8 @@ #include "gdscript_codegen.h" +#include "gdscript_function.h" + class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { bool ended = false; GDScriptFunction *function = nullptr; @@ -49,15 +51,26 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { int current_stack_size = 0; int current_temporaries = 0; + 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; #ifdef TOOLS_ENABLED Vector<StringName> named_globals; #endif - int current_line = 0; - int stack_max = 0; - int call_max = 0; + Map<Variant::ValidatedOperatorEvaluator, int> operator_func_map; + Map<Variant::ValidatedSetter, int> setters_map; + Map<Variant::ValidatedGetter, int> getters_map; + Map<Variant::ValidatedKeyedSetter, int> keyed_setters_map; + Map<Variant::ValidatedKeyedGetter, int> keyed_getters_map; + Map<Variant::ValidatedIndexedSetter, int> indexed_setters_map; + Map<Variant::ValidatedIndexedGetter, int> indexed_getters_map; + Map<Variant::ValidatedBuiltInMethod, int> builtin_method_map; + Map<Variant::ValidatedConstructor, int> constructors_map; + Map<MethodBind *, int> method_bind_map; List<int> if_jmp_addrs; // List since this can be nested. List<int> for_jmp_addrs; @@ -134,22 +147,105 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return pos; } + int get_operation_pos(const Variant::ValidatedOperatorEvaluator p_operation) { + if (operator_func_map.has(p_operation)) + return operator_func_map[p_operation]; + int pos = operator_func_map.size(); + operator_func_map[p_operation] = pos; + return pos; + } + + int get_setter_pos(const Variant::ValidatedSetter p_setter) { + if (setters_map.has(p_setter)) + return setters_map[p_setter]; + int pos = setters_map.size(); + setters_map[p_setter] = pos; + return pos; + } + + int get_getter_pos(const Variant::ValidatedGetter p_getter) { + if (getters_map.has(p_getter)) + return getters_map[p_getter]; + int pos = getters_map.size(); + getters_map[p_getter] = pos; + return pos; + } + + int get_keyed_setter_pos(const Variant::ValidatedKeyedSetter p_keyed_setter) { + if (keyed_setters_map.has(p_keyed_setter)) + return keyed_setters_map[p_keyed_setter]; + int pos = keyed_setters_map.size(); + keyed_setters_map[p_keyed_setter] = pos; + return pos; + } + + int get_keyed_getter_pos(const Variant::ValidatedKeyedGetter p_keyed_getter) { + if (keyed_getters_map.has(p_keyed_getter)) + return keyed_getters_map[p_keyed_getter]; + int pos = keyed_getters_map.size(); + keyed_getters_map[p_keyed_getter] = pos; + return pos; + } + + int get_indexed_setter_pos(const Variant::ValidatedIndexedSetter p_indexed_setter) { + if (indexed_setters_map.has(p_indexed_setter)) + return indexed_setters_map[p_indexed_setter]; + int pos = indexed_setters_map.size(); + indexed_setters_map[p_indexed_setter] = pos; + return pos; + } + + int get_indexed_getter_pos(const Variant::ValidatedIndexedGetter p_indexed_getter) { + if (indexed_getters_map.has(p_indexed_getter)) + return indexed_getters_map[p_indexed_getter]; + int pos = indexed_getters_map.size(); + indexed_getters_map[p_indexed_getter] = pos; + return pos; + } + + int get_builtin_method_pos(const Variant::ValidatedBuiltInMethod p_method) { + if (builtin_method_map.has(p_method)) { + return builtin_method_map[p_method]; + } + int pos = builtin_method_map.size(); + builtin_method_map[p_method] = pos; + return pos; + } + + int get_constructor_pos(const Variant::ValidatedConstructor p_constructor) { + if (constructors_map.has(p_constructor)) { + return constructors_map[p_constructor]; + } + int pos = constructors_map.size(); + constructors_map[p_constructor] = pos; + 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; } - void alloc_call(int p_params) { - if (p_params >= call_max) - call_max = p_params; - } - int increase_stack() { int top = current_stack_size++; alloc_stack(current_stack_size); 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: @@ -177,8 +273,13 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return -1; // Unreachable. } - void append(int code) { - opcodes.push_back(code); + void append(GDScriptFunction::Opcode p_code, int p_argument_count) { + opcodes.push_back((p_code & GDScriptFunction::INSTR_MASK) | (p_argument_count << GDScriptFunction::INSTR_BITS)); + instr_args_max = MAX(instr_args_max, p_argument_count); + } + + void append(int p_code) { + opcodes.push_back(p_code); } void append(const Address &p_address) { @@ -189,6 +290,46 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { opcodes.push_back(get_name_map_pos(p_name)); } + void append(const Variant::ValidatedOperatorEvaluator p_operation) { + opcodes.push_back(get_operation_pos(p_operation)); + } + + void append(const Variant::ValidatedSetter p_setter) { + opcodes.push_back(get_setter_pos(p_setter)); + } + + void append(const Variant::ValidatedGetter p_getter) { + opcodes.push_back(get_getter_pos(p_getter)); + } + + void append(const Variant::ValidatedKeyedSetter p_keyed_setter) { + opcodes.push_back(get_keyed_setter_pos(p_keyed_setter)); + } + + void append(const Variant::ValidatedKeyedGetter p_keyed_getter) { + opcodes.push_back(get_keyed_getter_pos(p_keyed_getter)); + } + + void append(const Variant::ValidatedIndexedSetter p_indexed_setter) { + opcodes.push_back(get_indexed_setter_pos(p_indexed_setter)); + } + + void append(const Variant::ValidatedIndexedGetter p_indexed_getter) { + opcodes.push_back(get_indexed_getter_pos(p_indexed_getter)); + } + + void append(const Variant::ValidatedBuiltInMethod p_method) { + opcodes.push_back(get_builtin_method_pos(p_method)); + } + + void append(const Variant::ValidatedConstructor p_constructor) { + opcodes.push_back(get_constructor_pos(p_constructor)); + } + + 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(); } @@ -244,8 +385,9 @@ 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_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &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; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 9872a61423..2616a34719 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -126,8 +126,9 @@ 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_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &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; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index a64a05fcba..69fe6d27cc 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/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,28 @@ 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 if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) { + gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments); } else { gen->write_call(result, base, call->function_name, arguments); } @@ -493,7 +570,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; diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp new file mode 100644 index 0000000000..c918251772 --- /dev/null +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -0,0 +1,813 @@ +/*************************************************************************/ +/* gdscript_disassembler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef DEBUG_ENABLED + +#include "gdscript_function.h" + +#include "core/string/string_builder.h" +#include "gdscript.h" +#include "gdscript_functions.h" + +static String _get_variant_string(const Variant &p_variant) { + String txt; + if (p_variant.get_type() == Variant::STRING) { + txt = "\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::STRING_NAME) { + txt = "&\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::NODE_PATH) { + txt = "^\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::OBJECT) { + Object *obj = p_variant; + if (!obj) { + txt = "null"; + } else { + GDScriptNativeClass *cls = Object::cast_to<GDScriptNativeClass>(obj); + if (cls) { + txt += cls->get_name(); + txt += " (class)"; + } else { + txt = obj->get_class(); + if (obj->get_script_instance()) { + txt += "(" + obj->get_script_instance()->get_script()->get_path() + ")"; + } + } + } + } else { + txt = p_variant; + } + return txt; +} + +static String _disassemble_address(const GDScript *p_script, const GDScriptFunction &p_function, int p_address) { + int addr = p_address & GDScriptFunction::ADDR_MASK; + + switch (p_address >> GDScriptFunction::ADDR_BITS) { + case GDScriptFunction::ADDR_TYPE_SELF: { + return "self"; + } break; + case GDScriptFunction::ADDR_TYPE_CLASS: { + return "class"; + } break; + case GDScriptFunction::ADDR_TYPE_MEMBER: { + return "member(" + p_script->debug_get_member_by_index(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT: { + return "class_const(" + p_function.get_global_name(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT: { + return "const(" + _get_variant_string(p_function.get_constant(addr)) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_STACK: { + return "stack(" + itos(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_STACK_VARIABLE: { + return "var_stack(" + itos(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_GLOBAL: { + return "global(" + _get_variant_string(GDScriptLanguage::get_singleton()->get_global_array()[addr]) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL: { + return "named_global(" + p_function.get_global_name(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_NIL: { + return "nil"; + } break; + } + + return "<err>"; +} + +void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { +#define DADDR(m_ip) (_disassemble_address(_script, *this, _code_ptr[ip + m_ip])) + + for (int ip = 0; ip < _code_size;) { + StringBuilder text; + int incr = 0; + + text += " "; + text += itos(ip); + text += ": "; + + // This makes the compiler complain if some opcode is unchecked in the switch. + Opcode code = Opcode(_code_ptr[ip] & INSTR_MASK); + int instr_var_args = (_code_ptr[ip] & INSTR_ARGS_MASK) >> INSTR_BITS; + + switch (code) { + case OPCODE_OPERATOR: { + int operation = _code_ptr[ip + 4]; + + text += "operator "; + + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += " "; + text += Variant::get_operator_name(Variant::Operator(operation)); + text += " "; + text += DADDR(2); + + incr += 5; + } break; + case OPCODE_OPERATOR_VALIDATED: { + text += "validated operator "; + + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += " <operator function> "; + text += DADDR(2); + + incr += 5; + } break; + case OPCODE_EXTENDS_TEST: { + text += "is object "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += " is "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_IS_BUILTIN: { + text += "is builtin "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += " is "; + text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3])); + + incr += 4; + } break; + case OPCODE_SET_KEYED: { + text += "set keyed "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "] = "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_SET_KEYED_VALIDATED: { + text += "set keyed validated "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "] = "; + text += DADDR(3); + + incr += 5; + } break; + case OPCODE_SET_INDEXED_VALIDATED: { + text += "set indexed validated "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "] = "; + text += DADDR(3); + + incr += 5; + } break; + case OPCODE_GET_KEYED: { + text += "get keyed "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "]"; + + incr += 4; + } break; + case OPCODE_GET_KEYED_VALIDATED: { + text += "get keyed validated "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "]"; + + incr += 5; + } break; + case OPCODE_GET_INDEXED_VALIDATED: { + text += "get indexed validated "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "]"; + + incr += 5; + } break; + case OPCODE_SET_NAMED: { + text += "set_named "; + text += DADDR(1); + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 3]]; + text += "\"] = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_SET_NAMED_VALIDATED: { + text += "set_named validated "; + text += DADDR(1); + text += "[\""; + text += "<unknown name>"; + text += "\"] = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_GET_NAMED: { + text += "get_named "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 3]]; + text += "\"]"; + + incr += 4; + } break; + case OPCODE_GET_NAMED_VALIDATED: { + text += "get_named validated "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += "[\""; + text += "<unknown name>"; + text += "\"]"; + + incr += 4; + } break; + case OPCODE_SET_MEMBER: { + text += "set_member "; + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 2]]; + text += "\"] = "; + text += DADDR(1); + + incr += 3; + } break; + case OPCODE_GET_MEMBER: { + text += "get_member "; + text += DADDR(1); + text += " = "; + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 2]]; + text += "\"]"; + + incr += 3; + } break; + case OPCODE_ASSIGN: { + text += "assign "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 3; + } break; + case OPCODE_ASSIGN_TRUE: { + text += "assign "; + text += DADDR(1); + text += " = true"; + + incr += 2; + } break; + case OPCODE_ASSIGN_FALSE: { + text += "assign "; + text += DADDR(1); + text += " = false"; + + incr += 2; + } break; + case OPCODE_ASSIGN_TYPED_BUILTIN: { + text += "assign typed builtin ("; + text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 3]); + text += ") "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_ASSIGN_TYPED_NATIVE: { + Variant class_name = _constants_ptr[_code_ptr[ip + 3]]; + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(class_name.operator Object *()); + + text += "assign typed native ("; + text += nc->get_name().operator String(); + text += ") "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_ASSIGN_TYPED_SCRIPT: { + Variant script = _constants_ptr[_code_ptr[ip + 3]]; + Script *sc = Object::cast_to<Script>(script.operator Object *()); + + text += "assign typed script ("; + text += sc->get_path(); + text += ") "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_CAST_TO_BUILTIN: { + text += "cast builtin "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += " as "; + text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 1])); + + incr += 4; + } break; + case OPCODE_CAST_TO_NATIVE: { + Variant class_name = _constants_ptr[_code_ptr[ip + 1]]; + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(class_name.operator Object *()); + + text += "cast native "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += " as "; + text += nc->get_name(); + + incr += 4; + } break; + case OPCODE_CAST_TO_SCRIPT: { + text += "cast "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += " as "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_CONSTRUCT: { + Variant::Type t = Variant::Type(_code_ptr[ip + 3 + instr_var_args]); + int argc = _code_ptr[ip + 1 + instr_var_args]; + + text += "construct "; + text += DADDR(1 + argc); + text += " = "; + + text += Variant::get_type_name(t) + "("; + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(i + 1); + } + text += ")"; + + incr = 3 + instr_var_args; + } break; + case OPCODE_CONSTRUCT_VALIDATED: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + + text += "construct validated "; + text += DADDR(1 + argc); + text += " = "; + + text += "<unkown type>("; + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(i + 1); + } + text += ")"; + + incr = 3 + instr_var_args; + } break; + case OPCODE_CONSTRUCT_ARRAY: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += " make_array "; + text += DADDR(1 + argc); + text += " = ["; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + + text += "]"; + + incr += 3 + argc; + } break; + case OPCODE_CONSTRUCT_DICTIONARY: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += "make_dict "; + text += DADDR(1 + argc * 2); + text += " = {"; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i * 2 + 0); + text += ": "; + text += DADDR(1 + i * 2 + 1); + } + + text += "}"; + + incr += 3 + argc * 2; + } break; + case OPCODE_CALL: + case OPCODE_CALL_RETURN: + case OPCODE_CALL_ASYNC: { + bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_RETURN; + bool async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC; + + if (ret) { + text += "call-ret "; + } else if (async) { + text += "call-async "; + } else { + text += "call "; + } + + int argc = _code_ptr[ip + 1 + instr_var_args]; + if (ret || async) { + text += DADDR(2 + argc) + " = "; + } + + text += DADDR(1 + argc) + "."; + text += String(_global_names_ptr[_code_ptr[ip + 2 + instr_var_args]]); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + 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_BUILTIN_TYPE_VALIDATED: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + + text += "call-builtin-method validated "; + + text += DADDR(2 + argc) + " = "; + + text += DADDR(1) + "."; + text += "<unknown method>"; + + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 5 + argc; + } break; + case OPCODE_CALL_BUILT_IN: { + text += "call-built-in "; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(1 + argc) + " = "; + + text += GDScriptFunctions::get_func_name(GDScriptFunctions::Function(_code_ptr[ip + 2 + instr_var_args])); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CALL_SELF_BASE: { + text += "call-self-base "; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(2 + argc) + " = "; + + text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]]; + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_AWAIT: { + text += "await "; + text += DADDR(1); + + incr += 2; + } break; + case OPCODE_AWAIT_RESUME: { + text += "await resume "; + text += DADDR(1); + + incr = 2; + } break; + case OPCODE_JUMP: { + text += "jump "; + text += itos(_code_ptr[ip + 1]); + + incr = 2; + } break; + case OPCODE_JUMP_IF: { + text += "jump-if "; + text += DADDR(1); + text += " to "; + text += itos(_code_ptr[ip + 2]); + + incr = 3; + } break; + case OPCODE_JUMP_IF_NOT: { + text += "jump-if-not "; + text += DADDR(1); + text += " to "; + text += itos(_code_ptr[ip + 2]); + + incr = 3; + } break; + case OPCODE_JUMP_TO_DEF_ARGUMENT: { + text += "jump-to-default-argument "; + + incr = 1; + } break; + case OPCODE_RETURN: { + text += "return "; + text += DADDR(1); + + incr = 2; + } break; + +#define DISASSEMBLE_ITERATE(m_type) \ + case OPCODE_ITERATE_##m_type: { \ + text += "for-loop (typed "; \ + text += #m_type; \ + text += ") "; \ + text += DADDR(3); \ + text += " in "; \ + text += DADDR(2); \ + text += " counter "; \ + text += DADDR(1); \ + text += " end "; \ + text += itos(_code_ptr[ip + 4]); \ + incr += 5; \ + } break + +#define DISASSEMBLE_ITERATE_BEGIN(m_type) \ + case OPCODE_ITERATE_BEGIN_##m_type: { \ + text += "for-init (typed "; \ + text += #m_type; \ + text += ") "; \ + text += DADDR(3); \ + text += " in "; \ + text += DADDR(2); \ + text += " counter "; \ + text += DADDR(1); \ + text += " end "; \ + text += itos(_code_ptr[ip + 4]); \ + incr += 5; \ + } break + +#define DISASSEMBLE_ITERATE_TYPES(m_macro) \ + m_macro(INT); \ + m_macro(FLOAT); \ + m_macro(VECTOR2); \ + m_macro(VECTOR2I); \ + m_macro(VECTOR3); \ + m_macro(VECTOR3I); \ + m_macro(STRING); \ + m_macro(DICTIONARY); \ + m_macro(ARRAY); \ + m_macro(PACKED_BYTE_ARRAY); \ + m_macro(PACKED_INT32_ARRAY); \ + m_macro(PACKED_INT64_ARRAY); \ + m_macro(PACKED_FLOAT32_ARRAY); \ + m_macro(PACKED_FLOAT64_ARRAY); \ + m_macro(PACKED_STRING_ARRAY); \ + m_macro(PACKED_VECTOR2_ARRAY); \ + m_macro(PACKED_VECTOR3_ARRAY); \ + m_macro(PACKED_COLOR_ARRAY); \ + m_macro(OBJECT) + + case OPCODE_ITERATE_BEGIN: { + text += "for-init "; + text += DADDR(3); + text += " in "; + text += DADDR(2); + text += " counter "; + text += DADDR(1); + text += " end "; + text += itos(_code_ptr[ip + 4]); + + incr += 5; + } break; + DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE_BEGIN); + case OPCODE_ITERATE: { + text += "for-loop "; + text += DADDR(2); + text += " in "; + text += DADDR(2); + text += " counter "; + text += DADDR(1); + text += " end "; + text += itos(_code_ptr[ip + 4]); + + incr += 5; + } break; + DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE); + case OPCODE_LINE: { + int line = _code_ptr[ip + 1] - 1; + if (line >= 0 && line < p_code_lines.size()) { + text += "line "; + text += itos(line + 1); + text += ": "; + text += p_code_lines[line]; + } else { + text += ""; + } + + incr += 2; + } break; + case OPCODE_ASSERT: { + text += "assert ("; + text += DADDR(1); + text += ", "; + text += DADDR(2); + text += ")"; + + incr += 3; + } break; + case OPCODE_BREAKPOINT: { + text += "breakpoint"; + + incr += 1; + } break; + case OPCODE_END: { + text += "== END =="; + + incr += 1; + } break; + } + + ip += incr; + if (text.get_string_length() > 0) { + print_line(text.as_string()); + } + } +} + +#endif diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 8372672cf7..32372439c5 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -30,1574 +30,7 @@ #include "gdscript_function.h" -#include "core/os/os.h" #include "gdscript.h" -#include "gdscript_functions.h" - -#ifdef DEBUG_ENABLED -#include "core/string/string_builder.h" -#endif - -Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const { - int address = p_address & ADDR_MASK; - - //sequential table (jump table generated by compiler) - switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) { - case ADDR_TYPE_SELF: { -#ifdef DEBUG_ENABLED - if (unlikely(!p_instance)) { - r_error = "Cannot access self without instance."; - return nullptr; - } -#endif - return &self; - } break; - case ADDR_TYPE_CLASS: { - return &static_ref; - } break; - case ADDR_TYPE_MEMBER: { -#ifdef DEBUG_ENABLED - if (unlikely(!p_instance)) { - r_error = "Cannot access member without instance."; - return nullptr; - } -#endif - //member indexing is O(1) - return &p_instance->members.write[address]; - } break; - case ADDR_TYPE_CLASS_CONSTANT: { - //todo change to index! - GDScript *s = p_script; -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); -#endif - const StringName *sn = &_global_names_ptr[address]; - - while (s) { - GDScript *o = s; - while (o) { - Map<StringName, Variant>::Element *E = o->constants.find(*sn); - if (E) { - return &E->get(); - } - o = o->_owner; - } - s = s->_base; - } - - ERR_FAIL_V_MSG(nullptr, "GDScriptCompiler bug."); - } break; - case ADDR_TYPE_LOCAL_CONSTANT: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _constant_count, nullptr); -#endif - return &_constants_ptr[address]; - } break; - case ADDR_TYPE_STACK: - case ADDR_TYPE_STACK_VARIABLE: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _stack_size, nullptr); -#endif - return &p_stack[address]; - } break; - case ADDR_TYPE_GLOBAL: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), nullptr); -#endif - return &GDScriptLanguage::get_singleton()->get_global_array()[address]; - } break; -#ifdef TOOLS_ENABLED - case ADDR_TYPE_NAMED_GLOBAL: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); -#endif - StringName id = _global_names_ptr[address]; - - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { - return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; - } else { - r_error = "Autoload singleton '" + String(id) + "' has been removed."; - return nullptr; - } - } break; -#endif - case ADDR_TYPE_NIL: { - return &nil; - } break; - } - - ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode)."); - return nullptr; -} - -#ifdef DEBUG_ENABLED -static String _get_var_type(const Variant *p_var) { - String basestr; - - if (p_var->get_type() == Variant::OBJECT) { - bool was_freed; - Object *bobj = p_var->get_validated_object_with_check(was_freed); - if (!bobj) { - if (was_freed) { - basestr = "null instance"; - } else { - basestr = "previously freed"; - } - } else { - if (bobj->get_script_instance()) { - basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; - } else { - basestr = bobj->get_class(); - } - } - - } else { - basestr = Variant::get_type_name(p_var->get_type()); - } - - return basestr; -} -#endif // DEBUG_ENABLED - -String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const { - String err_text; - - if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { - int errorarg = p_err.argument; - // Handle the Object to Object case separately as we don't have further class details. -#ifdef DEBUG_ENABLED - if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { - err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; - } else -#endif // DEBUG_ENABLED - { - err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; - } - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - err_text = "Invalid call. Nonexistent " + p_where + "."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { - err_text = "Attempt to call " + p_where + " on a null instance."; - } else { - err_text = "Bug, call error: #" + itos(p_err.error); - } - - return err_text; -} - -#if defined(__GNUC__) -#define OPCODES_TABLE \ - static const void *switch_table_ops[] = { \ - &&OPCODE_OPERATOR, \ - &&OPCODE_EXTENDS_TEST, \ - &&OPCODE_IS_BUILTIN, \ - &&OPCODE_SET, \ - &&OPCODE_GET, \ - &&OPCODE_SET_NAMED, \ - &&OPCODE_GET_NAMED, \ - &&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 \ - }; \ - 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) \ - m_op: -#define OPCODE_WHILE(m_test) -#define OPCODES_END \ - OPSEXIT: -#define OPCODES_OUT \ - OPSOUT: -#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]] -#define OPCODE_SWITCH(m_test) DISPATCH_OPCODE; -#define OPCODE_BREAK goto OPSEXIT -#define OPCODE_OUT goto OPSOUT -#else -#define OPCODES_TABLE -#define OPCODE(m_op) case m_op: -#define OPCODE_WHILE(m_test) while (m_test) -#define OPCODES_END -#define OPCODES_OUT -#define DISPATCH_OPCODE continue -#define OPCODE_SWITCH(m_test) switch (m_test) -#define OPCODE_BREAK break -#define OPCODE_OUT break -#endif - -Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) { - OPCODES_TABLE; - - if (!_code_ptr) { - return Variant(); - } - - r_err.error = Callable::CallError::CALL_OK; - - Variant self; - Variant static_ref; - Variant retvalue; - Variant *stack = nullptr; - Variant **call_args; - int defarg = 0; - -#ifdef DEBUG_ENABLED - - //GDScriptLanguage::get_singleton()->calls++; - -#endif - - uint32_t alloca_size = 0; - GDScript *script; - int ip = 0; - int line = _initial_line; - - if (p_state) { - //use existing (supplied) state (awaited) - stack = (Variant *)p_state->stack.ptr(); - call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check - line = p_state->line; - ip = p_state->ip; - alloca_size = p_state->stack.size(); - script = p_state->script; - p_instance = p_state->instance; - defarg = p_state->defarg; - self = p_state->self; - - } else { - if (p_argcount != _argument_count) { - if (p_argcount > _argument_count) { - r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_err.argument = _argument_count; - - return Variant(); - } else if (p_argcount < _argument_count - _default_arg_count) { - r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_err.argument = _argument_count - _default_arg_count; - return Variant(); - } else { - defarg = _argument_count - p_argcount; - } - } - - alloca_size = sizeof(Variant *) * _call_size + sizeof(Variant) * _stack_size; - - if (alloca_size) { - uint8_t *aptr = (uint8_t *)alloca(alloca_size); - - if (_stack_size) { - stack = (Variant *)aptr; - for (int i = 0; i < p_argcount; i++) { - if (!argument_types[i].has_type) { - memnew_placement(&stack[i], Variant(*p_args[i])); - continue; - } - - if (!argument_types[i].is_type(*p_args[i], true)) { - r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_err.argument = i; - r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; - return Variant(); - } - if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - Variant arg; - Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err); - memnew_placement(&stack[i], Variant(arg)); - } else { - memnew_placement(&stack[i], Variant(*p_args[i])); - } - } - for (int i = p_argcount; i < _stack_size; i++) { - memnew_placement(&stack[i], Variant); - } - } else { - stack = nullptr; - } - - if (_call_size) { - call_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; - } else { - call_args = nullptr; - } - - } else { - stack = nullptr; - call_args = nullptr; - } - - if (p_instance) { - if (p_instance->base_ref && static_cast<Reference *>(p_instance->owner)->is_referenced()) { - self = REF(static_cast<Reference *>(p_instance->owner)); - } else { - self = p_instance->owner; - } - script = p_instance->script.ptr(); - } else { - script = _script; - } - } - - static_ref = script; - - String err_text; - -#ifdef DEBUG_ENABLED - - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line); - } - -#define GD_ERR_BREAK(m_cond) \ - { \ - if (unlikely(m_cond)) { \ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \ - OPCODE_BREAK; \ - } \ - } - -#define CHECK_SPACE(m_space) \ - GD_ERR_BREAK((ip + m_space) > _code_size) - -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); \ - if (unlikely(!m_v)) \ - OPCODE_BREAK; - -#else -#define GD_ERR_BREAK(m_cond) -#define CHECK_SPACE(m_space) -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); - -#endif - -#ifdef DEBUG_ENABLED - - uint64_t function_start_time = 0; - uint64_t function_call_time = 0; - - if (GDScriptLanguage::get_singleton()->profiling) { - function_start_time = OS::get_singleton()->get_ticks_usec(); - function_call_time = 0; - profile.call_count++; - profile.frame_call_count++; - } - bool exit_ok = false; - bool awaited = false; -#endif - -#ifdef DEBUG_ENABLED - OPCODE_WHILE(ip < _code_size) { - int last_opcode = _code_ptr[ip]; -#else - OPCODE_WHILE(true) { -#endif - - OPCODE_SWITCH(_code_ptr[ip]) { - OPCODE(OPCODE_OPERATOR) { - CHECK_SPACE(5); - - bool valid; - Variant::Operator op = (Variant::Operator)_code_ptr[ip + 1]; - GD_ERR_BREAK(op >= Variant::OP_MAX); - - GET_VARIANT_PTR(a, 2); - GET_VARIANT_PTR(b, 3); - GET_VARIANT_PTR(dst, 4); - -#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 += 5; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_EXTENDS_TEST) { - CHECK_SPACE(4); - - GET_VARIANT_PTR(a, 1); - GET_VARIANT_PTR(b, 2); - GET_VARIANT_PTR(dst, 3); - -#ifdef DEBUG_ENABLED - if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) { - err_text = "Right operand of 'is' is not a class."; - OPCODE_BREAK; - } -#endif - - bool extends_ok = false; - if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) { -#ifdef DEBUG_ENABLED - bool was_freed; - Object *obj_A = a->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Left operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } - - Object *obj_B = b->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Right operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } -#else - - Object *obj_A = *a; - Object *obj_B = *b; -#endif // DEBUG_ENABLED - - GDScript *scr_B = Object::cast_to<GDScript>(obj_B); - - if (scr_B) { - //if B is a script, the only valid condition is that A has an instance which inherits from the script - //in other situation, this shoul return false. - - if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { - GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); - //bool found=false; - while (cmp) { - if (cmp == scr_B) { - //inherits from script, all ok - extends_ok = true; - break; - } - - cmp = cmp->_base; - } - } - - } else { - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); - -#ifdef DEBUG_ENABLED - if (!nc) { - err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "')."; - OPCODE_BREAK; - } -#endif - extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name()); - } - } - - *dst = extends_ok; - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_IS_BUILTIN) { - CHECK_SPACE(4); - - GET_VARIANT_PTR(value, 1); - Variant::Type var_type = (Variant::Type)_code_ptr[ip + 2]; - GET_VARIANT_PTR(dst, 3); - - GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); - - *dst = value->get_type() == var_type; - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(index, 2); - GET_VARIANT_PTR(value, 3); - - bool valid; - dst->set(*index, *value, &valid); - -#ifdef DEBUG_ENABLED - if (!valid) { - String v = index->operator String(); - if (v != "") { - v = "'" + v + "'"; - } else { - v = "of type '" + _get_var_type(index) + "'"; - } - err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; - OPCODE_BREAK; - } -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(src, 1); - GET_VARIANT_PTR(index, 2); - GET_VARIANT_PTR(dst, 3); - - bool valid; -#ifdef DEBUG_ENABLED - //allow better error message in cases where src and dst are the same stack position - Variant ret = src->get(*index, &valid); -#else - *dst = src->get(*index, &valid); - -#endif -#ifdef DEBUG_ENABLED - if (!valid) { - String v = index->operator String(); - if (v != "") { - v = "'" + v + "'"; - } else { - v = "of type '" + _get_var_type(index) + "'"; - } - err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; - OPCODE_BREAK; - } - *dst = ret; -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET_NAMED) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(value, 3); - - int indexname = _code_ptr[ip + 2]; - - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - - bool valid; - dst->set_named(*index, *value, valid); - -#ifdef DEBUG_ENABLED - if (!valid) { - String err_type; - err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; - OPCODE_BREAK; - } -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET_NAMED) { - CHECK_SPACE(4); - - GET_VARIANT_PTR(src, 1); - GET_VARIANT_PTR(dst, 3); - - int indexname = _code_ptr[ip + 2]; - - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - - bool valid; -#ifdef DEBUG_ENABLED - //allow better error message in cases where src and dst are the same stack position - Variant ret = src->get_named(*index, valid); - -#else - *dst = src->get_named(*index, valid); -#endif -#ifdef DEBUG_ENABLED - if (!valid) { - if (src->has_method(*index)) { - err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "'). Did you mean '." + index->operator String() + "()' or funcref(obj, \"" + index->operator String() + "\") ?"; - } else { - err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "')."; - } - OPCODE_BREAK; - } - *dst = ret; -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET_MEMBER) { - CHECK_SPACE(3); - int indexname = _code_ptr[ip + 1]; - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - GET_VARIANT_PTR(src, 2); - - bool valid; -#ifndef DEBUG_ENABLED - ClassDB::set_property(p_instance->owner, *index, *src, &valid); -#else - bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid); - if (!ok) { - err_text = "Internal error setting property: " + String(*index); - OPCODE_BREAK; - } else if (!valid) { - err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + "."; - OPCODE_BREAK; - } -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET_MEMBER) { - CHECK_SPACE(3); - int indexname = _code_ptr[ip + 1]; - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - GET_VARIANT_PTR(dst, 2); - -#ifndef DEBUG_ENABLED - ClassDB::get_property(p_instance->owner, *index, *dst); -#else - bool ok = ClassDB::get_property(p_instance->owner, *index, *dst); - if (!ok) { - err_text = "Internal error getting property: " + String(*index); - OPCODE_BREAK; - } -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN) { - CHECK_SPACE(3); - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(src, 2); - - *dst = *src; - - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TRUE) { - CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 1); - - *dst = true; - - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_FALSE) { - CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 1); - - *dst = false; - - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - - Variant::Type var_type = (Variant::Type)_code_ptr[ip + 1]; - GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); - - if (src->get_type() != var_type) { -#ifdef DEBUG_ENABLED - if (Variant::can_convert_strict(src->get_type(), var_type)) { -#endif // DEBUG_ENABLED - Callable::CallError ce; - Variant::construct(var_type, *dst, const_cast<const Variant **>(&src), 1, ce); - } else { -#ifdef DEBUG_ENABLED - err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; - OPCODE_BREAK; - } - } else { -#endif // DEBUG_ENABLED - *dst = *src; - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(type, 1); - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); - GD_ERR_BREAK(!nc); - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + nc->get_name() + "'."; - OPCODE_BREAK; - } - Object *src_obj = src->operator Object *(); - - if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { - err_text = "Trying to assign value of type '" + src_obj->get_class_name() + - "' to a variable of type '" + nc->get_name() + "'."; - OPCODE_BREAK; - } -#endif // DEBUG_ENABLED - *dst = *src; - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(type, 1); - Script *base_type = Object::cast_to<Script>(type->operator Object *()); - - GD_ERR_BREAK(!base_type); - - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - - if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { - ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); - if (!scr_inst) { - err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + - "' to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - - Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); - bool valid = false; - - while (src_type) { - if (src_type == base_type) { - valid = true; - break; - } - src_type = src_type->get_base_script().ptr(); - } - - if (!valid) { - err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + - "' to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - } -#endif // DEBUG_ENABLED - - *dst = *src; - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_BUILTIN) { - CHECK_SPACE(4); - Variant::Type to_type = (Variant::Type)_code_ptr[ip + 1]; - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); - - Callable::CallError err; - Variant::construct(to_type, *dst, (const Variant **)&src, 1, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'."; - OPCODE_BREAK; - } -#endif - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_NATIVE) { - CHECK_SPACE(4); - GET_VARIANT_PTR(to_type, 1); - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *()); - GD_ERR_BREAK(!nc); - -#ifdef DEBUG_ENABLED - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Invalid cast: can't convert a non-object value to an object type."; - OPCODE_BREAK; - } -#endif - Object *src_obj = src->operator Object *(); - - if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { - *dst = Variant(); // invalid cast, assign NULL - } else { - *dst = *src; - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_SCRIPT) { - CHECK_SPACE(4); - GET_VARIANT_PTR(to_type, 1); - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - Script *base_type = Object::cast_to<Script>(to_type->operator Object *()); - - GD_ERR_BREAK(!base_type); - -#ifdef DEBUG_ENABLED - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } -#endif - - bool valid = false; - - if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { - ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); - - if (scr_inst) { - Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); - - while (src_type) { - if (src_type == base_type) { - valid = true; - break; - } - src_type = src_type->get_base_script().ptr(); - } - } - } - - if (valid) { - *dst = *src; // Valid cast, copy the source object - } else { - *dst = Variant(); // invalid cast, assign NULL - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT) { - CHECK_SPACE(2); - Variant::Type t = Variant::Type(_code_ptr[ip + 1]); - int argc = _code_ptr[ip + 2]; - CHECK_SPACE(argc + 2); - Variant **argptrs = call_args; - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, 3 + i); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, 3 + argc); - Callable::CallError err; - Variant::construct(t, *dst, (const Variant **)argptrs, argc, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs); - OPCODE_BREAK; - } -#endif - - ip += 4 + argc; - //construct a basic type - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT_ARRAY) { - CHECK_SPACE(1); - int argc = _code_ptr[ip + 1]; - Array array; //arrays are always shared - array.resize(argc); - CHECK_SPACE(argc + 2); - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, 2 + i); - array[i] = *v; - } - - GET_VARIANT_PTR(dst, 2 + argc); - - *dst = array; - - ip += 3 + argc; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { - CHECK_SPACE(1); - int argc = _code_ptr[ip + 1]; - Dictionary dict; //arrays are always shared - - CHECK_SPACE(argc * 2 + 2); - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(k, 2 + i * 2 + 0); - GET_VARIANT_PTR(v, 2 + i * 2 + 1); - dict[*k] = *v; - } - - GET_VARIANT_PTR(dst, 2 + argc * 2); - - *dst = dict; - - ip += 3 + argc * 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_ASYNC) - OPCODE(OPCODE_CALL_RETURN) - OPCODE(OPCODE_CALL) { - CHECK_SPACE(4); - bool call_ret = _code_ptr[ip] != OPCODE_CALL; -#ifdef DEBUG_ENABLED - bool call_async = _code_ptr[ip] == OPCODE_CALL_ASYNC; -#endif - - int argc = _code_ptr[ip + 1]; - GET_VARIANT_PTR(base, 2); - int nameg = _code_ptr[ip + 3]; - - GD_ERR_BREAK(nameg < 0 || nameg >= _global_names_count); - const StringName *methodname = &_global_names_ptr[nameg]; - - GD_ERR_BREAK(argc < 0); - ip += 4; - CHECK_SPACE(argc + 1); - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i); - argptrs[i] = v; - } - -#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_VARIANT_PTR(ret, argc); - base->call(*methodname, (const Variant **)argptrs, argc, *ret, err); -#ifdef DEBUG_ENABLED - if (!call_async && ret->get_type() == Variant::OBJECT) { - // Check if getting a function state without await. - bool was_freed = false; - Object *obj = ret->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Got a freed object as a result of the call."; - OPCODE_BREAK; - } - if (obj && obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { - err_text = R"(Trying to call an async function without "await".)"; - OPCODE_BREAK; - } - } -#endif - } else { - Variant ret; - base->call(*methodname, (const Variant **)argptrs, argc, ret, 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 = *methodname; - 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; - } - } - } else if (methodstr == "call_recursive" && basestr == "TreeItem") { - if (argc >= 1) { - methodstr = String(*argptrs[0]) + " (via TreeItem.call_recursive)"; - if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { - err.argument += 1; - } - } - } - err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); - OPCODE_BREAK; - } -#endif - - //_call_func(nullptr,base,*methodname,ip,argc,p_instance,stack); - ip += argc + 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_BUILT_IN) { - CHECK_SPACE(4); - - GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 1]); - int argc = _code_ptr[ip + 2]; - GD_ERR_BREAK(argc < 0); - - ip += 3; - CHECK_SPACE(argc + 1); - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, argc); - - Callable::CallError err; - - GDScriptFunctions::call(func, (const Variant **)argptrs, argc, *dst, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - String methodstr = GDScriptFunctions::get_func_name(func); - if (dst->get_type() == Variant::STRING) { - //call provided error string - err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst); - } else { - err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs); - } - OPCODE_BREAK; - } -#endif - ip += argc + 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_SELF_BASE) { - CHECK_SPACE(2); - int self_fun = _code_ptr[ip + 1]; - -#ifdef DEBUG_ENABLED - if (self_fun < 0 || self_fun >= _global_names_count) { - err_text = "compiler bug, function name not found"; - OPCODE_BREAK; - } -#endif - const StringName *methodname = &_global_names_ptr[self_fun]; - - int argc = _code_ptr[ip + 2]; - - CHECK_SPACE(2 + argc + 1); - - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i + 3); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, argc + 3); - - const GDScript *gds = _script; - - const Map<StringName, GDScriptFunction *>::Element *E = nullptr; - while (gds->base.ptr()) { - gds = gds->base.ptr(); - E = gds->member_functions.find(*methodname); - if (E) { - break; - } - } - - Callable::CallError err; - - if (E) { - *dst = E->get()->call(p_instance, (const Variant **)argptrs, argc, err); - } else if (gds->native.ptr()) { - if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { - MethodBind *mb = ClassDB::get_method(gds->native->get_name(), *methodname); - if (!mb) { - err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - } else { - *dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err); - } - } else { - err.error = Callable::CallError::CALL_OK; - } - } else { - if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { - err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - } else { - err.error = Callable::CallError::CALL_OK; - } - } - - if (err.error != Callable::CallError::CALL_OK) { - String methodstr = *methodname; - err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs); - - OPCODE_BREAK; - } - - ip += 4 + argc; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_AWAIT) { - CHECK_SPACE(2); - - //do the oneshot connect - GET_VARIANT_PTR(argobj, 1); - - Signal sig; - bool is_signal = true; - - { - Variant result = *argobj; - - if (argobj->get_type() == Variant::OBJECT) { - bool was_freed = false; - Object *obj = argobj->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Trying to await on a freed object."; - OPCODE_BREAK; - } - - // Is this even possible to be null at this point? - if (obj) { - if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { - static StringName completed = _scs_create("completed"); - result = Signal(obj, completed); - } - } - } - - if (result.get_type() != Variant::SIGNAL) { - ip += 4; // Skip OPCODE_AWAIT_RESUME and its data. - // The stack pointer should be the same, so we don't need to set a return value. - is_signal = false; - } else { - sig = result; - } - } - - if (is_signal) { - Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState); - gdfs->function = this; - - gdfs->state.stack.resize(alloca_size); - //copy variant stack - for (int i = 0; i < _stack_size; i++) { - memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i])); - } - gdfs->state.stack_size = _stack_size; - gdfs->state.self = self; - gdfs->state.alloca_size = alloca_size; - gdfs->state.ip = ip + 2; - gdfs->state.line = line; - gdfs->state.script = _script; - { - MutexLock lock(GDScriptLanguage::get_singleton()->lock); - _script->pending_func_states.add(&gdfs->scripts_list); - if (p_instance) { - gdfs->state.instance = p_instance; - p_instance->pending_func_states.add(&gdfs->instances_list); - } else { - gdfs->state.instance = nullptr; - } - } -#ifdef DEBUG_ENABLED - gdfs->state.function_name = name; - gdfs->state.script_path = _script->get_path(); -#endif - gdfs->state.defarg = defarg; - gdfs->function = this; - - retvalue = gdfs; - - Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT); - if (err != OK) { - err_text = "Error connecting to signal: " + sig.get_name() + " during await."; - OPCODE_BREAK; - } - -#ifdef DEBUG_ENABLED - exit_ok = true; - awaited = true; -#endif - OPCODE_BREAK; - } - } - DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available). - - OPCODE(OPCODE_AWAIT_RESUME) { - CHECK_SPACE(2); -#ifdef DEBUG_ENABLED - if (!p_state) { - err_text = ("Invalid Resume (bug?)"); - OPCODE_BREAK; - } -#endif - GET_VARIANT_PTR(result, 1); - *result = p_state->result; - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP) { - CHECK_SPACE(2); - int to = _code_ptr[ip + 1]; - - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_IF) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(test, 1); - - bool result = test->booleanize(); - - if (result) { - int to = _code_ptr[ip + 2]; - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } else { - ip += 3; - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_IF_NOT) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(test, 1); - - bool result = test->booleanize(); - - if (!result) { - int to = _code_ptr[ip + 2]; - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } else { - ip += 3; - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_TO_DEF_ARGUMENT) { - CHECK_SPACE(2); - ip = _default_arg_ptr[defarg]; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_RETURN) { - CHECK_SPACE(2); - GET_VARIANT_PTR(r, 1); - retvalue = *r; -#ifdef DEBUG_ENABLED - exit_ok = true; -#endif - OPCODE_BREAK; - } - - OPCODE(OPCODE_ITERATE_BEGIN) { - CHECK_SPACE(8); //space for this a regular iterate - - GET_VARIANT_PTR(counter, 1); - GET_VARIANT_PTR(container, 2); - - bool valid; - if (!container->iter_init(*counter, valid)) { -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'."; - OPCODE_BREAK; - } -#endif - int jumpto = _code_ptr[ip + 3]; - GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); - ip = jumpto; - } else { - GET_VARIANT_PTR(iterator, 4); - - *iterator = container->iter_get(*counter, valid); -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'."; - OPCODE_BREAK; - } -#endif - ip += 5; //skip regular iterate which is always next - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ITERATE) { - CHECK_SPACE(4); - - GET_VARIANT_PTR(counter, 1); - GET_VARIANT_PTR(container, 2); - - bool valid; - if (!container->iter_next(*counter, valid)) { -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; - OPCODE_BREAK; - } -#endif - int jumpto = _code_ptr[ip + 3]; - GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); - ip = jumpto; - } else { - GET_VARIANT_PTR(iterator, 4); - - *iterator = container->iter_get(*counter, valid); -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; - OPCODE_BREAK; - } -#endif - ip += 5; //loop again - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSERT) { - CHECK_SPACE(3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(test, 1); - bool result = test->booleanize(); - - if (!result) { - String message_str; - if (_code_ptr[ip + 2] != 0) { - GET_VARIANT_PTR(message, 2); - message_str = *message; - } - if (message_str.empty()) { - err_text = "Assertion failed."; - } else { - err_text = "Assertion failed: " + message_str; - } - OPCODE_BREAK; - } - -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_BREAKPOINT) { -#ifdef DEBUG_ENABLED - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true); - } -#endif - ip += 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_LINE) { - CHECK_SPACE(2); - - line = _code_ptr[ip + 1]; - ip += 2; - - if (EngineDebugger::is_active()) { - // line - bool do_break = false; - - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) { - if (EngineDebugger::get_script_debugger()->get_depth() <= 0) { - EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1); - } - if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) { - do_break = true; - } - } - - if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) { - do_break = true; - } - - if (do_break) { - GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); - } - - EngineDebugger::get_singleton()->line_poll(); - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_END) { -#ifdef DEBUG_ENABLED - exit_ok = true; -#endif - OPCODE_BREAK; - } - -#if 0 // Enable for debugging. - default: { - err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip); - OPCODE_BREAK; - } -#endif - } - - OPCODES_END -#ifdef DEBUG_ENABLED - if (exit_ok) { - OPCODE_OUT; - } - //error - // function, file, line, error, explanation - String err_file; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->path != "") { - err_file = p_instance->script->path; - } else if (script) { - err_file = script->path; - } - if (err_file == "") { - err_file = "<built-in>"; - } - String err_func = name; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->name != "") { - err_func = p_instance->script->name + "." + err_func; - } - int err_line = line; - if (err_text == "") { - err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please)."; - } - - if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) { - // debugger break did not happen - - _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT); - } - -#endif - OPCODE_OUT; - } - - OPCODES_OUT -#ifdef DEBUG_ENABLED - if (GDScriptLanguage::get_singleton()->profiling) { - uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; - profile.total_time += time_taken; - profile.self_time += time_taken - function_call_time; - profile.frame_total_time += time_taken; - profile.frame_self_time += time_taken - function_call_time; - GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; - } - - // Check if this is the last time the function is resuming from await - // Will be true if never awaited as well - // When it's the last resume it will postpone the exit from stack, - // so the debugger knows which function triggered the resume of the next function (if any) - if (!p_state || awaited) { - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->exit_function(); - } -#endif - - if (_stack_size) { - //free stack - for (int i = 0; i < _stack_size; i++) { - stack[i].~Variant(); - } - } - -#ifdef DEBUG_ENABLED - } -#endif - - return retvalue; -} const int *GDScriptFunction::get_code() const { return _code_ptr; @@ -1706,31 +139,13 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String } } -GDScriptFunction::GDScriptFunction() : - function_list(this) { - _stack_size = 0; - _call_size = 0; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; +GDScriptFunction::GDScriptFunction() { name = "<anonymous>"; #ifdef DEBUG_ENABLED - _func_cname = nullptr; - { MutexLock lock(GDScriptLanguage::get_singleton()->lock); - GDScriptLanguage::get_singleton()->function_list.add(&function_list); } - - profile.call_count = 0; - profile.self_time = 0; - profile.total_time = 0; - profile.frame_call_count = 0; - profile.frame_self_time = 0; - profile.frame_total_time = 0; - profile.last_frame_call_count = 0; - profile.last_frame_self_time = 0; - profile.last_frame_total_time = 0; - #endif } @@ -1891,506 +306,3 @@ GDScriptFunctionState::~GDScriptFunctionState() { instances_list.remove_from_list(); } } - -#ifdef DEBUG_ENABLED -static String _get_variant_string(const Variant &p_variant) { - String txt; - if (p_variant.get_type() == Variant::STRING) { - txt = "\"" + String(p_variant) + "\""; - } else if (p_variant.get_type() == Variant::STRING_NAME) { - txt = "&\"" + String(p_variant) + "\""; - } else if (p_variant.get_type() == Variant::NODE_PATH) { - txt = "^\"" + String(p_variant) + "\""; - } else if (p_variant.get_type() == Variant::OBJECT) { - Object *obj = p_variant; - if (!obj) { - txt = "null"; - } else { - GDScriptNativeClass *cls = Object::cast_to<GDScriptNativeClass>(obj); - if (cls) { - txt += cls->get_name(); - txt += " (class)"; - } else { - txt = obj->get_class(); - if (obj->get_script_instance()) { - txt += "(" + obj->get_script_instance()->get_script()->get_path() + ")"; - } - } - } - } else { - txt = p_variant; - } - return txt; -} - -static String _disassemble_address(const GDScript *p_script, const GDScriptFunction &p_function, int p_address) { - int addr = p_address & GDScriptFunction::ADDR_MASK; - - switch (p_address >> GDScriptFunction::ADDR_BITS) { - case GDScriptFunction::ADDR_TYPE_SELF: { - return "self"; - } break; - case GDScriptFunction::ADDR_TYPE_CLASS: { - return "class"; - } break; - case GDScriptFunction::ADDR_TYPE_MEMBER: { - return "member(" + p_script->debug_get_member_by_index(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT: { - return "class_const(" + p_function.get_global_name(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT: { - return "const(" + _get_variant_string(p_function.get_constant(addr)) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_STACK: { - return "stack(" + itos(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_STACK_VARIABLE: { - return "var_stack(" + itos(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_GLOBAL: { - return "global(" + _get_variant_string(GDScriptLanguage::get_singleton()->get_global_array()[addr]) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL: { - return "named_global(" + p_function.get_global_name(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_NIL: { - return "nil"; - } break; - } - - return "<err>"; -} - -void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { -#define DADDR(m_ip) (_disassemble_address(_script, *this, _code_ptr[ip + m_ip])) - - for (int ip = 0; ip < _code_size;) { - StringBuilder text; - int incr = 0; - - text += " "; - text += itos(ip); - text += ": "; - - // This makes the compiler complain if some opcode is unchecked in the switch. - Opcode code = Opcode(_code_ptr[ip]); - - switch (code) { - case OPCODE_OPERATOR: { - int operation = _code_ptr[ip + 1]; - - text += "operator "; - - text += DADDR(4); - text += " = "; - text += DADDR(2); - text += " "; - text += Variant::get_operator_name(Variant::Operator(operation)); - text += " "; - text += DADDR(3); - - incr += 5; - } break; - case OPCODE_EXTENDS_TEST: { - text += "is object "; - text += DADDR(3); - text += " = "; - text += DADDR(1); - text += " is "; - text += DADDR(2); - - incr += 4; - } break; - case OPCODE_IS_BUILTIN: { - text += "is builtin "; - text += DADDR(3); - text += " = "; - text += DADDR(1); - text += " is "; - text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 2])); - - incr += 4; - } break; - case OPCODE_SET: { - text += "set "; - text += DADDR(1); - text += "["; - text += DADDR(2); - text += "] = "; - text += DADDR(3); - - incr += 4; - } break; - case OPCODE_GET: { - text += "get "; - text += DADDR(3); - text += " = "; - text += DADDR(1); - text += "["; - text += DADDR(2); - text += "]"; - - incr += 4; - } break; - case OPCODE_SET_NAMED: { - text += "set_named "; - text += DADDR(1); - text += "[\""; - text += _global_names_ptr[_code_ptr[ip + 2]]; - text += "\"] = "; - text += DADDR(3); - - incr += 4; - } break; - case OPCODE_GET_NAMED: { - text += "get_named "; - text += DADDR(3); - text += " = "; - text += DADDR(1); - text += "[\""; - text += _global_names_ptr[_code_ptr[ip + 2]]; - text += "\"]"; - - incr += 4; - } break; - case OPCODE_SET_MEMBER: { - text += "set_member "; - text += "[\""; - text += _global_names_ptr[_code_ptr[ip + 1]]; - text += "\"] = "; - text += DADDR(2); - - incr += 3; - } break; - case OPCODE_GET_MEMBER: { - text += "get_member "; - text += DADDR(2); - text += " = "; - text += "[\""; - text += _global_names_ptr[_code_ptr[ip + 1]]; - text += "\"]"; - - incr += 3; - } break; - case OPCODE_ASSIGN: { - text += "assign "; - text += DADDR(1); - text += " = "; - text += DADDR(2); - - incr += 3; - } break; - case OPCODE_ASSIGN_TRUE: { - text += "assign "; - text += DADDR(1); - text += " = true"; - - incr += 2; - } break; - case OPCODE_ASSIGN_FALSE: { - text += "assign "; - text += DADDR(1); - text += " = false"; - - incr += 2; - } break; - case OPCODE_ASSIGN_TYPED_BUILTIN: { - text += "assign typed builtin ("; - text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 1]); - text += ") "; - text += DADDR(2); - text += " = "; - text += DADDR(3); - - incr += 4; - } break; - case OPCODE_ASSIGN_TYPED_NATIVE: { - Variant class_name = _constants_ptr[_code_ptr[ip + 1]]; - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(class_name.operator Object *()); - - text += "assign typed native ("; - text += nc->get_name().operator String(); - text += ") "; - text += DADDR(2); - text += " = "; - text += DADDR(3); - - incr += 4; - } break; - case OPCODE_ASSIGN_TYPED_SCRIPT: { - Variant script = _constants_ptr[_code_ptr[ip + 1]]; - Script *sc = Object::cast_to<Script>(script.operator Object *()); - - text += "assign typed script ("; - text += sc->get_path(); - text += ") "; - text += DADDR(2); - text += " = "; - text += DADDR(3); - - incr += 4; - } break; - case OPCODE_CAST_TO_BUILTIN: { - text += "cast builtin "; - text += DADDR(3); - text += " = "; - text += DADDR(2); - text += " as "; - text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 1])); - - incr += 4; - } break; - case OPCODE_CAST_TO_NATIVE: { - Variant class_name = _constants_ptr[_code_ptr[ip + 1]]; - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(class_name.operator Object *()); - - text += "cast native "; - text += DADDR(3); - text += " = "; - text += DADDR(2); - text += " as "; - text += nc->get_name(); - - incr += 4; - } break; - case OPCODE_CAST_TO_SCRIPT: { - text += "cast "; - text += DADDR(3); - text += " = "; - text += DADDR(2); - text += " as "; - text += DADDR(1); - - incr += 4; - } break; - case OPCODE_CONSTRUCT: { - Variant::Type t = Variant::Type(_code_ptr[ip + 1]); - int argc = _code_ptr[ip + 2]; - - text += "construct "; - text += DADDR(3 + argc); - text += " = "; - - text += Variant::get_type_name(t) + "("; - for (int i = 0; i < argc; i++) { - if (i > 0) - text += ", "; - text += DADDR(i + 3); - } - text += ")"; - - incr = 4 + argc; - } break; - case OPCODE_CONSTRUCT_ARRAY: { - int argc = _code_ptr[ip + 1]; - text += " make_array "; - text += DADDR(2 + argc); - text += " = ["; - - for (int i = 0; i < argc; i++) { - if (i > 0) - text += ", "; - text += DADDR(2 + i); - } - - text += "]"; - - incr += 3 + argc; - } break; - case OPCODE_CONSTRUCT_DICTIONARY: { - int argc = _code_ptr[ip + 1]; - text += "make_dict "; - text += DADDR(2 + argc * 2); - text += " = {"; - - for (int i = 0; i < argc; i++) { - if (i > 0) - text += ", "; - text += DADDR(2 + i * 2 + 0); - text += ": "; - text += DADDR(2 + i * 2 + 1); - } - - text += "}"; - - incr += 3 + argc * 2; - } break; - case OPCODE_CALL: - case OPCODE_CALL_RETURN: - case OPCODE_CALL_ASYNC: { - bool ret = _code_ptr[ip] == OPCODE_CALL_RETURN; - bool async = _code_ptr[ip] == OPCODE_CALL_ASYNC; - - if (ret) { - text += "call-ret "; - } else if (async) { - text += "call-async "; - } else { - text += "call "; - } - - int argc = _code_ptr[ip + 1]; - if (ret || async) { - text += DADDR(4 + argc) + " = "; - } - - text += DADDR(2) + "."; - text += String(_global_names_ptr[_code_ptr[ip + 3]]); - text += "("; - - for (int i = 0; i < argc; i++) { - if (i > 0) - text += ", "; - text += DADDR(4 + i); - } - text += ")"; - - incr = 5 + argc; - } break; - case OPCODE_CALL_BUILT_IN: { - text += "call-built-in "; - - int argc = _code_ptr[ip + 2]; - text += DADDR(3 + argc) + " = "; - - text += GDScriptFunctions::get_func_name(GDScriptFunctions::Function(_code_ptr[ip + 1])); - text += "("; - - for (int i = 0; i < argc; i++) { - if (i > 0) - text += ", "; - text += DADDR(3 + i); - } - text += ")"; - - incr = 4 + argc; - } break; - case OPCODE_CALL_SELF_BASE: { - text += "call-self-base "; - - int argc = _code_ptr[ip + 2]; - text += DADDR(3 + argc) + " = "; - - text += _global_names_ptr[_code_ptr[ip + 1]]; - text += "("; - - for (int i = 0; i < argc; i++) { - if (i > 0) - text += ", "; - text += DADDR(3 + i); - } - text += ")"; - - incr = 4 + argc; - } break; - case OPCODE_AWAIT: { - text += "await "; - text += DADDR(1); - - incr += 2; - } break; - case OPCODE_AWAIT_RESUME: { - text += "await resume "; - text += DADDR(1); - - incr = 2; - } break; - case OPCODE_JUMP: { - text += "jump "; - text += itos(_code_ptr[ip + 1]); - - incr = 2; - } break; - case OPCODE_JUMP_IF: { - text += "jump-if "; - text += DADDR(1); - text += " to "; - text += itos(_code_ptr[ip + 2]); - - incr = 3; - } break; - case OPCODE_JUMP_IF_NOT: { - text += "jump-if-not "; - text += DADDR(1); - text += " to "; - text += itos(_code_ptr[ip + 2]); - - incr = 3; - } break; - case OPCODE_JUMP_TO_DEF_ARGUMENT: { - text += "jump-to-default-argument "; - - incr = 1; - } break; - case OPCODE_RETURN: { - text += "return "; - text += DADDR(1); - - incr = 2; - } break; - case OPCODE_ITERATE_BEGIN: { - text += "for-init "; - text += DADDR(4); - text += " in "; - text += DADDR(2); - text += " counter "; - text += DADDR(1); - text += " end "; - text += itos(_code_ptr[ip + 3]); - - incr += 5; - } break; - case OPCODE_ITERATE: { - text += "for-loop "; - text += DADDR(4); - text += " in "; - text += DADDR(2); - text += " counter "; - text += DADDR(1); - text += " end "; - text += itos(_code_ptr[ip + 3]); - - incr += 5; - } break; - case OPCODE_LINE: { - int line = _code_ptr[ip + 1] - 1; - if (line >= 0 && line < p_code_lines.size()) { - text += "line "; - text += itos(line + 1); - text += ": "; - text += p_code_lines[line]; - } else { - text += ""; - } - - incr += 2; - } break; - case OPCODE_ASSERT: { - text += "assert ("; - text += DADDR(1); - text += ", "; - text += DADDR(2); - text += ")"; - - incr += 3; - } break; - case OPCODE_BREAKPOINT: { - text += "breakpoint"; - - incr += 1; - } break; - case OPCODE_END: { - text += "== END =="; - - incr += 1; - } break; - } - - ip += incr; - if (text.get_string_length() > 0) { - print_line(text.as_string()); - } - } -} -#endif diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 50eadcaf86..bb5cc1284d 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -159,12 +159,19 @@ class GDScriptFunction { public: enum Opcode { OPCODE_OPERATOR, + OPCODE_OPERATOR_VALIDATED, OPCODE_EXTENDS_TEST, OPCODE_IS_BUILTIN, - OPCODE_SET, - OPCODE_GET, + 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, @@ -176,14 +183,54 @@ public: OPCODE_CAST_TO_BUILTIN, OPCODE_CAST_TO_NATIVE, OPCODE_CAST_TO_SCRIPT, - OPCODE_CONSTRUCT, //only for basic types!! + OPCODE_CONSTRUCT, // Only for basic types! + OPCODE_CONSTRUCT_VALIDATED, // Only for basic types! OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, OPCODE_CALL_ASYNC, OPCODE_CALL_BUILT_IN, + OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 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, @@ -192,7 +239,45 @@ public: OPCODE_JUMP_TO_DEF_ARGUMENT, OPCODE_RETURN, OPCODE_ITERATE_BEGIN, + OPCODE_ITERATE_BEGIN_INT, + OPCODE_ITERATE_BEGIN_FLOAT, + OPCODE_ITERATE_BEGIN_VECTOR2, + OPCODE_ITERATE_BEGIN_VECTOR2I, + OPCODE_ITERATE_BEGIN_VECTOR3, + OPCODE_ITERATE_BEGIN_VECTOR3I, + OPCODE_ITERATE_BEGIN_STRING, + OPCODE_ITERATE_BEGIN_DICTIONARY, + OPCODE_ITERATE_BEGIN_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, + OPCODE_ITERATE_BEGIN_OBJECT, OPCODE_ITERATE, + OPCODE_ITERATE_INT, + OPCODE_ITERATE_FLOAT, + OPCODE_ITERATE_VECTOR2, + OPCODE_ITERATE_VECTOR2I, + OPCODE_ITERATE_VECTOR3, + OPCODE_ITERATE_VECTOR3I, + OPCODE_ITERATE_STRING, + OPCODE_ITERATE_DICTIONARY, + OPCODE_ITERATE_ARRAY, + OPCODE_ITERATE_PACKED_BYTE_ARRAY, + OPCODE_ITERATE_PACKED_INT32_ARRAY, + OPCODE_ITERATE_PACKED_INT64_ARRAY, + OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, + OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, + OPCODE_ITERATE_PACKED_STRING_ARRAY, + OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, + OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, + OPCODE_ITERATE_PACKED_COLOR_ARRAY, + OPCODE_ITERATE_OBJECT, OPCODE_ASSERT, OPCODE_BREAKPOINT, OPCODE_LINE, @@ -215,6 +300,12 @@ public: ADDR_TYPE_NIL = 9 }; + enum Instruction { + INSTR_BITS = 20, + INSTR_MASK = ((1 << INSTR_BITS) - 1), + INSTR_ARGS_MASK = ~INSTR_MASK, + }; + struct StackDebug { int line; int pos; @@ -229,27 +320,59 @@ private: StringName source; mutable Variant nil; - mutable Variant *_constants_ptr; - int _constant_count; - const StringName *_global_names_ptr; - int _global_names_count; - const int *_default_arg_ptr; - int _default_arg_count; - const int *_code_ptr; - int _code_size; - int _argument_count; - int _stack_size; - int _call_size; - int _initial_line; - bool _static; - MultiplayerAPI::RPCMode rpc_mode; - - GDScript *_script; + mutable Variant *_constants_ptr = nullptr; + int _constant_count = 0; + const StringName *_global_names_ptr = nullptr; + int _global_names_count = 0; + const int *_default_arg_ptr = nullptr; + int _default_arg_count = 0; + int _operator_funcs_count = 0; + const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr; + int _setters_count = 0; + const Variant::ValidatedSetter *_setters_ptr = nullptr; + int _getters_count = 0; + const Variant::ValidatedGetter *_getters_ptr = nullptr; + int _keyed_setters_count = 0; + const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr; + int _keyed_getters_count = 0; + const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr; + int _indexed_setters_count = 0; + const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr; + int _indexed_getters_count = 0; + const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr; + int _builtin_methods_count = 0; + const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr; + int _constructors_count = 0; + const Variant::ValidatedConstructor *_constructors_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; + + GDScript *_script = nullptr; StringName name; Vector<Variant> constants; Vector<StringName> global_names; Vector<int> default_arguments; + Vector<Variant::ValidatedOperatorEvaluator> operator_funcs; + Vector<Variant::ValidatedSetter> setters; + Vector<Variant::ValidatedGetter> getters; + Vector<Variant::ValidatedKeyedSetter> keyed_setters; + Vector<Variant::ValidatedKeyedGetter> keyed_getters; + Vector<Variant::ValidatedIndexedSetter> indexed_setters; + Vector<Variant::ValidatedIndexedGetter> indexed_getters; + Vector<Variant::ValidatedBuiltInMethod> builtin_methods; + Vector<Variant::ValidatedConstructor> constructors; + Vector<MethodBind *> methods; Vector<int> code; Vector<GDScriptDataType> argument_types; GDScriptDataType return_type; @@ -265,22 +388,22 @@ private: friend class GDScriptLanguage; - SelfList<GDScriptFunction> function_list; + SelfList<GDScriptFunction> function_list{ this }; #ifdef DEBUG_ENABLED CharString func_cname; - const char *_func_cname; + const char *_func_cname = nullptr; struct Profile { StringName signature; - uint64_t call_count; - uint64_t self_time; - uint64_t total_time; - uint64_t frame_call_count; - uint64_t frame_self_time; - uint64_t frame_total_time; - uint64_t last_frame_call_count; - uint64_t last_frame_self_time; - uint64_t last_frame_total_time; + uint64_t call_count = 0; + uint64_t self_time = 0; + uint64_t total_time = 0; + uint64_t frame_call_count = 0; + uint64_t frame_self_time = 0; + uint64_t frame_total_time = 0; + uint64_t last_frame_call_count = 0; + uint64_t last_frame_self_time = 0; + uint64_t last_frame_total_time = 0; } profile; #endif diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp new file mode 100644 index 0000000000..7c8bfcd944 --- /dev/null +++ b/modules/gdscript/gdscript_vm.cpp @@ -0,0 +1,2863 @@ +/*************************************************************************/ +/* gdscript_vm.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_function.h" + +#include "core/core_string_names.h" +#include "core/os/os.h" +#include "gdscript.h" +#include "gdscript_functions.h" + +Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const { + int address = p_address & ADDR_MASK; + + //sequential table (jump table generated by compiler) + switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) { + case ADDR_TYPE_SELF: { +#ifdef DEBUG_ENABLED + if (unlikely(!p_instance)) { + r_error = "Cannot access self without instance."; + return nullptr; + } +#endif + return &self; + } break; + case ADDR_TYPE_CLASS: { + return &static_ref; + } break; + case ADDR_TYPE_MEMBER: { +#ifdef DEBUG_ENABLED + if (unlikely(!p_instance)) { + r_error = "Cannot access member without instance."; + return nullptr; + } +#endif + //member indexing is O(1) + return &p_instance->members.write[address]; + } break; + case ADDR_TYPE_CLASS_CONSTANT: { + //todo change to index! + GDScript *s = p_script; +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); +#endif + const StringName *sn = &_global_names_ptr[address]; + + while (s) { + GDScript *o = s; + while (o) { + Map<StringName, Variant>::Element *E = o->constants.find(*sn); + if (E) { + return &E->get(); + } + o = o->_owner; + } + s = s->_base; + } + + ERR_FAIL_V_MSG(nullptr, "GDScriptCompiler bug."); + } break; + case ADDR_TYPE_LOCAL_CONSTANT: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _constant_count, nullptr); +#endif + return &_constants_ptr[address]; + } break; + case ADDR_TYPE_STACK: + case ADDR_TYPE_STACK_VARIABLE: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _stack_size, nullptr); +#endif + return &p_stack[address]; + } break; + case ADDR_TYPE_GLOBAL: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), nullptr); +#endif + return &GDScriptLanguage::get_singleton()->get_global_array()[address]; + } break; +#ifdef TOOLS_ENABLED + case ADDR_TYPE_NAMED_GLOBAL: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); +#endif + StringName id = _global_names_ptr[address]; + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { + return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; + } else { + r_error = "Autoload singleton '" + String(id) + "' has been removed."; + return nullptr; + } + } break; +#endif + case ADDR_TYPE_NIL: { + return &nil; + } break; + } + + ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode)."); + return nullptr; +} + +#ifdef DEBUG_ENABLED +static String _get_var_type(const Variant *p_var) { + String basestr; + + if (p_var->get_type() == Variant::OBJECT) { + bool was_freed; + Object *bobj = p_var->get_validated_object_with_check(was_freed); + if (!bobj) { + if (was_freed) { + basestr = "null instance"; + } else { + basestr = "previously freed"; + } + } else { + if (bobj->get_script_instance()) { + basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; + } else { + basestr = bobj->get_class(); + } + } + + } else { + basestr = Variant::get_type_name(p_var->get_type()); + } + + return basestr; +} +#endif // DEBUG_ENABLED + +String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const { + String err_text; + + if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { + int errorarg = p_err.argument; + // Handle the Object to Object case separately as we don't have further class details. +#ifdef DEBUG_ENABLED + if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { + err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; + } else +#endif // DEBUG_ENABLED + { + err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; + } + } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { + err_text = "Invalid call. Nonexistent " + p_where + "."; + } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { + err_text = "Attempt to call " + p_where + " on a null instance."; + } else { + err_text = "Bug, call error: #" + itos(p_err.error); + } + + return err_text; +} + +#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_VALIDATED, \ + &&OPCODE_CONSTRUCT_ARRAY, \ + &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_CALL, \ + &&OPCODE_CALL_RETURN, \ + &&OPCODE_CALL_ASYNC, \ + &&OPCODE_CALL_BUILT_IN, \ + &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ + &&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_BEGIN_INT, \ + &&OPCODE_ITERATE_BEGIN_FLOAT, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2I, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3I, \ + &&OPCODE_ITERATE_BEGIN_STRING, \ + &&OPCODE_ITERATE_BEGIN_DICTIONARY, \ + &&OPCODE_ITERATE_BEGIN_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_OBJECT, \ + &&OPCODE_ITERATE, \ + &&OPCODE_ITERATE_INT, \ + &&OPCODE_ITERATE_FLOAT, \ + &&OPCODE_ITERATE_VECTOR2, \ + &&OPCODE_ITERATE_VECTOR2I, \ + &&OPCODE_ITERATE_VECTOR3, \ + &&OPCODE_ITERATE_VECTOR3I, \ + &&OPCODE_ITERATE_STRING, \ + &&OPCODE_ITERATE_DICTIONARY, \ + &&OPCODE_ITERATE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_OBJECT, \ + &&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) \ + m_op: +#define OPCODE_WHILE(m_test) \ + OPSWHILE: +#define OPCODES_END \ + OPSEXIT: +#define OPCODES_OUT \ + OPSOUT: +#define DISPATCH_OPCODE goto OPSWHILE +#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test]; +#define OPCODE_BREAK goto OPSEXIT +#define OPCODE_OUT goto OPSOUT +#else +#define OPCODES_TABLE +#define OPCODE(m_op) case m_op: +#define OPCODE_WHILE(m_test) while (m_test) +#define OPCODES_END +#define OPCODES_OUT +#define DISPATCH_OPCODE continue +#define OPCODE_SWITCH(m_test) switch (m_test) +#define OPCODE_BREAK break +#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; + + if (!_code_ptr) { + return Variant(); + } + + r_err.error = Callable::CallError::CALL_OK; + + Variant self; + Variant static_ref; + Variant retvalue; + Variant *stack = nullptr; + Variant **instruction_args; + const void **call_args_ptr = nullptr; + int defarg = 0; + +#ifdef DEBUG_ENABLED + + //GDScriptLanguage::get_singleton()->calls++; + +#endif + + uint32_t alloca_size = 0; + GDScript *script; + int ip = 0; + int line = _initial_line; + + if (p_state) { + //use existing (supplied) state (awaited) + stack = (Variant *)p_state->stack.ptr(); + instruction_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check + line = p_state->line; + ip = p_state->ip; + alloca_size = p_state->stack.size(); + script = p_state->script; + p_instance = p_state->instance; + defarg = p_state->defarg; + self = p_state->self; + + } else { + if (p_argcount != _argument_count) { + if (p_argcount > _argument_count) { + r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_err.argument = _argument_count; + + return Variant(); + } else if (p_argcount < _argument_count - _default_arg_count) { + r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_err.argument = _argument_count - _default_arg_count; + return Variant(); + } else { + defarg = _argument_count - p_argcount; + } + } + + alloca_size = sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size; + + if (alloca_size) { + uint8_t *aptr = (uint8_t *)alloca(alloca_size); + + if (_stack_size) { + stack = (Variant *)aptr; + for (int i = 0; i < p_argcount; i++) { + if (!argument_types[i].has_type) { + memnew_placement(&stack[i], Variant(*p_args[i])); + continue; + } + + if (!argument_types[i].is_type(*p_args[i], true)) { + r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_err.argument = i; + r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; + return Variant(); + } + if (argument_types[i].kind == GDScriptDataType::BUILTIN) { + Variant arg; + Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err); + memnew_placement(&stack[i], Variant(arg)); + } else { + memnew_placement(&stack[i], Variant(*p_args[i])); + } + } + for (int i = p_argcount; i < _stack_size; i++) { + memnew_placement(&stack[i], Variant); + } + } else { + stack = nullptr; + } + + if (_instruction_args_size) { + instruction_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; + } else { + instruction_args = nullptr; + } + + } else { + stack = nullptr; + instruction_args = nullptr; + } + + if (p_instance) { + if (p_instance->base_ref && static_cast<Reference *>(p_instance->owner)->is_referenced()) { + self = REF(static_cast<Reference *>(p_instance->owner)); + } else { + self = p_instance->owner; + } + script = p_instance->script.ptr(); + } else { + 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; + + String err_text; + +#ifdef DEBUG_ENABLED + + if (EngineDebugger::is_active()) { + GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line); + } + +#define GD_ERR_BREAK(m_cond) \ + { \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \ + OPCODE_BREAK; \ + } \ + } + +#define CHECK_SPACE(m_space) \ + GD_ERR_BREAK((ip + m_space) > _code_size) + +#define GET_VARIANT_PTR(m_v, m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); \ + if (unlikely(!m_v)) \ + OPCODE_BREAK; + +#else +#define GD_ERR_BREAK(m_cond) +#define CHECK_SPACE(m_space) +#define GET_VARIANT_PTR(m_v, m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); + +#endif + +#define GET_INSTRUCTION_ARG(m_v, m_idx) \ + Variant *m_v = instruction_args[m_idx] + +#ifdef DEBUG_ENABLED + + uint64_t function_start_time = 0; + uint64_t function_call_time = 0; + + if (GDScriptLanguage::get_singleton()->profiling) { + function_start_time = OS::get_singleton()->get_ticks_usec(); + function_call_time = 0; + profile.call_count++; + profile.frame_call_count++; + } + bool exit_ok = false; + bool awaited = false; +#endif + +#ifdef DEBUG_ENABLED + OPCODE_WHILE(ip < _code_size) { + int last_opcode = _code_ptr[ip]; +#else + OPCODE_WHILE(true) { +#endif + // Load arguments for the instruction before each instruction. + int instr_arg_count = ((_code_ptr[ip]) & INSTR_ARGS_MASK) >> INSTR_BITS; + for (int i = 0; i < instr_arg_count; i++) { + GET_VARIANT_PTR(v, i + 1); + instruction_args[i] = v; + } + + OPCODE_SWITCH(_code_ptr[ip] & INSTR_MASK) { + OPCODE(OPCODE_OPERATOR) { + CHECK_SPACE(5); + + bool valid; + Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4]; + GD_ERR_BREAK(op >= Variant::OP_MAX); + + GET_INSTRUCTION_ARG(a, 0); + GET_INSTRUCTION_ARG(b, 1); + GET_INSTRUCTION_ARG(dst, 2); + +#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 += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_OPERATOR_VALIDATED) { + CHECK_SPACE(5); + + int operator_idx = _code_ptr[ip + 4]; + GD_ERR_BREAK(operator_idx < 0 || operator_idx >= _operator_funcs_count); + Variant::ValidatedOperatorEvaluator operator_func = _operator_funcs_ptr[operator_idx]; + + GET_INSTRUCTION_ARG(a, 0); + GET_INSTRUCTION_ARG(b, 1); + GET_INSTRUCTION_ARG(dst, 2); + + operator_func(a, b, dst); + + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_EXTENDS_TEST) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(a, 0); + GET_INSTRUCTION_ARG(b, 1); + GET_INSTRUCTION_ARG(dst, 2); + +#ifdef DEBUG_ENABLED + if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) { + err_text = "Right operand of 'is' is not a class."; + OPCODE_BREAK; + } +#endif + + bool extends_ok = false; + if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) { +#ifdef DEBUG_ENABLED + bool was_freed; + Object *obj_A = a->get_validated_object_with_check(was_freed); + + if (was_freed) { + err_text = "Left operand of 'is' is a previously freed instance."; + OPCODE_BREAK; + } + + Object *obj_B = b->get_validated_object_with_check(was_freed); + + if (was_freed) { + err_text = "Right operand of 'is' is a previously freed instance."; + OPCODE_BREAK; + } +#else + + Object *obj_A = *a; + Object *obj_B = *b; +#endif // DEBUG_ENABLED + + GDScript *scr_B = Object::cast_to<GDScript>(obj_B); + + if (scr_B) { + //if B is a script, the only valid condition is that A has an instance which inherits from the script + //in other situation, this shoul return false. + + if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { + GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); + //bool found=false; + while (cmp) { + if (cmp == scr_B) { + //inherits from script, all ok + extends_ok = true; + break; + } + + cmp = cmp->_base; + } + } + + } else { + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); + +#ifdef DEBUG_ENABLED + if (!nc) { + err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "')."; + OPCODE_BREAK; + } +#endif + extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name()); + } + } + + *dst = extends_ok; + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_IS_BUILTIN) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(value, 0); + GET_INSTRUCTION_ARG(dst, 1); + Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; + + GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); + + *dst = value->get_type() == var_type; + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_KEYED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(value, 2); + + bool valid; + dst->set(*index, *value, &valid); + +#ifdef DEBUG_ENABLED + if (!valid) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; + OPCODE_BREAK; + } +#endif + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_KEYED_VALIDATED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(value, 2); + + int index_setter = _code_ptr[ip + 4]; + GD_ERR_BREAK(index_setter < 0 || index_setter >= _keyed_setters_count); + const Variant::ValidatedKeyedSetter setter = _keyed_setters_ptr[index_setter]; + + bool valid; + setter(dst, index, value, valid); + +#ifdef DEBUG_ENABLED + if (!valid) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; + OPCODE_BREAK; + } +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_INDEXED_VALIDATED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(value, 2); + + int index_setter = _code_ptr[ip + 4]; + GD_ERR_BREAK(index_setter < 0 || index_setter >= _indexed_setters_count); + const Variant::ValidatedIndexedSetter setter = _indexed_setters_ptr[index_setter]; + + int64_t int_index = *VariantInternal::get_int(index); + + bool oob; + setter(dst, int_index, value, oob); + +#ifdef DEBUG_ENABLED + if (oob) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Out of bounds set index " + v + " (on base: '" + _get_var_type(dst) + "')"; + OPCODE_BREAK; + } +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_KEYED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(dst, 2); + + bool valid; +#ifdef DEBUG_ENABLED + // Allow better error message in cases where src and dst are the same stack position. + Variant ret = src->get(*index, &valid); +#else + *dst = src->get(*index, &valid); + +#endif +#ifdef DEBUG_ENABLED + if (!valid) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; + OPCODE_BREAK; + } + *dst = ret; +#endif + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_KEYED_VALIDATED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(key, 1); + GET_INSTRUCTION_ARG(dst, 2); + + int index_getter = _code_ptr[ip + 4]; + GD_ERR_BREAK(index_getter < 0 || index_getter >= _keyed_getters_count); + const Variant::ValidatedKeyedGetter getter = _keyed_getters_ptr[index_getter]; + + bool valid; +#ifdef DEBUG_ENABLED + // Allow better error message in cases where src and dst are the same stack position. + Variant ret; + getter(src, key, &ret, valid); +#else + getter(src, key, dst, valid); +#endif +#ifdef DEBUG_ENABLED + if (!valid) { + String v = key->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(key) + "'"; + } + err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; + OPCODE_BREAK; + } + *dst = ret; +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_INDEXED_VALIDATED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(dst, 2); + + int index_getter = _code_ptr[ip + 4]; + GD_ERR_BREAK(index_getter < 0 || index_getter >= _indexed_getters_count); + const Variant::ValidatedIndexedGetter getter = _indexed_getters_ptr[index_getter]; + + int64_t int_index = *VariantInternal::get_int(index); + + bool oob; + getter(src, int_index, dst, oob); + +#ifdef DEBUG_ENABLED + if (oob) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Out of bounds get index " + v + " (on base: '" + _get_var_type(src) + "')"; + OPCODE_BREAK; + } +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_NAMED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(value, 1); + + int indexname = _code_ptr[ip + 3]; + + GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; + dst->set_named(*index, *value, valid); + +#ifdef DEBUG_ENABLED + if (!valid) { + String err_type; + err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + OPCODE_BREAK; + } +#endif + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_NAMED_VALIDATED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(value, 1); + + int index_setter = _code_ptr[ip + 3]; + GD_ERR_BREAK(index_setter < 0 || index_setter >= _setters_count); + const Variant::ValidatedSetter setter = _setters_ptr[index_setter]; + + setter(dst, value); + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_NAMED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + + int indexname = _code_ptr[ip + 3]; + + GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; +#ifdef DEBUG_ENABLED + //allow better error message in cases where src and dst are the same stack position + Variant ret = src->get_named(*index, valid); + +#else + *dst = src->get_named(*index, valid); +#endif +#ifdef DEBUG_ENABLED + if (!valid) { + if (src->has_method(*index)) { + err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "'). Did you mean '." + index->operator String() + "()' or funcref(obj, \"" + index->operator String() + "\") ?"; + } else { + err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "')."; + } + OPCODE_BREAK; + } + *dst = ret; +#endif + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_NAMED_VALIDATED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + + int index_getter = _code_ptr[ip + 3]; + GD_ERR_BREAK(index_getter < 0 || index_getter >= _getters_count); + const Variant::ValidatedGetter getter = _getters_ptr[index_getter]; + + getter(src, dst); + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_MEMBER) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(src, 0); + int indexname = _code_ptr[ip + 2]; + GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; +#ifndef DEBUG_ENABLED + ClassDB::set_property(p_instance->owner, *index, *src, &valid); +#else + bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid); + if (!ok) { + err_text = "Internal error setting property: " + String(*index); + OPCODE_BREAK; + } else if (!valid) { + err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + "."; + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_MEMBER) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(dst, 0); + int indexname = _code_ptr[ip + 2]; + GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); + const StringName *index = &_global_names_ptr[indexname]; +#ifndef DEBUG_ENABLED + ClassDB::get_property(p_instance->owner, *index, *dst); +#else + bool ok = ClassDB::get_property(p_instance->owner, *index, *dst); + if (!ok) { + err_text = "Internal error getting property: " + String(*index); + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + + *dst = *src; + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TRUE) { + CHECK_SPACE(2); + GET_INSTRUCTION_ARG(dst, 0); + + *dst = true; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_FALSE) { + CHECK_SPACE(2); + GET_INSTRUCTION_ARG(dst, 0); + + *dst = false; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + + Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; + GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); + + if (src->get_type() != var_type) { +#ifdef DEBUG_ENABLED + if (Variant::can_convert_strict(src->get_type(), var_type)) { +#endif // DEBUG_ENABLED + Callable::CallError ce; + Variant::construct(var_type, *dst, const_cast<const Variant **>(&src), 1, ce); + } else { +#ifdef DEBUG_ENABLED + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; + OPCODE_BREAK; + } + } else { +#endif // DEBUG_ENABLED + *dst = *src; + } + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + +#ifdef DEBUG_ENABLED + GET_INSTRUCTION_ARG(type, 2); + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); + GD_ERR_BREAK(!nc); + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + nc->get_name() + "'."; + OPCODE_BREAK; + } + Object *src_obj = src->operator Object *(); + + if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + err_text = "Trying to assign value of type '" + src_obj->get_class_name() + + "' to a variable of type '" + nc->get_name() + "'."; + OPCODE_BREAK; + } +#endif // DEBUG_ENABLED + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + +#ifdef DEBUG_ENABLED + GET_INSTRUCTION_ARG(type, 2); + Script *base_type = Object::cast_to<Script>(type->operator Object *()); + + GD_ERR_BREAK(!base_type); + + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + + if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { + ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + if (!scr_inst) { + err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + + "' to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + + Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + bool valid = false; + + while (src_type) { + if (src_type == base_type) { + valid = true; + break; + } + src_type = src_type->get_base_script().ptr(); + } + + if (!valid) { + err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + + "' to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + } +#endif // DEBUG_ENABLED + + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_BUILTIN) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + Variant::Type to_type = (Variant::Type)_code_ptr[ip + 3]; + + GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); + + Callable::CallError err; + Variant::construct(to_type, *dst, (const Variant **)&src, 1, err); + +#ifdef DEBUG_ENABLED + if (err.error != Callable::CallError::CALL_OK) { + err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'."; + OPCODE_BREAK; + } +#endif + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_NATIVE) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + GET_INSTRUCTION_ARG(to_type, 2); + + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *()); + GD_ERR_BREAK(!nc); + +#ifdef DEBUG_ENABLED + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Invalid cast: can't convert a non-object value to an object type."; + OPCODE_BREAK; + } +#endif + Object *src_obj = src->operator Object *(); + + if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + *dst = Variant(); // invalid cast, assign NULL + } else { + *dst = *src; + } + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_SCRIPT) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + GET_INSTRUCTION_ARG(to_type, 2); + + Script *base_type = Object::cast_to<Script>(to_type->operator Object *()); + + GD_ERR_BREAK(!base_type); + +#ifdef DEBUG_ENABLED + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } +#endif + + bool valid = false; + + if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { + ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + + if (scr_inst) { + Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + + while (src_type) { + if (src_type == base_type) { + valid = true; + break; + } + src_type = src_type->get_base_script().ptr(); + } + } + } + + if (valid) { + *dst = *src; // Valid cast, copy the source object + } else { + *dst = Variant(); // invalid cast, assign NULL + } + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + Variant::Type t = Variant::Type(_code_ptr[ip + 2]); + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + Callable::CallError err; + Variant::construct(t, *dst, (const Variant **)argptrs, argc, err); + +#ifdef DEBUG_ENABLED + if (err.error != Callable::CallError::CALL_OK) { + err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs); + OPCODE_BREAK; + } +#endif + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT_VALIDATED) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + int constructor_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(constructor_idx < 0 || constructor_idx >= _constructors_count); + Variant::ValidatedConstructor constructor = _constructors_ptr[constructor_idx]; + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + constructor(*dst, (const Variant **)argptrs); + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT_ARRAY) { + CHECK_SPACE(1 + instr_arg_count); + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + Array array; + array.resize(argc); + + for (int i = 0; i < argc; i++) { + array[i] = *(instruction_args[i]); + } + + GET_INSTRUCTION_ARG(dst, argc); + + *dst = array; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + Dictionary dict; + + for (int i = 0; i < argc; i++) { + GET_INSTRUCTION_ARG(k, i * 2 + 0); + GET_INSTRUCTION_ARG(v, i * 2 + 1); + dict[*k] = *v; + } + + GET_INSTRUCTION_ARG(dst, argc * 2); + + *dst = dict; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_ASYNC) + OPCODE(OPCODE_CALL_RETURN) + OPCODE(OPCODE_CALL) { + CHECK_SPACE(3 + instr_arg_count); + bool call_ret = (_code_ptr[ip] & INSTR_MASK) != OPCODE_CALL; +#ifdef DEBUG_ENABLED + bool call_async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC; +#endif + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + int methodname_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(methodname_idx < 0 || methodname_idx >= _global_names_count); + const StringName *methodname = &_global_names_ptr[methodname_idx]; + + GET_INSTRUCTION_ARG(base, argc); + 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); + base->call(*methodname, (const Variant **)argptrs, argc, *ret, err); +#ifdef DEBUG_ENABLED + if (!call_async && ret->get_type() == Variant::OBJECT) { + // Check if getting a function state without await. + bool was_freed = false; + Object *obj = ret->get_validated_object_with_check(was_freed); + + if (was_freed) { + err_text = "Got a freed object as a result of the call."; + OPCODE_BREAK; + } + if (obj && obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { + err_text = R"(Trying to call an async function without "await".)"; + OPCODE_BREAK; + } + } +#endif + } else { + Variant ret; + base->call(*methodname, (const Variant **)argptrs, argc, ret, 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 = *methodname; + 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; + } + } + } else if (methodstr == "call_recursive" && basestr == "TreeItem") { + if (argc >= 1) { + methodstr = String(*argptrs[0]) + " (via TreeItem.call_recursive)"; + if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { + err.argument += 1; + } + } + } + err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); + OPCODE_BREAK; + } +#endif + + ip += 3; + } + 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_BUILTIN_TYPE_VALIDATED) { + 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] >= _builtin_methods_count); + Variant::ValidatedBuiltInMethod method = _builtin_methods_ptr[_code_ptr[ip + 2]]; + 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 + + GET_INSTRUCTION_ARG(ret, argc + 1); + method(base, (const Variant **)argptrs, argc, ret); + +#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); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 2]); + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + Callable::CallError err; + GDScriptFunctions::call(func, (const Variant **)argptrs, argc, *dst, err); + +#ifdef DEBUG_ENABLED + if (err.error != Callable::CallError::CALL_OK) { + String methodstr = GDScriptFunctions::get_func_name(func); + if (dst->get_type() == Variant::STRING) { + //call provided error string + err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst); + } else { + err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs); + } + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_SELF_BASE) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int self_fun = _code_ptr[ip + 1]; +#ifdef DEBUG_ENABLED + if (self_fun < 0 || self_fun >= _global_names_count) { + err_text = "compiler bug, function name not found"; + OPCODE_BREAK; + } +#endif + const StringName *methodname = &_global_names_ptr[self_fun]; + + int argc = _code_ptr[ip + 2]; + GD_ERR_BREAK(argc < 0); + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + const GDScript *gds = _script; + + const Map<StringName, GDScriptFunction *>::Element *E = nullptr; + while (gds->base.ptr()) { + gds = gds->base.ptr(); + E = gds->member_functions.find(*methodname); + if (E) { + break; + } + } + + Callable::CallError err; + + if (E) { + *dst = E->get()->call(p_instance, (const Variant **)argptrs, argc, err); + } else if (gds->native.ptr()) { + if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { + MethodBind *mb = ClassDB::get_method(gds->native->get_name(), *methodname); + if (!mb) { + err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + } else { + *dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err); + } + } else { + err.error = Callable::CallError::CALL_OK; + } + } else { + if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { + err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + } else { + err.error = Callable::CallError::CALL_OK; + } + } + + if (err.error != Callable::CallError::CALL_OK) { + String methodstr = *methodname; + err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs); + + OPCODE_BREAK; + } + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_AWAIT) { + CHECK_SPACE(2); + + // Do the oneshot connect. + GET_INSTRUCTION_ARG(argobj, 0); + + Signal sig; + bool is_signal = true; + + { + Variant result = *argobj; + + if (argobj->get_type() == Variant::OBJECT) { + bool was_freed = false; + Object *obj = argobj->get_validated_object_with_check(was_freed); + + if (was_freed) { + err_text = "Trying to await on a freed object."; + OPCODE_BREAK; + } + + // Is this even possible to be null at this point? + if (obj) { + if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { + static StringName completed = _scs_create("completed"); + result = Signal(obj, completed); + } + } + } + + if (result.get_type() != Variant::SIGNAL) { + ip += 4; // Skip OPCODE_AWAIT_RESUME and its data. + // The stack pointer should be the same, so we don't need to set a return value. + is_signal = false; + } else { + sig = result; + } + } + + if (is_signal) { + Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState); + gdfs->function = this; + + gdfs->state.stack.resize(alloca_size); + //copy variant stack + for (int i = 0; i < _stack_size; i++) { + memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i])); + } + gdfs->state.stack_size = _stack_size; + gdfs->state.self = self; + gdfs->state.alloca_size = alloca_size; + gdfs->state.ip = ip + 2; + gdfs->state.line = line; + gdfs->state.script = _script; + { + MutexLock lock(GDScriptLanguage::get_singleton()->lock); + _script->pending_func_states.add(&gdfs->scripts_list); + if (p_instance) { + gdfs->state.instance = p_instance; + p_instance->pending_func_states.add(&gdfs->instances_list); + } else { + gdfs->state.instance = nullptr; + } + } +#ifdef DEBUG_ENABLED + gdfs->state.function_name = name; + gdfs->state.script_path = _script->get_path(); +#endif + gdfs->state.defarg = defarg; + gdfs->function = this; + + retvalue = gdfs; + + Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT); + if (err != OK) { + err_text = "Error connecting to signal: " + sig.get_name() + " during await."; + OPCODE_BREAK; + } + +#ifdef DEBUG_ENABLED + exit_ok = true; + awaited = true; +#endif + OPCODE_BREAK; + } + } + DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available). + + OPCODE(OPCODE_AWAIT_RESUME) { + CHECK_SPACE(2); +#ifdef DEBUG_ENABLED + if (!p_state) { + err_text = ("Invalid Resume (bug?)"); + OPCODE_BREAK; + } +#endif + GET_INSTRUCTION_ARG(result, 0); + *result = p_state->result; + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_JUMP) { + CHECK_SPACE(2); + int to = _code_ptr[ip + 1]; + + GD_ERR_BREAK(to < 0 || to > _code_size); + ip = to; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_JUMP_IF) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(test, 0); + + bool result = test->booleanize(); + + if (result) { + int to = _code_ptr[ip + 2]; + GD_ERR_BREAK(to < 0 || to > _code_size); + ip = to; + } else { + ip += 3; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_JUMP_IF_NOT) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(test, 0); + + bool result = test->booleanize(); + + if (!result) { + int to = _code_ptr[ip + 2]; + GD_ERR_BREAK(to < 0 || to > _code_size); + ip = to; + } else { + ip += 3; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_JUMP_TO_DEF_ARGUMENT) { + CHECK_SPACE(2); + ip = _default_arg_ptr[defarg]; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_RETURN) { + CHECK_SPACE(2); + GET_INSTRUCTION_ARG(r, 0); + retvalue = *r; +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif + OPCODE_BREAK; + } + + OPCODE(OPCODE_ITERATE_BEGIN) { + CHECK_SPACE(8); // Space for this and a regular iterate. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + bool valid; + if (!container->iter_init(*counter, valid)) { +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'."; + OPCODE_BREAK; + } +#endif + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + + *iterator = container->iter_get(*counter, valid); +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'."; + OPCODE_BREAK; + } +#endif + ip += 5; // Skip regular iterate which is always next. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_INT) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + int64_t size = *VariantInternal::get_int(container); + + VariantInternal::initialize(counter, Variant::INT); + *VariantInternal::get_int(counter) = 0; + + if (size > 0) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::INT); + *VariantInternal::get_int(iterator) = 0; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_FLOAT) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + double size = *VariantInternal::get_float(container); + + VariantInternal::initialize(counter, Variant::FLOAT); + *VariantInternal::get_float(counter) = 0.0; + + if (size > 0) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::FLOAT); + *VariantInternal::get_float(iterator) = 0; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Vector2 *bounds = VariantInternal::get_vector2(container); + + VariantInternal::initialize(counter, Variant::FLOAT); + *VariantInternal::get_float(counter) = bounds->x; + + if (bounds->x < bounds->y) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::FLOAT); + *VariantInternal::get_float(iterator) = bounds->x; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2I) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Vector2i *bounds = VariantInternal::get_vector2i(container); + + VariantInternal::initialize(counter, Variant::FLOAT); + *VariantInternal::get_int(counter) = bounds->x; + + if (bounds->x < bounds->y) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::INT); + *VariantInternal::get_int(iterator) = bounds->x; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Vector3 *bounds = VariantInternal::get_vector3(container); + double from = bounds->x; + double to = bounds->y; + double step = bounds->z; + + VariantInternal::initialize(counter, Variant::FLOAT); + *VariantInternal::get_float(counter) = from; + + bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0); + + if (do_continue) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::FLOAT); + *VariantInternal::get_float(iterator) = from; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3I) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Vector3i *bounds = VariantInternal::get_vector3i(container); + int64_t from = bounds->x; + int64_t to = bounds->y; + int64_t step = bounds->z; + + VariantInternal::initialize(counter, Variant::INT); + *VariantInternal::get_int(counter) = from; + + bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0); + + if (do_continue) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::INT); + *VariantInternal::get_int(iterator) = from; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_STRING) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + String *str = VariantInternal::get_string(container); + + VariantInternal::initialize(counter, Variant::INT); + *VariantInternal::get_int(counter) = 0; + + if (!str->empty()) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::STRING); + *VariantInternal::get_string(iterator) = str->substr(0, 1); + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_DICTIONARY) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Dictionary *dict = VariantInternal::get_dictionary(container); + const Variant *next = dict->next(nullptr); + *counter = *next; + + if (!dict->empty()) { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = *next; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_ARRAY) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Array *array = VariantInternal::get_array(container); + + VariantInternal::initialize(counter, Variant::INT); + *VariantInternal::get_int(counter) = 0; + + if (!array->empty()) { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = array->get(0); + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + +#define OPCODE_ITERATE_BEGIN_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_var_ret_type, m_ret_type, m_ret_get_func) \ + OPCODE(OPCODE_ITERATE_BEGIN_PACKED_##m_var_type##_ARRAY) { \ + CHECK_SPACE(8); \ + GET_INSTRUCTION_ARG(counter, 0); \ + GET_INSTRUCTION_ARG(container, 1); \ + Vector<m_elem_type> *array = VariantInternal::m_get_func(container); \ + VariantInternal::initialize(counter, Variant::INT); \ + *VariantInternal::get_int(counter) = 0; \ + if (!array->empty()) { \ + GET_INSTRUCTION_ARG(iterator, 2); \ + VariantInternal::initialize(iterator, Variant::m_var_ret_type); \ + m_ret_type *it = VariantInternal::m_ret_get_func(iterator); \ + *it = array->get(0); \ + ip += 5; \ + } else { \ + int jumpto = _code_ptr[ip + 4]; \ + GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \ + ip = jumpto; \ + } \ + } \ + DISPATCH_OPCODE + + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(BYTE, uint8_t, get_byte_array, INT, int64_t, get_int); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(INT32, int32_t, get_int32_array, INT, int64_t, get_int); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(INT64, int64_t, get_int64_array, INT, int64_t, get_int); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(FLOAT32, float, get_float32_array, FLOAT, double, get_float); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(FLOAT64, double, get_float64_array, FLOAT, double, get_float); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(STRING, String, get_string_array, STRING, String, get_string); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(VECTOR2, Vector2, get_vector2_array, VECTOR2, Vector2, get_vector2); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(VECTOR3, Vector3, get_vector3_array, VECTOR3, Vector3, get_vector3); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(COLOR, Color, get_color_array, COLOR, Color, get_color); + + OPCODE(OPCODE_ITERATE_BEGIN_OBJECT) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *obj = container->get_validated_object_with_check(freed); + if (freed) { + err_text = "Trying to iterate on a previously freed object."; + OPCODE_BREAK; + } else if (!obj) { + err_text = "Trying to iterate on a null value."; + OPCODE_BREAK; + } +#else + Object *obj = *VariantInternal::get_object(container); +#endif + Array ref; + ref.push_back(*counter); + Variant vref; + VariantInternal::initialize(&vref, Variant::ARRAY); + *VariantInternal::get_array(&vref) = ref; + + Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore. + args[0] = &vref; + + Callable::CallError ce; + Variant has_next = obj->call(CoreStringNames::get_singleton()->_iter_init, (const Variant **)args, 1, ce); + +#ifdef DEBUG_ENABLED + if (ce.error != Callable::CallError::CALL_OK) { + err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container); + OPCODE_BREAK; + } +#endif + if (!has_next.booleanize()) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = obj->call(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce); +#ifdef DEBUG_ENABLED + if (ce.error != Callable::CallError::CALL_OK) { + err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container); + OPCODE_BREAK; + } +#endif + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + bool valid; + if (!container->iter_next(*counter, valid)) { +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; + OPCODE_BREAK; + } +#endif + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + + *iterator = container->iter_get(*counter, valid); +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; + OPCODE_BREAK; + } +#endif + ip += 5; //loop again + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_INT) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + int64_t size = *VariantInternal::get_int(container); + int64_t *count = VariantInternal::get_int(counter); + + (*count)++; + + if (*count >= size) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_int(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_FLOAT) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + double size = *VariantInternal::get_float(container); + double *count = VariantInternal::get_float(counter); + + (*count)++; + + if (*count >= size) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_float(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_VECTOR2) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Vector2 *bounds = VariantInternal::get_vector2((const Variant *)container); + double *count = VariantInternal::get_float(counter); + + (*count)++; + + if (*count >= bounds->y) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_float(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_VECTOR2I) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Vector2i *bounds = VariantInternal::get_vector2i((const Variant *)container); + int64_t *count = VariantInternal::get_int(counter); + + (*count)++; + + if (*count >= bounds->y) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_int(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_VECTOR3) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Vector3 *bounds = VariantInternal::get_vector3((const Variant *)container); + double *count = VariantInternal::get_float(counter); + + *count += bounds->z; + + if ((bounds->z < 0 && *count <= bounds->y) || (bounds->z > 0 && *count >= bounds->y)) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_float(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_VECTOR3I) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Vector3i *bounds = VariantInternal::get_vector3i((const Variant *)container); + int64_t *count = VariantInternal::get_int(counter); + + *count += bounds->z; + + if ((bounds->z < 0 && *count <= bounds->y) || (bounds->z > 0 && *count >= bounds->y)) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_int(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_STRING) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const String *str = VariantInternal::get_string((const Variant *)container); + int64_t *idx = VariantInternal::get_int(counter); + (*idx)++; + + if (*idx >= str->length()) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_string(iterator) = str->substr(*idx, 1); + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_DICTIONARY) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Dictionary *dict = VariantInternal::get_dictionary((const Variant *)container); + const Variant *next = dict->next(counter); + + if (!next) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *counter = *next; + *iterator = *next; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_ARRAY) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Array *array = VariantInternal::get_array((const Variant *)container); + int64_t *idx = VariantInternal::get_int(counter); + (*idx)++; + + if (*idx >= array->size()) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = array->get(*idx); + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + +#define OPCODE_ITERATE_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_ret_get_func) \ + OPCODE(OPCODE_ITERATE_PACKED_##m_var_type##_ARRAY) { \ + CHECK_SPACE(4); \ + GET_INSTRUCTION_ARG(counter, 0); \ + GET_INSTRUCTION_ARG(container, 1); \ + const Vector<m_elem_type> *array = VariantInternal::m_get_func((const Variant *)container); \ + int64_t *idx = VariantInternal::get_int(counter); \ + (*idx)++; \ + if (*idx >= array->size()) { \ + int jumpto = _code_ptr[ip + 4]; \ + GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \ + ip = jumpto; \ + } else { \ + GET_INSTRUCTION_ARG(iterator, 2); \ + *VariantInternal::m_ret_get_func(iterator) = array->get(*idx); \ + ip += 5; \ + } \ + } \ + DISPATCH_OPCODE + + OPCODE_ITERATE_PACKED_ARRAY(BYTE, uint8_t, get_byte_array, get_int); + OPCODE_ITERATE_PACKED_ARRAY(INT32, int32_t, get_int32_array, get_int); + OPCODE_ITERATE_PACKED_ARRAY(INT64, int64_t, get_int64_array, get_int); + OPCODE_ITERATE_PACKED_ARRAY(FLOAT32, float, get_float32_array, get_float); + OPCODE_ITERATE_PACKED_ARRAY(FLOAT64, double, get_float64_array, get_float); + OPCODE_ITERATE_PACKED_ARRAY(STRING, String, get_string_array, get_string); + OPCODE_ITERATE_PACKED_ARRAY(VECTOR2, Vector2, get_vector2_array, get_vector2); + OPCODE_ITERATE_PACKED_ARRAY(VECTOR3, Vector3, get_vector3_array, get_vector3); + OPCODE_ITERATE_PACKED_ARRAY(COLOR, Color, get_color_array, get_color); + + OPCODE(OPCODE_ITERATE_OBJECT) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *obj = container->get_validated_object_with_check(freed); + if (freed) { + err_text = "Trying to iterate on a previously freed object."; + OPCODE_BREAK; + } else if (!obj) { + err_text = "Trying to iterate on a null value."; + OPCODE_BREAK; + } +#else + Object *obj = *VariantInternal::get_object(container); +#endif + Array ref; + ref.push_back(*counter); + Variant vref; + VariantInternal::initialize(&vref, Variant::ARRAY); + *VariantInternal::get_array(&vref) = ref; + + Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore. + args[0] = &vref; + + Callable::CallError ce; + Variant has_next = obj->call(CoreStringNames::get_singleton()->_iter_next, (const Variant **)args, 1, ce); + +#ifdef DEBUG_ENABLED + if (ce.error != Callable::CallError::CALL_OK) { + err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container); + OPCODE_BREAK; + } +#endif + if (!has_next.booleanize()) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = obj->call(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce); +#ifdef DEBUG_ENABLED + if (ce.error != Callable::CallError::CALL_OK) { + err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container); + OPCODE_BREAK; + } +#endif + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSERT) { + CHECK_SPACE(3); + +#ifdef DEBUG_ENABLED + GET_INSTRUCTION_ARG(test, 0); + bool result = test->booleanize(); + + if (!result) { + String message_str; + if (_code_ptr[ip + 2] != 0) { + GET_INSTRUCTION_ARG(message, 1); + message_str = *message; + } + if (message_str.empty()) { + err_text = "Assertion failed."; + } else { + err_text = "Assertion failed: " + message_str; + } + OPCODE_BREAK; + } + +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_BREAKPOINT) { +#ifdef DEBUG_ENABLED + if (EngineDebugger::is_active()) { + GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true); + } +#endif + ip += 1; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_LINE) { + CHECK_SPACE(2); + + line = _code_ptr[ip + 1]; + ip += 2; + + if (EngineDebugger::is_active()) { + // line + bool do_break = false; + + if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) { + if (EngineDebugger::get_script_debugger()->get_depth() <= 0) { + EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1); + } + if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) { + do_break = true; + } + } + + if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) { + do_break = true; + } + + if (do_break) { + GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); + } + + EngineDebugger::get_singleton()->line_poll(); + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_END) { +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif + OPCODE_BREAK; + } + +#if 0 // Enable for debugging. + default: { + err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip); + OPCODE_BREAK; + } +#endif + } + + OPCODES_END +#ifdef DEBUG_ENABLED + if (exit_ok) { + OPCODE_OUT; + } + //error + // function, file, line, error, explanation + String err_file; + if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->path != "") { + err_file = p_instance->script->path; + } else if (script) { + err_file = script->path; + } + if (err_file == "") { + err_file = "<built-in>"; + } + String err_func = name; + if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->name != "") { + err_func = p_instance->script->name + "." + err_func; + } + int err_line = line; + if (err_text == "") { + err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please)."; + } + + if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) { + // debugger break did not happen + + _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT); + } + +#endif + OPCODE_OUT; + } + + OPCODES_OUT +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; + profile.total_time += time_taken; + profile.self_time += time_taken - function_call_time; + profile.frame_total_time += time_taken; + profile.frame_self_time += time_taken - function_call_time; + GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; + } + + // Check if this is the last time the function is resuming from await + // Will be true if never awaited as well + // When it's the last resume it will postpone the exit from stack, + // so the debugger knows which function triggered the resume of the next function (if any) + if (!p_state || awaited) { + if (EngineDebugger::is_active()) { + GDScriptLanguage::get_singleton()->exit_function(); + } +#endif + + if (_stack_size) { + //free stack + for (int i = 0; i < _stack_size; i++) { + stack[i].~Variant(); + } + } + +#ifdef DEBUG_ENABLED + } +#endif + + return retvalue; +} |