Skip to content

Commit 0d36379

Browse files
committed
Omit _Formals from duk_hcompfunc when possible
1 parent b750406 commit 0d36379

4 files changed

Lines changed: 86 additions & 48 deletions

File tree

src-input/duk_api_bytecode.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,15 @@ DUK_LOCAL duk_uint8_t *duk__dump_formals(duk_hthread *thr, duk_uint8_t *p, duk_b
205205
*/
206206
varname = DUK_TVAL_GET_STRING(tv_val);
207207
DUK_ASSERT(varname != NULL);
208+
DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(varname) >= 1); /* won't be confused with terminator */
208209

209210
DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */
210211
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4 + DUK_HSTRING_GET_BYTELEN(varname), p);
211212
p = duk__dump_hstring_raw(p, varname);
212213
}
213214
}
215+
} else {
216+
DUK_DD(DUK_DDPRINT("dumping function without _Formals, emit empty list"));
214217
}
215218
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4, p);
216219
DUK_RAW_WRITE_U32_BE(p, 0); /* end of _Formals */
@@ -625,6 +628,11 @@ static duk_uint8_t *duk__load_func(duk_context *ctx, duk_uint8_t *p, duk_uint8_t
625628
duk_compact(ctx, -1);
626629
duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE);
627630

631+
/* If _Formals wasn't present in the original function, the list
632+
* here will be empty. Same happens if _Formals was present but
633+
* had zero length. We can omit _Formals from the result if its
634+
* length is zero and matches nargs.
635+
*/
628636
duk_push_array(ctx); /* _Formals */
629637
for (arr_idx = 0; ; arr_idx++) {
630638
/* XXX: awkward */
@@ -635,8 +643,12 @@ static duk_uint8_t *duk__load_func(duk_context *ctx, duk_uint8_t *p, duk_uint8_t
635643
}
636644
duk_put_prop_index(ctx, -2, arr_idx);
637645
}
638-
duk_compact(ctx, -1);
639-
duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE);
646+
if (arr_idx == 0 && h_fun->nargs == 0) {
647+
duk_pop(ctx);
648+
} else {
649+
duk_compact(ctx, -1);
650+
duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE);
651+
}
640652

641653
/* Return with final function pushed on stack top. */
642654
DUK_DD(DUK_DDPRINT("final loaded function: %!iT", duk_get_tval(ctx, -1)));

src-input/duk_js_call.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,16 @@ DUK_LOCAL void duk__create_arguments_object(duk_hthread *thr,
144144
duk_push_hobject(ctx, func);
145145
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_FORMALS);
146146
formals = duk_get_hobject(ctx, -1);
147-
n_formals = 0;
148147
if (formals) {
149-
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);
150-
n_formals = (duk_idx_t) duk_require_int(ctx, -1);
151-
duk_pop(ctx);
148+
n_formals = (duk_idx_t) duk_get_length(ctx, -1);
149+
} else {
150+
/* This shouldn't happen without tampering of internal
151+
* properties: if a function accesses 'arguments', _Formals
152+
* is kept. Check for the case anyway in case internal
153+
* properties have been modified manually.
154+
*/
155+
DUK_D(DUK_DPRINT("_Formals is undefined when creating arguments, use n_formals == 0"));
156+
n_formals = 0;
152157
}
153158
duk_remove(ctx, -2); /* leave formals on stack for later use */
154159
i_formals = duk_require_top_index(ctx);

src-input/duk_js_compiler.c

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,8 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx, duk_boo
658658
duk_instr_t *p_instr;
659659
duk_compiler_instr *q_instr;
660660
duk_tval *tv;
661+
duk_bool_t keep_varmap;
662+
duk_bool_t keep_formals;
661663

662664
DUK_DDD(DUK_DDDPRINT("converting duk_compiler_func to function/template"));
663665

@@ -791,6 +793,25 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx, duk_boo
791793

792794
duk_pop(ctx); /* 'data' (and everything in it) is reachable through h_res now */
793795

796+
/*
797+
* Init non-property result fields
798+
*
799+
* 'nregs' controls how large a register frame is allocated.
800+
*
801+
* 'nargs' controls how many formal arguments are written to registers:
802+
* r0, ... r(nargs-1). The remaining registers are initialized to
803+
* undefined.
804+
*/
805+
806+
DUK_ASSERT(func->temp_max >= 0);
807+
h_res->nregs = (duk_uint16_t) func->temp_max;
808+
h_res->nargs = (duk_uint16_t) duk_hobject_get_length(thr, func->h_argnames);
809+
DUK_ASSERT(h_res->nregs >= h_res->nargs); /* pass2 allocation handles this */
810+
#if defined(DUK_USE_DEBUGGER_SUPPORT)
811+
h_res->start_line = (duk_uint32_t) func->min_line;
812+
h_res->end_line = (duk_uint32_t) func->max_line;
813+
#endif
814+
794815
/*
795816
* Init object properties
796817
*
@@ -807,14 +828,20 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx, duk_boo
807828
* after a cleanup. When debugging is enabled, we always need the varmap to
808829
* be able to lookup variables at any point.
809830
*/
831+
810832
#if defined(DUK_USE_DEBUGGER_SUPPORT)
811-
if (1) {
833+
DUK_DD(DUK_DDPRINT("keeping _Varmap because debugger support is enabled"));
834+
keep_varmap = 1;
812835
#else
813-
if (func->id_access_slow || /* directly uses slow accesses */
814-
func->may_direct_eval || /* may indirectly slow access through a direct eval */
815-
funcs_count > 0) { /* has inner functions which may slow access (XXX: this can be optimized by looking at the inner functions) */
836+
keep_varmap =
837+
func->id_access_slow || /* directly uses slow accesses */
838+
func->may_direct_eval || /* may indirectly slow access through a direct eval */
839+
funcs_count > 0; /* has inner functions which may slow access (XXX: this can be optimized by looking at the inner functions) */
816840
#endif
841+
842+
if (keep_varmap) {
817843
duk_int_t num_used;
844+
DUK_DD(DUK_DDPRINT("keeping _Varmap"));
818845
duk_dup(ctx, func->varmap_idx);
819846
num_used = duk__cleanup_varmap(comp_ctx);
820847
DUK_DDD(DUK_DDDPRINT("cleaned up varmap: %!T (num_used=%ld)",
@@ -823,20 +850,35 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx, duk_boo
823850
if (num_used > 0) {
824851
duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE);
825852
} else {
826-
DUK_DDD(DUK_DDDPRINT("varmap is empty after cleanup -> no need to add"));
853+
DUK_DD(DUK_DDPRINT("varmap is empty after cleanup -> no need to add"));
827854
duk_pop(ctx);
828855
}
829856
}
830857

831-
/* _Formals: omitted if function is guaranteed not to need a (non-strict) arguments object */
832-
if (1) {
833-
/* XXX: Add a proper condition. If formals list is omitted, recheck
834-
* handling for 'length' in duk_js_push_closure(); it currently relies
835-
* on _Formals being set. Removal may need to be conditional to debugging
836-
* being enabled/disabled too.
837-
*/
858+
/* _Formals: omitted if function is guaranteed not to need a (non-strict)
859+
* arguments object, and _Formals.length matches nargs exactly.
860+
*
861+
* Non-arrow functions can't see an outer function's 'argument' binding
862+
* (because they have their own), but arrow functions can. When arrow
863+
* functions are added, this condition would need to be added:
864+
* inner_arrow_funcs_count > 0 inner arrow functions may access 'arguments'
865+
*/
866+
#if defined(DUK_USE_DEBUGGER_SUPPORT)
867+
DUK_DD(DUK_DDPRINT("keeping _Formals because debugger support is enabled"));
868+
keep_formals = 1;
869+
#else
870+
keep_formals =
871+
func->id_access_arguments || /* accesses 'arguments', main reason to keep, could check strictness too */
872+
func->may_direct_eval || /* direct eval -> may access 'arguments' via eval */
873+
duk_get_length(ctx, func->argnames_idx) != (duk_size_t) h_res->nargs; /* nargs not enough for closure .length */
874+
#endif
875+
876+
if (keep_formals) {
877+
DUK_DD(DUK_DDPRINT("keeping _Formals"));
838878
duk_dup(ctx, func->argnames_idx);
839879
duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE);
880+
} else {
881+
DUK_DD(DUK_DDPRINT("omitting _Formals, nargs matches _Formals.length, so no properties added"));
840882
}
841883

842884
/* name */
@@ -920,25 +962,6 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx, duk_boo
920962
duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_NONE);
921963
}
922964

923-
/*
924-
* Init remaining result fields
925-
*
926-
* 'nregs' controls how large a register frame is allocated.
927-
*
928-
* 'nargs' controls how many formal arguments are written to registers:
929-
* r0, ... r(nargs-1). The remaining registers are initialized to
930-
* undefined.
931-
*/
932-
933-
DUK_ASSERT(func->temp_max >= 0);
934-
h_res->nregs = (duk_uint16_t) func->temp_max;
935-
h_res->nargs = (duk_uint16_t) duk_hobject_get_length(thr, func->h_argnames);
936-
DUK_ASSERT(h_res->nregs >= h_res->nargs); /* pass2 allocation handles this */
937-
#if defined(DUK_USE_DEBUGGER_SUPPORT)
938-
h_res->start_line = (duk_uint32_t) func->min_line;
939-
h_res->end_line = (duk_uint32_t) func->max_line;
940-
#endif
941-
942965
DUK_DD(DUK_DDPRINT("converted function: %!ixT",
943966
(duk_tval *) duk_get_tval(ctx, -1)));
944967

src-input/duk_js_var.c

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -337,24 +337,22 @@ void duk_js_push_closure(duk_hthread *thr,
337337
/*
338338
* "length" maps to number of formals (E5 Section 13.2) for function
339339
* declarations/expressions (non-bound functions). Note that 'nargs'
340-
* is NOT necessarily equal to the number of arguments.
340+
* is NOT necessarily equal to the number of arguments. Use length
341+
* of _Formals; if missing, assume nargs matches .length.
341342
*/
342343

343344
/* [ ... closure template ] */
344345

345-
len_value = 0;
346-
347-
/* XXX: use helper for size optimization */
348-
if (duk_get_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS)) {
346+
/* XXX: these lookups should be just own property lookups instead of
347+
* looking up the inheritance chain.
348+
*/
349+
if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_FORMALS)) {
349350
/* [ ... closure template formals ] */
350-
DUK_ASSERT(duk_has_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH));
351-
DUK_ASSERT(duk_get_length(ctx, -1) <= DUK_UINT_MAX); /* formal arg limits */
352351
len_value = (duk_uint_t) duk_get_length(ctx, -1); /* could access duk_harray directly, not important */
352+
DUK_DD(DUK_DDPRINT("closure length from _Formals -> %ld", (long) len_value));
353353
} else {
354-
/* XXX: what to do if _Formals is not empty but compiler has
355-
* optimized it away -- read length from an explicit property
356-
* then?
357-
*/
354+
len_value = fun_temp->nargs;
355+
DUK_DD(DUK_DDPRINT("closure length defaulted from nargs -> %ld", (long) len_value));
358356
}
359357
duk_pop(ctx);
360358

0 commit comments

Comments
 (0)