diff --git a/src-input/duk_api_stack.c b/src-input/duk_api_stack.c index dc838a1ee0..e3f8f6b001 100644 --- a/src-input/duk_api_stack.c +++ b/src-input/duk_api_stack.c @@ -5138,6 +5138,10 @@ DUK_LOCAL duk_idx_t duk__push_c_function_raw(duk_hthread *thr, duk_c_function fu DUK_ASSERT_BIDX_VALID(proto_bidx); DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) obj, thr->builtins[proto_bidx]); + + if (flags & DUK_HOBJECT_FLAG_CONSTRUCTABLE) { + DUK_STATS_INC(thr->heap, stats_object_proto_potential); + } return ret; api_error: diff --git a/src-input/duk_heap.h b/src-input/duk_heap.h index 57136f9165..4276d0ed34 100644 --- a/src-input/duk_heap.h +++ b/src-input/duk_heap.h @@ -605,6 +605,8 @@ struct duk_heap { duk_int_t stats_strtab_litcache_pin; duk_int_t stats_object_realloc_props; duk_int_t stats_object_abandon_array; + duk_int_t stats_object_proto_potential; + duk_int_t stats_object_proto_create; duk_int_t stats_getownpropdesc_count; duk_int_t stats_getownpropdesc_hit; duk_int_t stats_getownpropdesc_miss; diff --git a/src-input/duk_heap_markandsweep.c b/src-input/duk_heap_markandsweep.c index 48555c0dcd..7052030266 100644 --- a/src-input/duk_heap_markandsweep.c +++ b/src-input/duk_heap_markandsweep.c @@ -1125,8 +1125,10 @@ DUK_LOCAL void duk__dump_stats(duk_heap *heap) { (long) heap->stats_strtab_resize_check, (long) heap->stats_strtab_resize_grow, (long) heap->stats_strtab_resize_shrink, (long) heap->stats_strtab_litcache_hit, (long) heap->stats_strtab_litcache_miss, (long) heap->stats_strtab_litcache_pin)); - DUK_D(DUK_DPRINT("stats object: realloc_props=%ld, abandon_array=%ld", - (long) heap->stats_object_realloc_props, (long) heap->stats_object_abandon_array)); + DUK_D(DUK_DPRINT("stats object: realloc_props=%ld, abandon_array=%ld, " + "proto_potential=%ld, proto_create=%ld", + (long) heap->stats_object_realloc_props, (long) heap->stats_object_abandon_array, + (long) heap->stats_object_proto_potential, (long) heap->stats_object_proto_create)); DUK_D(DUK_DPRINT("stats getownpropdesc: count=%ld, hit=%ld, miss=%ld", (long) heap->stats_getownpropdesc_count, (long) heap->stats_getownpropdesc_hit, (long) heap->stats_getownpropdesc_miss)); diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index 1da0ec9352..5bc386eb83 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -351,6 +351,75 @@ DUK_LOCAL duk_bool_t duk__abandon_array_slow_check_required(duk_uint32_t arr_idx return (arr_idx > DUK_USE_HOBJECT_ARRAY_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3)); } +/* + * On-demand .prototype for functions + * + * The automatic .prototype property of function objects is writable, but + * not enumerable or configurable. We create the property on-demand when + * its presence matters: + * + * - On a direct property access with the property key "prototype". + * - Object.defineProperty(). + * - Object.getOwnPropertyKeys() or other enumeration which also includes + * non-enumerable keys. In these cases only the string "prototype" + * must be returned, the property itself doesn't (yet) need to be created. + * + * Because .prototype is non-configurable, it cannot be deleted. So it should + * be safe to add the property if it is missing and then never delete it. + */ + +/* XXX: separate flag for virtual .prototype, not just any constructable + * function lacking the property? + */ +/* XXX: DUK_DEFPROP_FORCE vs. deletion of .prototype */ +/* XXX: enumeration order */ + +DUK_INTERNAL duk_bool_t duk_hobject_ondemand_proto_check(duk_hthread *thr, duk_hobject *obj) { + duk_int_t e_idx; + duk_int_t h_idx; + duk_hstring **keyptr; + duk_bool_t rc; + + /* Automatic .prototype property only applies to constructable + * functions, e.g. function expressions, not e.g. object literal + * getters/setters, arrow functions, etc. + */ + if (!DUK_HOBJECT_HAS_CONSTRUCTABLE(obj)) { + return 0; + } + /* Assumption: caller has already checked that the property doesn't + * exist. + */ + DUK_STATS_INC(thr->heap, stats_object_proto_create); + DUK_D(DUK_DPRINT("create .prototype on-demand for %!O", obj)); + duk_push_hobject(thr, obj); + duk_push_object(thr); /* -> [ ... func proto ] */ + duk_dup_m2(thr); /* -> [ ... func proto func ] */ + duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC | DUK_DEFPROP_FORCE); /* -> [ ... func proto ] */ + duk_compact(thr, -1); /* compact the prototype */ + + /* When we add the .prototype property we don't want to do a property + * descriptor lookup because it'd cause infinite recursion. For now, + * define the property using a different name and then rename it in + * place. + */ + duk_xdef_prop_stridx(thr, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_W | DUK_DEFPROP_FORCE); /* -> [ ... func ] */ + + rc = duk_hobject_find_existing_entry(thr->heap, obj, DUK_HTHREAD_STRING_INT_TARGET(thr), &e_idx, &h_idx); + DUK_UNREF(rc); + DUK_ASSERT(rc != 0); + DUK_ASSERT(e_idx >= 0); + keyptr = DUK_HOBJECT_E_GET_KEY_PTR(thr->heap, obj, e_idx); + DUK_ASSERT(keyptr != NULL); + DUK_ASSERT(*keyptr == DUK_HTHREAD_STRING_INT_TARGET(thr)); + *keyptr = DUK_HTHREAD_STRING_PROTOTYPE(thr); + DUK_HSTRING_INCREF(thr, DUK_HTHREAD_STRING_PROTOTYPE(thr)); + DUK_HSTRING_DECREF(thr, DUK_HTHREAD_STRING_INT_TARGET(thr)); + + duk_pop_unsafe(thr); + return 1; +} + /* * Proxy helpers */ @@ -1753,6 +1822,21 @@ DUK_LOCAL duk_bool_t duk__get_own_propdesc_raw(duk_hthread *thr, duk_hobject *ob * Not found as a concrete property, check for virtual properties. */ + /* If object has an on-demand .prototype property, create it now and + * retry via recursion. + */ + /* XXX: faster check for key, e.g. by adding DUK_HSTRING_IS_PROTOTYPE */ + /* XXX: enable "has virtual properties" for at least constructable functions, + * so that we can avoid this check in the fast path? + */ + if (DUK_UNLIKELY(key == DUK_HTHREAD_STRING_PROTOTYPE(thr))) { + DUK_D(DUK_DPRINT("ondemand .property, missing -> create?")); + if (duk_hobject_ondemand_proto_check(thr, obj)) { + DUK_D(DUK_DPRINT("ondemand .property, retry own propdesc lookup")); + return duk__get_own_propdesc_raw(thr, obj, key, arr_idx, out_desc, flags); + } + } + if (!DUK_HOBJECT_HAS_VIRTUAL_PROPERTIES(obj)) { /* Quick skip. */ goto prop_not_found; diff --git a/src-input/duk_hstring.h b/src-input/duk_hstring.h index 04698de907..02ced3a118 100644 --- a/src-input/duk_hstring.h +++ b/src-input/duk_hstring.h @@ -41,6 +41,7 @@ /* With lowmem builds the high 16 bits of duk_heaphdr are used for other * purposes, so this leaves 7 duk_heaphdr flags and 9 duk_hstring flags. */ +/* FIXME: eval_or_arguments -> FLAG_PROTOTYPE */ #define DUK_HSTRING_FLAG_ASCII DUK_HEAPHDR_USER_FLAG(0) /* string is ASCII, clen == blen */ #define DUK_HSTRING_FLAG_ARRIDX DUK_HEAPHDR_USER_FLAG(1) /* string is a valid array index */ #define DUK_HSTRING_FLAG_SYMBOL DUK_HEAPHDR_USER_FLAG(2) /* string is a symbol (invalid utf-8) */ diff --git a/src-input/duk_js_var.c b/src-input/duk_js_var.c index 715644dda3..bc4b3eb2a6 100644 --- a/src-input/duk_js_var.c +++ b/src-input/duk_js_var.c @@ -395,11 +395,14 @@ void duk_js_push_closure(duk_hthread *thr, /* [ ... closure template ] */ if (add_auto_proto) { + DUK_STATS_INC(thr->heap, stats_object_proto_potential); +#if 0 /* FIXME: testing */ duk_push_object(thr); /* -> [ ... closure template newobj ] */ duk_dup_m3(thr); /* -> [ ... closure template newobj closure ] */ duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC); /* -> [ ... closure template newobj ] */ duk_compact(thr, -1); /* compact the prototype */ duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_PROTOTYPE, DUK_PROPDESC_FLAGS_W); /* -> [ ... closure template ] */ +#endif } /* @@ -470,7 +473,9 @@ void duk_js_push_closure(duk_hthread *thr, DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, &fun_clos->obj) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(&fun_clos->obj)); DUK_ASSERT(duk_has_prop_stridx(thr, -2, DUK_STRIDX_LENGTH) != 0); +#if 0 /* FIXME: has check triggers creation of .prototype! */ DUK_ASSERT(add_auto_proto == 0 || duk_has_prop_stridx(thr, -2, DUK_STRIDX_PROTOTYPE) != 0); +#endif /* May be missing .name */ DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj) || duk_has_prop_stridx(thr, -2, DUK_STRIDX_CALLER) != 0);