From 9b414deb8d8240c721fa3f56160c1856bf501915 Mon Sep 17 00:00:00 2001 From: Ruben Ayrapetyan Date: Tue, 23 Jun 2015 19:01:33 +0300 Subject: [PATCH 1/4] Introduce explicit description of registers (temporary variables) ranges. JerryScript-DCO-1.0-Signed-off-by: Ruben Ayrapetyan r.ayrapetyan@samsung.com --- jerry-core/parser/js/opcodes-dumper.cpp | 37 +++++++++++++++++++------ jerry-core/vm/opcodes.h | 12 ++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/jerry-core/parser/js/opcodes-dumper.cpp b/jerry-core/parser/js/opcodes-dumper.cpp index abc733033b..93df79ad87 100644 --- a/jerry-core/parser/js/opcodes-dumper.cpp +++ b/jerry-core/parser/js/opcodes-dumper.cpp @@ -20,7 +20,6 @@ #include "syntax-errors.h" #include "opcodes-native-call.h" -#define MIN_TEMP_NAME 128 static idx_t temp_name, max_temp_name; #define OPCODE(name) (__op__idx_##name) @@ -115,22 +114,42 @@ enum }; STATIC_STACK (reg_var_decls, opcode_counter_t) +/** + * Reset counter of register variables allocator + * to identifier of first general register + */ static void reset_temp_name (void) { - temp_name = MIN_TEMP_NAME; -} + temp_name = OPCODE_REG_GENERAL_FIRST; +} /* reset_temp_name */ +/** + * Allocate next register variable + * + * @return identifier of the allocated variable + */ static idx_t next_temp_name (void) { - temp_name++; - if (max_temp_name < temp_name) + idx_t next_reg = temp_name++; + + if (next_reg > OPCODE_REG_GENERAL_LAST) { - max_temp_name = temp_name; + /* + * FIXME: + * Implement mechanism, allowing reusage of register variables + */ + PARSE_ERROR ("Not enough register variables", 0); } - return temp_name; -} + + if (max_temp_name < next_reg) + { + max_temp_name = next_reg; + } + + return next_reg; +} /* next_temp_name */ static op_meta create_op_meta (opcode_t op, lit_cpointer_t lit_id1, lit_cpointer_t lit_id2, lit_cpointer_t lit_id3) @@ -2500,7 +2519,7 @@ void dump_reg_var_decl_for_rewrite (void) { STACK_PUSH (reg_var_decls, serializer_get_current_opcode_counter ()); - serializer_dump_op_meta (create_op_meta_000 (getop_reg_var_decl (MIN_TEMP_NAME, INVALID_VALUE))); + serializer_dump_op_meta (create_op_meta_000 (getop_reg_var_decl (OPCODE_REG_FIRST, INVALID_VALUE))); } void diff --git a/jerry-core/vm/opcodes.h b/jerry-core/vm/opcodes.h index ff7d8594aa..c6ada99aaa 100644 --- a/jerry-core/vm/opcodes.h +++ b/jerry-core/vm/opcodes.h @@ -102,6 +102,18 @@ typedef enum : idx_t * 'eval' identifier */ } opcode_scope_code_flags_t; +/** + * Enumeration of registers (temp variables) ranges + */ +typedef enum : idx_t +{ + OPCODE_REG_FIRST = 128, /** identifier of first special register */ + OPCODE_REG_SPECIAL_EVAL_RET = OPCODE_REG_FIRST, /**< eval return value */ + OPCODE_REG_GENERAL_FIRST, /** identifier of first non-special register */ + OPCODE_REG_GENERAL_LAST = 253, /** identifier of last non-special register */ + OPCODE_REG_LAST = OPCODE_REG_GENERAL_FIRST /**< identifier of last register */ +} opcode_special_reg_t; + /** * Forward declaration of opcode structure */ From 6c6e740c7a61bafd12514c744c4e65b2557b727f Mon Sep 17 00:00:00 2001 From: Ruben Ayrapetyan Date: Thu, 25 Jun 2015 20:06:07 +0300 Subject: [PATCH 2/4] Fix check of run scope bounds in vm_loop. JerryScript-DCO-1.0-Signed-off-by: Ruben Ayrapetyan r.ayrapetyan@samsung.com --- jerry-core/vm/vm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jerry-core/vm/vm.cpp b/jerry-core/vm/vm.cpp index 1ad36b3f46..801c6ae273 100644 --- a/jerry-core/vm/vm.cpp +++ b/jerry-core/vm/vm.cpp @@ -497,7 +497,7 @@ vm_loop (int_data_t *int_data_p, /**< interpreter context */ */ if (run_scope_p == NULL /* if no run scope set */ || (target >= run_scope_p->start_oc /* or target is within the current run scope */ - && target < run_scope_p->end_oc)) + && target <= run_scope_p->end_oc)) { int_data_p->pos = target; From b988fe2fce7d1d2fad512c8f742931ecc36cbcc4 Mon Sep 17 00:00:00 2001 From: Ruben Ayrapetyan Date: Tue, 23 Jun 2015 19:03:21 +0300 Subject: [PATCH 3/4] Introduce for-in opcode; implement handler for the opcode. JerryScript-DCO-1.0-Signed-off-by: Ruben Ayrapetyan r.ayrapetyan@samsung.com --- jerry-core/ecma/base/ecma-globals.h | 5 + jerry-core/ecma/base/ecma-lcache.cpp | 2 +- jerry-core/vm/opcodes-for-in.cpp | 277 +++++++++++++++++++++++++++ jerry-core/vm/opcodes.cpp | 1 + jerry-core/vm/opcodes.h | 8 +- tests/unit/test-parser.cpp | 6 +- 6 files changed, 293 insertions(+), 6 deletions(-) create mode 100644 jerry-core/vm/opcodes-for-in.cpp diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 072ddeb1fb..f5c3f30416 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -793,6 +793,11 @@ typedef uint32_t ecma_magic_string_ex_id_t; */ typedef uint8_t ecma_string_hash_t; +/** + * Length of string hash, in bits + */ +#define ECMA_STRING_HASH_BITS (sizeof (ecma_string_hash_t) * JERRY_BITSINBYTE) + /** * Number of string's last characters to use for hash calculation */ diff --git a/jerry-core/ecma/base/ecma-lcache.cpp b/jerry-core/ecma/base/ecma-lcache.cpp index 7b63da691a..8534dc8714 100644 --- a/jerry-core/ecma/base/ecma-lcache.cpp +++ b/jerry-core/ecma/base/ecma-lcache.cpp @@ -50,7 +50,7 @@ JERRY_STATIC_ASSERT (sizeof (ecma_lcache_hash_entry_t) == sizeof (uint64_t)); /** * LCache hash value length, in bits */ -#define ECMA_LCACHE_HASH_BITS (sizeof (ecma_string_hash_t) * JERRY_BITSINBYTE) +#define ECMA_LCACHE_HASH_BITS (ECMA_STRING_HASH_BITS) /** * Number of rows in LCache's hash table diff --git a/jerry-core/vm/opcodes-for-in.cpp b/jerry-core/vm/opcodes-for-in.cpp new file mode 100644 index 0000000000..f495f809f6 --- /dev/null +++ b/jerry-core/vm/opcodes-for-in.cpp @@ -0,0 +1,277 @@ +/* Copyright 2015 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jrt.h" +#include "opcodes.h" +#include "opcodes-ecma-support.h" + +/** + * Enumerate properties and construct collection with their + * names for further iteration in for-in opcode handler. + * + * See also: + * ECMA-262 v5, 12.6.4 + * + * @return header of constructed strings collection (should be freed with ecma_free_values_collection), + * or NULL - if there are no properties to enumerate in for-in. + */ +static ecma_collection_header_t * +vm_helper_for_in_enumerate_properties_names (ecma_object_t *obj_p) /**< starting object - result of ToObject + * conversion (ECMA-262 v5, 12.6.4, step 4) */ +{ + const size_t bitmap_row_size = sizeof (uint32_t) * JERRY_BITSINBYTE; + uint32_t names_hashes_bitmap[(1u << ECMA_STRING_HASH_BITS) / bitmap_row_size]; + + memset (names_hashes_bitmap, 0, sizeof (names_hashes_bitmap)); + + ecma_length_t all_properties_count = 0; + + /* First pass: counting properties */ + for (ecma_object_t *prototype_chain_iter_p = obj_p; + prototype_chain_iter_p != NULL; + prototype_chain_iter_p = ecma_get_object_prototype (prototype_chain_iter_p)) + { + for (ecma_property_t *prop_iter_p = ecma_get_property_list (prototype_chain_iter_p); + prop_iter_p != NULL; + prop_iter_p = ECMA_GET_POINTER (ecma_property_t, prop_iter_p->next_property_p)) + { + if (prop_iter_p->type == ECMA_PROPERTY_NAMEDDATA + || prop_iter_p->type == ECMA_PROPERTY_NAMEDACCESSOR) + { + all_properties_count++; + } + else + { + JERRY_ASSERT (prop_iter_p->type == ECMA_PROPERTY_INTERNAL); + } + } + } + + if (all_properties_count == 0) + { + return NULL; + } + + ecma_collection_header_t *ret_p = NULL; + + /* Second pass: collecting properties names */ + MEM_DEFINE_LOCAL_ARRAY (names_p, all_properties_count, ecma_string_t*); + + ecma_length_t enumerated_properties_count = 0; + ecma_length_t non_enumerated_properties_count = 0; + + for (ecma_object_t *prototype_chain_iter_p = obj_p; + prototype_chain_iter_p != NULL; + prototype_chain_iter_p = ecma_get_object_prototype (prototype_chain_iter_p)) + { + for (ecma_property_t *prop_iter_p = ecma_get_property_list (prototype_chain_iter_p); + prop_iter_p != NULL; + prop_iter_p = ECMA_GET_POINTER (ecma_property_t, prop_iter_p->next_property_p)) + { + if (prop_iter_p->type == ECMA_PROPERTY_NAMEDDATA + || prop_iter_p->type == ECMA_PROPERTY_NAMEDACCESSOR) + { + bool is_enumerated; + + ecma_string_t *prop_name_p; + + if (prop_iter_p->type == ECMA_PROPERTY_NAMEDDATA) + { + prop_name_p = ECMA_GET_NON_NULL_POINTER (ecma_string_t, prop_iter_p->u.named_data_property.name_p); + } + else + { + prop_name_p = ECMA_GET_NON_NULL_POINTER (ecma_string_t, prop_iter_p->u.named_accessor_property.name_p); + } + + ecma_string_hash_t hash = prop_name_p->hash; + uint32_t bitmap_row = hash / bitmap_row_size; + uint32_t bitmap_column = hash % bitmap_row_size; + + if (ecma_is_property_enumerable (prop_iter_p)) + { + if ((names_hashes_bitmap[bitmap_row] & (1u << bitmap_column)) == 0) + { + /* no name with the hash occured during the iteration session */ + is_enumerated = true; + } + else + { + /* name with same hash already occured */ + bool is_equal_found = false; + + for (uint32_t index = 0; + !is_equal_found && index < enumerated_properties_count; + index++) + { + if (ecma_compare_ecma_strings (prop_name_p, + names_p[index])) + { + is_equal_found = true; + } + } + + for (uint32_t index = 0; + !is_equal_found && index < non_enumerated_properties_count; + index++) + { + if (ecma_compare_ecma_strings (prop_name_p, + names_p[all_properties_count - index - 1])) + { + is_equal_found = true; + } + } + + is_enumerated = !is_equal_found; + } + } + else + { + is_enumerated = false; + } + + names_hashes_bitmap[bitmap_row] |= (1u << bitmap_column); + + if (is_enumerated) + { + names_p[enumerated_properties_count++] = prop_name_p; + } + else + { + names_p[all_properties_count - non_enumerated_properties_count++ - 1] = prop_name_p; + } + + JERRY_ASSERT (enumerated_properties_count + non_enumerated_properties_count <= all_properties_count); + } + else + { + JERRY_ASSERT (prop_iter_p->type == ECMA_PROPERTY_INTERNAL); + } + } + } + + if (enumerated_properties_count != 0) + { + ret_p = ecma_new_strings_collection (names_p, enumerated_properties_count); + } + + MEM_FINALIZE_LOCAL_ARRAY (names_p); + + return ret_p; +} /* vm_helper_for_in_enumerate_properties_names */ + +/** + * 'for-in' opcode handler + * + * See also: + * ECMA-262 v5, 12.6.4 + * + * @return completion value + * Returned value must be freed with ecma_free_completion_value + */ +ecma_completion_value_t +opfunc_for_in (opcode_t opdata, /**< operation data */ + int_data_t *int_data_p) /**< interpreter context */ +{ + const idx_t expr_idx = opdata.data.for_in.expr; + const idx_t block_end_oc_idx_1 = opdata.data.for_in.oc_idx_1; + const idx_t block_end_oc_idx_2 = opdata.data.for_in.oc_idx_2; + const opcode_counter_t for_in_end_oc = (opcode_counter_t) ( + calc_opcode_counter_from_idx_idx (block_end_oc_idx_1, + block_end_oc_idx_2) + int_data_p->pos); + + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + + /* 1., 2. */ + ECMA_TRY_CATCH (expr_value, + get_variable_value (int_data_p, + expr_idx, + false), + ret_value); + + int_data_p->pos++; + + opcode_t meta_opcode = vm_get_opcode (int_data_p->opcodes_p, for_in_end_oc); + JERRY_ASSERT (meta_opcode.op_idx == __op__idx_meta); + JERRY_ASSERT (meta_opcode.data.meta.type == OPCODE_META_TYPE_END_FOR_IN); + + /* 3. */ + if (!ecma_is_value_undefined (expr_value) + && !ecma_is_value_null (expr_value)) + { + /* 4. */ + ECMA_TRY_CATCH (obj_expr_value, + ecma_op_to_object (expr_value), + ret_value); + + ecma_object_t *obj_p = ecma_get_object_from_value (obj_expr_value); + + ecma_collection_iterator_t names_iterator; + ecma_collection_header_t *names_p = vm_helper_for_in_enumerate_properties_names (obj_p); + + if (names_p != NULL) + { + ecma_collection_iterator_init (&names_iterator, names_p); + + const opcode_counter_t for_in_body_begin_oc = int_data_p->pos; + const opcode_counter_t for_in_body_end_oc = for_in_end_oc; + + while (ecma_collection_iterator_next (&names_iterator)) + { + ecma_value_t name_value = *names_iterator.current_value_p; + + ecma_string_t *name_p = ecma_get_string_from_value (name_value); + + if (ecma_op_object_get_property (obj_p, name_p) != NULL) + { + ecma_completion_value_t completion = set_variable_value (int_data_p, + int_data_p->pos, + OPCODE_REG_SPECIAL_FOR_IN_PROPERTY_NAME, + name_value); + JERRY_ASSERT (ecma_is_completion_value_empty (completion)); + + vm_run_scope_t run_scope_for_in = { for_in_body_begin_oc, for_in_body_end_oc }; + + ecma_completion_value_t for_in_body_completion = vm_loop (int_data_p, &run_scope_for_in); + if (ecma_is_completion_value_empty (for_in_body_completion)) + { + JERRY_ASSERT (int_data_p->pos == for_in_body_end_oc); + + int_data_p->pos = for_in_body_begin_oc; + } + else + { + JERRY_ASSERT (!ecma_is_completion_value_normal (for_in_body_completion)); + JERRY_ASSERT (int_data_p->pos <= for_in_body_end_oc); + + ret_value = for_in_body_completion; + break; + } + } + } + + ecma_free_values_collection (names_p, true); + } + + ECMA_FINALIZE (obj_expr_value); + } + + int_data_p->pos = (opcode_counter_t) (for_in_end_oc + 1u); + + ECMA_FINALIZE (expr_value); + + return ret_value; +} /* opfunc_for_in */ + diff --git a/jerry-core/vm/opcodes.cpp b/jerry-core/vm/opcodes.cpp index d1e3a6fc59..09d494b6ea 100644 --- a/jerry-core/vm/opcodes.cpp +++ b/jerry-core/vm/opcodes.cpp @@ -1748,6 +1748,7 @@ opfunc_meta (opcode_t opdata, /**< operation data */ case OPCODE_META_TYPE_CATCH: case OPCODE_META_TYPE_FINALLY: case OPCODE_META_TYPE_END_TRY_CATCH_FINALLY: + case OPCODE_META_TYPE_END_FOR_IN: { return ecma_make_meta_completion_value (); } diff --git a/jerry-core/vm/opcodes.h b/jerry-core/vm/opcodes.h index c6ada99aaa..5c19aec90e 100644 --- a/jerry-core/vm/opcodes.h +++ b/jerry-core/vm/opcodes.h @@ -72,8 +72,9 @@ typedef enum OPCODE_META_TYPE_CATCH_EXCEPTION_IDENTIFIER, /**< literal index containing name of variable with exception object */ OPCODE_META_TYPE_FINALLY, /**< mark of beginning of finally block containing pointer to end of finally block */ OPCODE_META_TYPE_END_TRY_CATCH_FINALLY, /**< mark of end of try-catch, try-finally, try-catch-finally blocks */ - OPCODE_META_TYPE_SCOPE_CODE_FLAGS /**< set of flags indicating various properties of the scope's code - * (See also: opcode_scope_code_flags_t) */ + OPCODE_META_TYPE_SCOPE_CODE_FLAGS, /**< set of flags indicating various properties of the scope's code + * (See also: opcode_scope_code_flags_t) */ + OPCODE_META_TYPE_END_FOR_IN /**< end of for-in statement */ } opcode_meta_type; typedef enum : idx_t @@ -109,6 +110,8 @@ typedef enum : idx_t { OPCODE_REG_FIRST = 128, /** identifier of first special register */ OPCODE_REG_SPECIAL_EVAL_RET = OPCODE_REG_FIRST, /**< eval return value */ + OPCODE_REG_SPECIAL_FOR_IN_PROPERTY_NAME, /**< variable, containing property name, + * at start of for-in loop body */ OPCODE_REG_GENERAL_FIRST, /** identifier of first non-special register */ OPCODE_REG_GENERAL_LAST = 253, /** identifier of last non-special register */ OPCODE_REG_LAST = OPCODE_REG_GENERAL_FIRST /**< identifier of last register */ @@ -186,6 +189,7 @@ opcode_counter_t read_meta_opcode_counter (opcode_meta_type expected_type, int_d p##_2 (a, delete_var, lhs, name) \ p##_3 (a, delete_prop, lhs, base, name) \ p##_2 (a, typeof, lhs, obj) \ + p##_3 (a, for_in, expr, oc_idx_1, oc_idx_2) \ p##_3 (a, with, expr, oc_idx_1, oc_idx_2) \ p##_2 (a, try_block, oc_idx_1, oc_idx_2) \ p##_1 (a, throw_value, var) diff --git a/tests/unit/test-parser.cpp b/tests/unit/test-parser.cpp index e1b1604c69..3af72cb742 100644 --- a/tests/unit/test-parser.cpp +++ b/tests/unit/test-parser.cpp @@ -91,10 +91,10 @@ main (int __attr_unused___ argc, OPCODE_SCOPE_CODE_FLAGS_NOT_REF_ARGUMENTS_IDENTIFIER | OPCODE_SCOPE_CODE_FLAGS_NOT_REF_EVAL_IDENTIFIER, INVALID_VALUE), - getop_reg_var_decl (128, 129), // var tmp128 .. tmp129; + getop_reg_var_decl (OPCODE_REG_FIRST, OPCODE_REG_GENERAL_FIRST), getop_var_decl (0), // var a; - getop_assignment (129, 1, 1), // tmp129 = 1: SMALLINT; - getop_assignment (0, 6, 129), // a = tmp129 : TYPEOF (tmp129); + getop_assignment (130, 1, 1), // $tmp0 = 1; + getop_assignment (0, 6, 130), // a = $tmp0; getop_exitval (0) // exit 0; }; From 507411f0a0626a58ca91e902881884888fa820e2 Mon Sep 17 00:00:00 2001 From: Ruben Ayrapetyan Date: Thu, 25 Jun 2015 01:20:31 +0300 Subject: [PATCH 4/4] Implement parse of for-in statement. JerryScript-DCO-1.0-Signed-off-by: Evgeny Gavrin e.gavrin@samsung.com JerryScript-DCO-1.0-Signed-off-by: Ruben Ayrapetyan r.ayrapetyan@samsung.com --- jerry-core/parser/js/opcodes-dumper.cpp | 73 ++++++ jerry-core/parser/js/opcodes-dumper.h | 5 + jerry-core/parser/js/parser.cpp | 179 ++++++++++++++- jerry-core/parser/js/scopes-tree.cpp | 1 + jerry-core/vm/pretty-printer.cpp | 6 + tests/jerry/for-in.js | 283 ++++++++++++++++++++++++ 6 files changed, 539 insertions(+), 8 deletions(-) create mode 100644 tests/jerry/for-in.js diff --git a/jerry-core/parser/js/opcodes-dumper.cpp b/jerry-core/parser/js/opcodes-dumper.cpp index 93df79ad87..cd48c8d077 100644 --- a/jerry-core/parser/js/opcodes-dumper.cpp +++ b/jerry-core/parser/js/opcodes-dumper.cpp @@ -708,6 +708,23 @@ eval_ret_operand (void) return ret; } /* eval_ret_operand */ +/** + * Creates operand for taking iterator value (next property name) + * from for-in opcode handler. + * + * @return constructed operand + */ +operand +jsp_create_operand_for_in_special_reg (void) +{ + operand ret; + + ret.type = OPERAND_TMP; + ret.data.uid = OPCODE_REG_SPECIAL_FOR_IN_PROPERTY_NAME; + + return ret; +} /* jsp_create_operand_for_in_special_reg */ + bool operand_is_empty (operand op) { @@ -2362,6 +2379,62 @@ dump_with_end (void) serializer_dump_op_meta (create_op_meta_000 (opcode)); } /* dump_with_end */ +/** + * Dump template of 'for_in' instruction. + * + * Note: + * the instruction's flags field is written later (see also: rewrite_for_in). + * + * @return position of dumped instruction + */ +opcode_counter_t +dump_for_in_for_rewrite (operand op) /**< operand - result of evaluating Expression + * in for-in statement */ +{ + opcode_counter_t oc = serializer_get_current_opcode_counter (); + + if (op.type == OPERAND_LITERAL) + { + const opcode_t opcode = getop_for_in (LITERAL_TO_REWRITE, INVALID_VALUE, INVALID_VALUE); + serializer_dump_op_meta (create_op_meta_100 (opcode, op.data.lit_id)); + } + else + { + JERRY_ASSERT (op.type == OPERAND_TMP); + + const opcode_t opcode = getop_for_in (op.data.uid, INVALID_VALUE, INVALID_VALUE); + serializer_dump_op_meta (create_op_meta_000 (opcode)); + } + + return oc; +} /* dump_for_in_for_rewrite */ + +/** + * Write position of 'for_in' block's end to specified 'for_in' instruction template, + * dumped earlier (see also: dump_for_in_for_rewrite). + */ +void +rewrite_for_in (opcode_counter_t oc) /**< opcode counter of the instruction template */ +{ + op_meta for_in_op_meta = serializer_get_op_meta (oc); + + idx_t id1, id2; + split_opcode_counter (get_diff_from (oc), &id1, &id2); + for_in_op_meta.op.data.for_in.oc_idx_1 = id1; + for_in_op_meta.op.data.for_in.oc_idx_2 = id2; + serializer_rewrite_op_meta (oc, for_in_op_meta); +} /* rewrite_for_in */ + +/** + * Dump 'meta' instruction of 'end for_in' type + */ +void +dump_for_in_end (void) +{ + const opcode_t opcode = getop_meta (OPCODE_META_TYPE_END_FOR_IN, INVALID_VALUE, INVALID_VALUE); + serializer_dump_op_meta (create_op_meta_000 (opcode)); +} /* dump_for_in_end */ + void dump_try_for_rewrite (void) { diff --git a/jerry-core/parser/js/opcodes-dumper.h b/jerry-core/parser/js/opcodes-dumper.h index 72ed8573cd..2870d7222a 100644 --- a/jerry-core/parser/js/opcodes-dumper.h +++ b/jerry-core/parser/js/opcodes-dumper.h @@ -49,6 +49,7 @@ typedef enum __attr_packed___ operand empty_operand (void); operand literal_operand (lit_cpointer_t); operand eval_ret_operand (void); +operand jsp_create_operand_for_in_special_reg (void); bool operand_is_empty (operand); void dumper_init (void); @@ -211,6 +212,10 @@ opcode_counter_t dump_with_for_rewrite (operand); void rewrite_with (opcode_counter_t); void dump_with_end (void); +opcode_counter_t dump_for_in_for_rewrite (operand); +void rewrite_for_in (opcode_counter_t); +void dump_for_in_end (void); + void dump_try_for_rewrite (void); void rewrite_try (void); void dump_catch_for_rewrite (operand); diff --git a/jerry-core/parser/js/parser.cpp b/jerry-core/parser/js/parser.cpp index a2696256cf..3413ef7622 100644 --- a/jerry-core/parser/js/parser.cpp +++ b/jerry-core/parser/js/parser.cpp @@ -1768,7 +1768,7 @@ parse_expression (bool in_allowed, /**< flag indicating if 'in' is allowed insid initialiser : '=' LT!* assignment_expression ; */ -static void +static operand parse_variable_declaration (void) { current_token_must_be (TOK_NAME); @@ -1785,6 +1785,8 @@ parse_variable_declaration (void) { lexer_save_token (tok); } + + return name; } /* variable_declaration_list @@ -1933,15 +1935,176 @@ jsp_parse_for_statement (jsp_label_t *outermost_stmt_label_p, /**< outermost (fi } } /* jsp_parse_for_statement */ +/** + * Parse VariableDeclarationNoIn / LeftHandSideExpression (iterator part) of for-in statement + * + * See also: + * jsp_parse_for_in_statement + * + * @return true - if iterator consists of base and property name, + * false - otherwise, iterator consists of an identifier name (without base). + */ +static bool +jsp_parse_for_in_statement_iterator (operand *base_p, /**< out: base value of member expression, if any, + * empty operand - otherwise */ + operand *identifier_p) /**< out: property name (if base value is not empty), + * identifier - otherwise */ +{ + JERRY_ASSERT (base_p != NULL); + JERRY_ASSERT (identifier_p != NULL); + + if (is_keyword (KW_VAR)) + { + skip_newlines (); + + *base_p = empty_operand (); + *identifier_p = parse_variable_declaration (); + + return false; + } + else + { + operand base, identifier; + + /* + * FIXME: + * Remove evaluation of last part of identifier chain + */ + operand i = parse_left_hand_side_expression (&base, &identifier); + + if (operand_is_empty (base)) + { + *base_p = empty_operand (); + *identifier_p = i; + + return false; + } + else + { + *base_p = base; + *identifier_p = identifier; + + return true; + } + } +} /* jsp_parse_for_in_statement_iterator */ + +/** + * Parse for-in statement + * + * See also: + * ECMA-262 v5, 12.6.4 + * + * Note: + * Syntax: + * Iterator Collection Body LoopEnd + * - for ( LeftHandSideExpression in Expression) Statement + * - for (var VariableDeclarationNoIn in Expression) Statement + * + * Note: + * Layout of generate byte-code is the following: + * tmp <- Collection (Expression) + * for_in instruction (tmp, opcode counter of for-in end mark) + * { + * Assignment of OPCODE_REG_SPECIAL_FOR_IN_PROPERTY_NAME to + * Iterator (VariableDeclarationNoIn / LeftHandSideExpression) + * } + * Body (Statement) + * ContinueTarget: + * meta (OPCODE_META_TYPE_END_FOR_IN) + */ static void -parse_for_in (jsp_label_t *outermost_stmt_label_p) /**< outermost (first) label, corresponding to - * the statement (or NULL, if there are no named - * labels associated with the statement) */ +jsp_parse_for_in_statement (jsp_label_t *outermost_stmt_label_p, /**< outermost (first) label, + * corresponding to the statement + * (or NULL, if there are no name + * labels associated with the statement) */ + locus for_body_statement_loc) /**< locus of loop body statement */ { - (void) outermost_stmt_label_p; + jsp_label_raise_nested_jumpable_border (); - EMIT_SORRY ("'for in' loops are not supported yet"); -} + current_token_must_be (TOK_OPEN_PAREN); + skip_newlines (); + + // Save Iterator location + locus iterator_loc = tok.loc; + + while (tok.loc < for_body_statement_loc) + { + if (jsp_find_next_token_before_the_locus (TOK_KEYWORD, + for_body_statement_loc, + true)) + { + if (is_keyword (KW_IN)) + { + break; + } + else + { + skip_token (); + } + } + else + { + EMIT_ERROR ("Invalid for statement"); + } + } + + JERRY_ASSERT (is_keyword (KW_IN)); + skip_newlines (); + + // Collection + operand collection = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP); + current_token_must_be (TOK_CLOSE_PAREN); + skip_token (); + + // Dump for-in instruction + opcode_counter_t for_in_oc = dump_for_in_for_rewrite (collection); + + // Dump assignment VariableDeclarationNoIn / LeftHandSideExpression <- OPCODE_REG_SPECIAL_FOR_IN_PROPERTY_NAME + lexer_seek (iterator_loc); + tok = lexer_next_token (); + + operand iterator_base, iterator_identifier, for_in_special_reg; + for_in_special_reg = jsp_create_operand_for_in_special_reg (); + + if (jsp_parse_for_in_statement_iterator (&iterator_base, &iterator_identifier)) + { + dump_prop_setter (iterator_base, iterator_identifier, for_in_special_reg); + } + else + { + JERRY_ASSERT (operand_is_empty (iterator_base)); + dump_variable_assignment (iterator_identifier, for_in_special_reg); + } + + // Body + lexer_seek (for_body_statement_loc); + tok = lexer_next_token (); + + parse_statement (NULL); + + // Save LoopEnd locus + const locus loop_end_loc = tok.loc; + + // Setup ContinueTarget + jsp_label_setup_continue_target (outermost_stmt_label_p, + serializer_get_current_opcode_counter ()); + + // Write position of for-in end to for_in instruction + rewrite_for_in (for_in_oc); + + // Dump meta (OPCODE_META_TYPE_END_FOR_IN) + dump_for_in_end (); + + lexer_seek (loop_end_loc); + tok = lexer_next_token (); + if (tok.type != TOK_CLOSE_BRACE) + { + lexer_save_token (tok); + } + + jsp_label_remove_nested_jumpable_border (); +} /* jsp_parse_for_in_statement */ /** * Parse for/for-in statements @@ -1982,7 +2145,7 @@ jsp_parse_for_or_for_in_statement (jsp_label_t *outermost_stmt_label_p) /**< out } else { - parse_for_in (outermost_stmt_label_p); + jsp_parse_for_in_statement (outermost_stmt_label_p, for_body_statement_loc); } } /* jsp_parse_for_or_for_in_statement */ diff --git a/jerry-core/parser/js/scopes-tree.cpp b/jerry-core/parser/js/scopes-tree.cpp index 7a00d93992..013ef856d1 100644 --- a/jerry-core/parser/js/scopes-tree.cpp +++ b/jerry-core/parser/js/scopes-tree.cpp @@ -306,6 +306,7 @@ generate_opcode (scopes_tree tree, opcode_counter_t opc_index, lit_id_hash_table case OPCODE (obj_decl): case OPCODE (this_binding): case OPCODE (with): + case OPCODE (for_in): case OPCODE (throw_value): case OPCODE (is_true_jmp_up): case OPCODE (is_true_jmp_down): diff --git a/jerry-core/vm/pretty-printer.cpp b/jerry-core/vm/pretty-printer.cpp index f510bfc496..6dba56b34c 100644 --- a/jerry-core/vm/pretty-printer.cpp +++ b/jerry-core/vm/pretty-printer.cpp @@ -232,6 +232,7 @@ pp_op_meta (const opcode_t *opcodes_p, PP_OP (delete_prop, "%s = delete %s.%s;"); PP_OP (typeof, "%s = typeof %s;"); PP_OP (with, "with (%s);"); + PP_OP (for_in, "for_in (%s);"); case NAME_TO_ID (is_true_jmp_up): printf ("if (%s) goto %d;", VAR (1), oc - OC (2, 3)); break; case NAME_TO_ID (is_false_jmp_up): printf ("if (%s == false) goto %d;", VAR (1), oc - OC (2, 3)); break; case NAME_TO_ID (is_true_jmp_down): printf ("if (%s) goto %d;", VAR (1), oc + OC (2, 3)); break; @@ -554,6 +555,11 @@ pp_op_meta (const opcode_t *opcodes_p, printf ("end with;"); break; } + case OPCODE_META_TYPE_END_FOR_IN: + { + printf ("end for-in;"); + break; + } case OPCODE_META_TYPE_FUNCTION_END: { printf ("function end: %d;", oc + OC (2, 3)); diff --git a/tests/jerry/for-in.js b/tests/jerry/for-in.js new file mode 100644 index 0000000000..7ff73cc3fe --- /dev/null +++ b/tests/jerry/for-in.js @@ -0,0 +1,283 @@ +// Copyright 2015 Samsung Electronics Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// 1. +var simple_obj = {a: 1, b: 2, c: 3, d: 4}; +for (var prop_of_simple_obj in simple_obj) { + simple_obj[prop_of_simple_obj] += 4; +} + +assert(simple_obj.a === 5 + && simple_obj.b === 6 + && simple_obj.c === 7 + && simple_obj.d === 8); + +// 2. +for + ( + var + prop_of_simple_obj in simple_obj + ) { + simple_obj[prop_of_simple_obj] -= 4; +} + +assert(simple_obj.a === 1 + && simple_obj.b === 2 + && simple_obj.c === 3 + && simple_obj.d === 4); + +// 3. +function test() { + var cnt = 0; + + for (var prop_of_simple_obj in simple_obj) { + if (prop_of_simple_obj === 'b') + continue; + + cnt++; + + simple_obj[prop_of_simple_obj] += 4; + } + + return cnt; +} + +var ret_val = test(); + +assert((simple_obj.a === 5 + && simple_obj.b === 2 + && simple_obj.c === 7 + && simple_obj.d == 8) + && ret_val === 3); + +// 4. +var array_obj = new Array(1, 2, 3, 4, 5, 6, 7); +var prop_of_array_obj; + +array_obj.eight = 8; + +for (prop_of_array_obj in array_obj) { + array_obj[prop_of_array_obj] += 1; +} + +assert(array_obj[0] === 2 + && array_obj[1] === 3 + && array_obj[2] === 4 + && array_obj[3] === 5 + && array_obj[4] === 6 + && array_obj[5] === 7 + && array_obj[6] === 8 + && array_obj['eight'] === 9); + +// 5. +var null_obj = null; +for (var prop_of_null_obj in null_obj) { + assert(false); +} + +// 6. +var empty_object = {}; +for (var prop_of_empty_object in empty_object) { + assert(false); +} + +// 7. +for (var i in undefined) { + assert(false); +} + +// 8. +var base_obj = {base_prop: "base"}; + +function constr() { + this.derived_prop = "derived"; +} + +constr.prototype = base_obj; + +var derived_obj = new constr(); + +for (var prop_of_derived_obj in derived_obj) { + derived_obj[prop_of_derived_obj] += "A"; +} + +assert(derived_obj.base_prop === "baseA" && derived_obj.derived_prop === "derivedA"); + +// 9. +log = {}; +count = 0; + +for (i in {q : 1}) +{ + log [i] = true; + count++; +} + +assert (count == 1 && 'q' in log); + +// 10. +log = {}; +count = 0; + +for (i in {q : 1, p : 2, get f() { ; }, set f (v) { ; }, get t () { }, set c (v) {}}) +{ + log [i] = true; + count++; +} + +assert (count == 5 + && 'q' in log + && 'p' in log + && 'f' in log + && 't' in log + && 'c' in log); + +// 11. +log = {}; +count = 0; + +var a = []; +a[5] = 5; +for (var x in a) +{ + log[x] = true; + count++; +} + +assert (count == 1 + && '5' in log); + +// 12. +log = {}; +count = 0; + +q = { c : 3, d : 4 }; + +function p_constructor () +{ + this.a = 1; + this.b = 2; + + return this; +} + +p_constructor.prototype = q; +p = new p_constructor (); + +Object.defineProperty (p, 'h', { value : 5, enumerable : false, configurable : true }); +Object.defineProperty (q, 'h', { value : 6, enumerable : true, configurable : true }); + +for (var i in p) +{ + log[i] = true; + count++; +} + +assert (count == 4 + && 'a' in log + && 'b' in log + && 'c' in log + && 'd' in log); + +// 13. +log = {}; +count = 0; + +function f() +{ + var tmp = { a: 1, b: 2, c: 3, d: 4 }; + + return tmp; +} + +for (var i in f()) +{ + log[i] = true; + count++; +} + +assert (count == 4 + && 'a' in log + && 'b' in log + && 'c' in log + && 'd' in log); + +// 14. +log = {}; +count = 0; + +b = 'prop'; +c = { prop : 1 }; +Boolean.prototype.boolean_prototype_prop = 1; + +for (a in b in c) +{ + log[a] = true; + count++; +} + +assert (count == 1 + && 'boolean_prototype_prop' in log); + +// 15. +log = {}; +count = 0; + +for (a in 'prop' in { prop : 1 }) +{ + log[a] = true; + count++; +} + +assert (count == 1 + && 'boolean_prototype_prop' in log); + +// 16. +a = 'str'; +b = {}; +for ((a in b) ; ; ) +{ + break; +} + +// 17. +log = {}; +count = 0; + +var base_obj = { base_prop1: "base1", base_prop2: "base2" }; + +function l () { + this.derived_prop1 = "derived1"; + this.derived_prop2 = "derived2"; +} + +l.prototype = base_obj; + +var derived_obj = new l(); + +for (var prop_of_derived_obj in derived_obj) { + delete derived_obj.derived_prop1; + delete derived_obj.derived_prop2; + delete base_obj.base_prop1; + delete base_obj.base_prop2; + + log[prop_of_derived_obj] = true; + count++; +} + +assert(count == 1 + && ('base_prop1' in log + || 'base_prop2' in log + || 'derived_prop1' in log + || 'derived_prop2' in log));