MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add function declaration detection to code parsing

+138 -88
+1
include/config.h
··· 64 64 SLOT_HOISTED_VARS, 65 65 SLOT_HOISTED_VARS_LEN, 66 66 SLOT_STRICT_EVAL_SCOPE, 67 + SLOT_NO_FUNC_DECLS, 67 68 SLOT_MAX = 255 68 69 } internal_slot_t; 69 70
+1
include/config.h.in
··· 53 53 SLOT_HOISTED_VARS, 54 54 SLOT_HOISTED_VARS_LEN, 55 55 SLOT_STRICT_EVAL_SCOPE, 56 + SLOT_NO_FUNC_DECLS, 56 57 SLOT_MAX = 255 57 58 } internal_slot_t; 58 59
+1
include/internal.h
··· 54 54 bool gc_suppress; // suppress GC during microtask batch processing 55 55 int eval_depth; // recursion depth of js_eval calls 56 56 int parse_depth; // recursion depth of parser (for stack overflow protection) 57 + bool skip_func_hoist; // skip function declaration hoisting (pre-computed) 57 58 58 59 // for-let loop context stack 59 60 struct for_let_ctx *for_let_stack;
+93 -88
src/ant.c
··· 806 806 807 807 static bool streq(const char *buf, size_t len, const char *p, size_t n); 808 808 static bool is_this_loop_continue_target(int depth_at_entry); 809 + static bool code_has_function_decl(const char *code, size_t len); 809 810 static bool parse_func_params(struct js *js, uint8_t *flags, int *out_count); 810 811 static bool try_dynamic_setter(struct js *js, jsval_t obj, const char *key, size_t key_len, jsval_t value); 811 812 ··· 3404 3405 if (!arena_code) return; 3405 3406 3406 3407 set_func_code_ptr(js, func_obj, arena_code, len); 3408 + if (!code_has_function_decl(code, len)) set_slot(js, func_obj, SLOT_NO_FUNC_DECLS, js_mktrue()); 3409 + 3407 3410 if (!memmem(code, len, "var", 3)) return; 3408 3411 3409 3412 size_t vars_buf_len; ··· 4052 4055 4053 4056 #define likely(x) __builtin_expect(!!(x), 1) 4054 4057 #define unlikely(x) __builtin_expect(!!(x), 0) 4058 + 4059 + static inline bool is_function_keyword(const char *code, jsoff_t pos, jsoff_t end) { 4060 + if (pos + 8 > end) return false; 4061 + uint64_t word; 4062 + memcpy(&word, code + pos, 8); 4063 + 4064 + if (word != 0x6e6f6974636e7566ULL) return false; 4065 + return (pos + 8 >= end) || !IS_IDENT(code[pos + 8]); 4066 + } 4067 + 4068 + static inline bool is_async_function(const char *code, jsoff_t pos, jsoff_t end) { 4069 + if (pos + 5 > end) return false; 4070 + 4071 + uint32_t word4; 4072 + memcpy(&word4, code + pos, 4); 4073 + 4074 + if (word4 != 0x6e797361U || code[pos + 4] != 'c') return false; 4075 + if (pos + 5 < end && IS_IDENT(code[pos + 5])) return false; 4076 + 4077 + jsoff_t scan = pos + 5; 4078 + while (scan < end && ( 4079 + code[scan] == ' ' || 4080 + code[scan] == '\t' || 4081 + code[scan] == '\n' || 4082 + code[scan] == '\r' 4083 + )) scan++; 4084 + 4085 + return is_function_keyword(code, scan, end); 4086 + } 4087 + 4088 + static bool code_has_function_decl(const char *code, size_t len) { 4089 + if (!memmem(code, len, "function", 8)) return false; 4090 + 4091 + size_t pos = 0; 4092 + int depth = 0; 4093 + 4094 + while (pos < len) { 4095 + uint8_t c = (uint8_t)code[pos]; 4096 + 4097 + if (c == '"' || c == '\'' || c == '`') { 4098 + uint8_t quote = c; 4099 + pos++; 4100 + while (pos < len && (uint8_t)code[pos] != quote) { 4101 + if (code[pos] == '\\' && pos + 1 < len) pos++; 4102 + pos++; 4103 + } 4104 + if (pos < len) pos++; 4105 + continue; 4106 + } 4107 + 4108 + if (c == '/' && pos + 1 < len) { 4109 + if (code[pos + 1] == '/') { 4110 + pos += 2; 4111 + while (pos < len && code[pos] != '\n') pos++; 4112 + continue; 4113 + } 4114 + if (code[pos + 1] == '*') { 4115 + pos += 2; 4116 + while (pos + 1 < len && !(code[pos] == '*' && code[pos + 1] == '/')) pos++; 4117 + if (pos + 1 < len) pos += 2; 4118 + continue; 4119 + } 4120 + } 4121 + 4122 + if (c == '{') { depth++; pos++; continue; } 4123 + if (c == '}') { 4124 + if (depth == 0) break; 4125 + depth--; pos++; continue; 4126 + } 4127 + 4128 + if (depth == 0) { 4129 + if (c == 'f' && is_function_keyword(code, pos, len)) return true; 4130 + if (c == 'a' && is_async_function(code, pos, len)) return true; 4131 + } 4132 + 4133 + pos++; 4134 + } 4135 + 4136 + return false; 4137 + } 4055 4138 4056 4139 static const uint8_t single_char_tok[128] = { 4057 4140 ['('] = TOK_LPAREN, ··· 4868 4951 static jsval_t js_func_decl(struct js *js); 4869 4952 static jsval_t js_func_decl_async(struct js *js); 4870 4953 4871 - static inline bool is_function_keyword(const char *code, jsoff_t pos, jsoff_t end) { 4872 - if (pos + 8 > end) return false; 4873 - uint64_t word; 4874 - memcpy(&word, code + pos, 8); 4875 - 4876 - if (word != 0x6e6f6974636e7566ULL) return false; 4877 - return (pos + 8 >= end) || !IS_IDENT(code[pos + 8]); 4878 - } 4879 - 4880 - static inline bool is_async_function(const char *code, jsoff_t pos, jsoff_t end) { 4881 - if (pos + 5 > end) return false; 4882 - 4883 - uint32_t word4; 4884 - memcpy(&word4, code + pos, 4); 4885 - 4886 - if (word4 != 0x6e797361U || code[pos + 4] != 'c') return false; 4887 - if (pos + 5 < end && IS_IDENT(code[pos + 5])) return false; 4888 - 4889 - jsoff_t scan = pos + 5; 4890 - while (scan < end && ( 4891 - code[scan] == ' ' || 4892 - code[scan] == '\t' || 4893 - code[scan] == '\n' || 4894 - code[scan] == '\r' 4895 - )) scan++; 4896 - 4897 - return is_function_keyword(code, scan, end); 4898 - } 4899 - 4900 - static bool block_has_function_decl(struct js *js) { 4901 - const char *code = js->code; 4902 - jsoff_t pos = js->pos; 4903 - jsoff_t end = js->clen; 4904 - int depth = 0; 4905 - 4906 - while (pos < end) { 4907 - uint8_t c = (uint8_t)code[pos]; 4908 - 4909 - if (c == '"' || c == '\'' || c == '`') { 4910 - uint8_t quote = c; 4911 - pos++; 4912 - while (pos < end && (uint8_t)code[pos] != quote) { 4913 - if (code[pos] == '\\' && pos + 1 < end) pos++; 4914 - pos++; 4915 - } 4916 - if (pos < end) pos++; 4917 - continue; 4918 - } 4919 - 4920 - if (c == '/' && pos + 1 < end) { 4921 - if (code[pos + 1] == '/') { 4922 - pos += 2; 4923 - while (pos < end && code[pos] != '\n') pos++; 4924 - continue; 4925 - } 4926 - if (code[pos + 1] == '*') { 4927 - pos += 2; 4928 - while (pos + 1 < end && !(code[pos] == '*' && code[pos + 1] == '/')) pos++; 4929 - if (pos + 1 < end) pos += 2; 4930 - continue; 4931 - } 4932 - } 4933 - 4934 - if (c == '{') { depth++; pos++; continue; } 4935 - if (c == '}') { 4936 - if (depth == 0) break; 4937 - depth--; pos++; continue; 4938 - } 4939 - 4940 - if (depth == 0) { 4941 - if (c == 'f' && is_function_keyword(code, pos, end)) return true; 4942 - if (c == 'a' && is_async_function(code, pos, end)) return true; 4943 - } 4944 - 4945 - pos++; 4946 - } 4947 - 4948 - return false; 4949 - } 4950 - 4951 4954 static void hoist_function_declarations(struct js *js) { 4952 4955 if (js->flags & F_NOEXEC) return; 4953 4956 if (js->is_hoisting) return; 4954 4957 4955 - if (!memmem(js->code + js->pos, js->clen - js->pos, "function", 8)) return; 4956 - if (!block_has_function_decl(js)) return; 4958 + if (js->skip_func_hoist) return; 4959 + if (!code_has_function_decl(js->code + js->pos, js->clen - js->pos)) return; 4957 4960 4958 4961 js->is_hoisting = true; 4959 - 4960 4962 js_parse_state_t saved; 4961 4963 JS_SAVE_STATE(js, saved); 4962 4964 jsval_t saved_scope = js->scope; ··· 6573 6575 if (vtype(func_val) == T_FUNC) { 6574 6576 jsval_t func_obj = mkval(T_OBJ, vdata(func_val)); 6575 6577 hoist_var_declarations_from_slot(js, function_scope, func_obj); 6576 - } 6578 + jsval_t no_func_decls = get_slot(js, func_obj, SLOT_NO_FUNC_DECLS); 6579 + js->skip_func_hoist = (vtype(no_func_decls) == T_BOOL && vdata(no_func_decls) == 1); 6580 + } else js->skip_func_hoist = false; 6577 6581 6578 6582 if (func_strict && (vtype(target_this) == T_UNDEF || vtype(target_this) == T_NULL || 6579 6583 (vtype(target_this) == T_OBJ && vdata(target_this) == 0))) { 6580 6584 js->this_val = js_mkundef(); 6581 - } else { 6582 - js->this_val = target_this; 6583 - } 6585 + } else js->this_val = target_this; 6586 + 6584 6587 js->flags = F_CALL | (func_strict ? F_STRICT : 0); 6588 + jsval_t res = js_eval(js, &fn[pf->body_start], pf->body_len); 6589 + js->skip_func_hoist = false; 6585 6590 6586 - jsval_t res = js_eval(js, &fn[pf->body_start], pf->body_len); 6587 6591 if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); 6588 6592 if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack); 6589 6593 6590 6594 utarray_free(args_arr); 6591 6595 restore_saved_scope(js); 6596 + 6592 6597 return res; 6593 6598 } 6594 6599
+42
tests/microbench_hoist.js
··· 1 + // Benchmark to test function declaration hoisting optimization 2 + // The function contains "function" in strings/comments but no actual declarations 3 + 4 + function processItem(item) { 5 + // This comment mentions function declaration hoisting 6 + // The word "function" appears here: function function function 7 + let type = "function"; // string contains "function" 8 + let desc = "This is a function test"; 9 + let x = item * 2; 10 + let y = x + 1; 11 + return y + type.length + desc.length; 12 + } 13 + 14 + const iterations = 200000; 15 + console.log(`Running ${iterations} iterations...`); 16 + 17 + let start = Date.now(); 18 + let result = 0; 19 + 20 + for (let i = 0; i < iterations; i++) { 21 + result += processItem(i); 22 + } 23 + 24 + console.log(`with "function" in body: ${Date.now() - start}ms (result: ${result})`); 25 + 26 + // Compare with a function that doesn't have "function" anywhere 27 + function processClean(item) { 28 + let type = "method"; 29 + let desc = "This is a test"; 30 + let x = item * 2; 31 + let y = x + 1; 32 + return y + type.length + desc.length; 33 + } 34 + 35 + start = Date.now(); 36 + result = 0; 37 + 38 + for (let i = 0; i < iterations; i++) { 39 + result += processClean(i); 40 + } 41 + 42 + console.log(`without "function" in body: ${Date.now() - start}ms (result: ${result})`);