diff --git a/Makefile b/Makefile index d079822153..e293946042 100644 --- a/Makefile +++ b/Makefile @@ -79,9 +79,9 @@ CONFIGOPTS_NONDEBUG=--option-file util/makeduk_base.yaml CONFIGOPTS_NONDEBUG_SCANBUILD=--option-file util/makeduk_base.yaml --option-file util/makeduk_scanbuild.yaml CONFIGOPTS_NONDEBUG_PERF=--option-file config/examples/performance_sensitive.yaml CONFIGOPTS_NONDEBUG_SIZE=--option-file config/examples/low_memory.yaml -CONFIGOPTS_NONDEBUG_AJDUK=--option-file util/makeduk_ajduk.yaml --fixup-file util/makeduk_ajduk_fixup.h +CONFIGOPTS_NONDEBUG_AJDUK=--option-file config/examples/low_memory.yaml --option-file util/makeduk_ajduk.yaml --fixup-file util/makeduk_ajduk_fixup.h CONFIGOPTS_NONDEBUG_ROM=--rom-support --rom-auto-lightfunc --option-file util/makeduk_base.yaml -DDUK_USE_ROM_STRINGS -DDUK_USE_ROM_OBJECTS -DDUK_USE_ROM_GLOBAL_INHERIT -UDUK_USE_HSTRING_ARRIDX -CONFIGOPTS_NONDEBUG_AJDUK_ROM=--rom-support --rom-auto-lightfunc --option-file util/makeduk_ajduk.yaml --fixup-file util/makeduk_ajduk_fixup.h --builtin-file util/example_user_builtins1.yaml --builtin-file util/example_user_builtins2.yaml -DDUK_USE_ROM_STRINGS -DDUK_USE_ROM_OBJECTS -DDUK_USE_ROM_GLOBAL_INHERIT -UDUK_USE_HSTRING_ARRIDX -UDUK_USE_DEBUG +CONFIGOPTS_NONDEBUG_AJDUK_ROM=--rom-support --rom-auto-lightfunc --option-file config/examples/low_memory.yaml --option-file util/makeduk_ajduk.yaml --fixup-file util/makeduk_ajduk_fixup.h --builtin-file util/example_user_builtins1.yaml --builtin-file util/example_user_builtins2.yaml -DDUK_USE_ROM_STRINGS -DDUK_USE_ROM_OBJECTS -DDUK_USE_ROM_GLOBAL_INHERIT -UDUK_USE_HSTRING_ARRIDX -DDUK_USE_ASSERTIONS -UDUK_USE_DEBUG CONFIGOPTS_NONDEBUG_AJDUK_NOREFC=--option-file util/makeduk_base.yaml --option-file util/makeduk_ajduk.yaml --fixup-file util/makeduk_ajduk_fixup.h -UDUK_USE_REFERENCE_COUNTING -UDUK_USE_DOUBLE_LINKED_HEAP CONFIGOPTS_DEBUG=--option-file util/makeduk_base.yaml --option-file util/makeduk_debug.yaml CONFIGOPTS_DEBUG_SCANBUILD=--option-file util/makeduk_base.yaml --option-file util/makeduk_debug.yaml --option-file util/makeduk_scanbuild.yaml diff --git a/RELEASES.rst b/RELEASES.rst index 7033a5892b..9fa6127bd3 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -2670,6 +2670,10 @@ Planned in a state which prevented mark-and-sweep from fully working afterwards (GH-1427) +* Add experimental support for mapping Ecmascript function 'data' area into a + user provided external buffer on-the-fly which is useful on some low memory + targets (GH-1409) + * Fix a garbage collection bug where a finalizer triggered by mark-and-sweep could cause a recursive entry into mark-and-sweep (leading to memory unsafe behavior) if the voluntary GC trigger counter dropped to zero during diff --git a/config/config-options/DUK_USE_EXTBC_CHECK.yaml b/config/config-options/DUK_USE_EXTBC_CHECK.yaml new file mode 100644 index 0000000000..051f5ae357 --- /dev/null +++ b/config/config-options/DUK_USE_EXTBC_CHECK.yaml @@ -0,0 +1,22 @@ +define: DUK_USE_EXTBC_CHECK +introduced: 2.0.0 +default: false +tags: + - memory + - experimental +description: > + Provide a hook for checking if a compiled Ecmascript function's bytecode + (which is free of dynamic pointer values) should be mapped to a user + supplied data area. + + The user callback gets three arguments: heap userdata, data pointer, and + length. If the user code wants to map that bytecode data area into a user + supplied data area, it returns replacement pointer and NULL otherwise. + + The returned pointer MUST remain valid until Duktape is guaranteed to no + longer reference it. At present there's no integration to notify the + application of the data area becoming unreferenced (to be fixed in separate + pulls). + + See doc/low-memory.rst for more discussion how to use this feature option + in practice. diff --git a/config/examples/low_memory.yaml b/config/examples/low_memory.yaml index 4afb1fab39..d3c2c5b5bb 100644 --- a/config/examples/low_memory.yaml +++ b/config/examples/low_memory.yaml @@ -77,6 +77,9 @@ DUK_USE_HOBJECT_HASH_PROP_LIMIT: 64 #DUK_USE_EXTSTR_INTERN_CHECK #DUK_USE_EXTSTR_FREE +# Consider using bytecode mapping to external buffer, see doc/low-memory.rst. +#DUK_USE_EXTBC_CHECK + # Consider removing Node.js Buffer and ES2015 typed array support if not # needed (about 10 kB code footprint difference on x64) DUK_USE_BUFFEROBJECT_SUPPORT: false diff --git a/doc/low-memory.rst b/doc/low-memory.rst index b45139fb59..736a178960 100644 --- a/doc/low-memory.rst +++ b/doc/low-memory.rst @@ -46,6 +46,9 @@ realistic memory targets are roughly: - Requires use of ROM strings and objects to reduce Duktape startup RAM usage (which drops to around 2-3kB with ROM strings/objects). + - Mapping Ecmascript function bytecode to ROM (read-only code section) + may be useful. + * 128kB system flash memory (code) and 32kB system RAM - Requires the above, and removing built-in bindings like the global @@ -343,6 +346,17 @@ system RAM): - As of Duktape 1.5 an alternative to external strings is to move strings (including the string heap header) to ROM, see below. +* Enable mapping compiled Ecmascript function bytecode, i.e. the opcodes + but not constants which involve pointers, into a user supplied data area. + The data area can be e.g. memory mapped flash, or even read-only memory + if known bytecode sequences are pre-compiled and cached. + + - ``#define DUK_USE_EXTBC_CHECK(udata,ptr,len)``, return non-NULL if + bytecode at ``[ptr,ptr+len[`` has been moved to a user supplied data + area. + + - See config option description for details. + * Enable struct packing in compiler options if your platform doesn't have strict alignment requirements, e.g. on gcc/x86 you can: diff --git a/examples/cmdline/duk_cmdline_ajduk.c b/examples/cmdline/duk_cmdline_ajduk.c index ecc571ab18..ce08b05feb 100644 --- a/examples/cmdline/duk_cmdline_ajduk.c +++ b/examples/cmdline/duk_cmdline_ajduk.c @@ -87,18 +87,18 @@ static const AJS_HeapConfig ajsheap_config[] = { { 16, 300, AJS_POOL_BORROW, 0 }, { 20, 300, AJS_POOL_BORROW, 0 }, { 24, 300, AJS_POOL_BORROW, 0 }, - { 28, 250, AJS_POOL_BORROW, 0 }, - { 32, 150, AJS_POOL_BORROW, 0 }, - { 40, 150, AJS_POOL_BORROW, 0 }, + { 28, 300, AJS_POOL_BORROW, 0 }, + { 32, 300, AJS_POOL_BORROW, 0 }, + { 40, 300, AJS_POOL_BORROW, 0 }, { 48, 50, AJS_POOL_BORROW, 0 }, { 52, 50, AJS_POOL_BORROW, 0 }, { 56, 50, AJS_POOL_BORROW, 0 }, { 60, 50, AJS_POOL_BORROW, 0 }, - { 64, 50, AJS_POOL_BORROW, 0 }, + { 64, 150, AJS_POOL_BORROW, 0 }, { 96, 50, AJS_POOL_BORROW, 0 }, - { 128, 80, AJS_POOL_BORROW, 0 }, + { 128, 200, AJS_POOL_BORROW, 0 }, { 200, 1, AJS_POOL_BORROW, 0 }, /* duk_heap, with heap ptr compression, ROM strings+objects */ - { 256, 16, AJS_POOL_BORROW, 0 }, + { 256, 64, AJS_POOL_BORROW, 0 }, { 288, 1, AJS_POOL_BORROW, 0 }, { 320, 1, AJS_POOL_BORROW, 0 }, { 396, 1, AJS_POOL_BORROW, 0 }, /* duk_hthread, with heap ptr compression, ROM strings+objects */ @@ -1005,6 +1005,48 @@ duk_bool_t ajsheap_exec_timeout_check(void *udata) { return 0; } +/* + * Ecmascript bytecode "interning" + */ + +void *ajsheap_extbc_check(void *ptr, duk_size_t len) { + /* Very hacky example where any function 'data' which isn't tiny is + * mapped on an external buffer. There's no release mechanism for + * the buffer now so the malloc()'d external buffers just leak now. + */ + + if (len > 8) { + void *tmp = malloc(len); + if (!tmp) { + return NULL; + } + memcpy(tmp, ptr, len); +#if 0 + { + duk_size_t i; + printf("EXTBC DUMP:"); + for (i = 0; i < len; i++) { + printf("%02x", (unsigned int) ((unsigned char *) tmp)[i]); + } + printf("\n"); + } +#endif +#if 0 + printf("ajsheap external bytecode, mapped ptr=%p, len=%ld -> %p\n", + ptr, (long) len, tmp); + fflush(stdout); +#endif + return tmp; + } else { +#if 0 + printf("ajsheap external bytecode check: ptr=%p, len=%ld -> NULL (not moved to external storage)\n", + ptr, (long) len); + fflush(stdout); +#endif + return NULL; + } +} + #else /* DUK_CMDLINE_AJSHEAP */ int ajs_dummy = 0; /* to avoid empty source file */ diff --git a/src-input/duk_debug_vsnprintf.c b/src-input/duk_debug_vsnprintf.c index 85a1502d9e..2707fd2a27 100644 --- a/src-input/duk_debug_vsnprintf.c +++ b/src-input/duk_debug_vsnprintf.c @@ -493,6 +493,7 @@ DUK_LOCAL void duk__print_hobject(duk__dprint_state *st, duk_hobject *h) { DUK__COMMA(); duk_fb_sprintf(fb, "__length_nonwritable:%ld", (long) a->length_nonwritable); } else if (st->internal && DUK_HOBJECT_IS_COMPFUNC(h)) { duk_hcompfunc *f = (duk_hcompfunc *) h; + DUK__COMMA(); duk_fb_sprintf(fb, "__bytecode:%p", (void *) f->bytecode); DUK__COMMA(); duk_fb_put_cstring(fb, "__data:"); duk__print_hbuffer(st, (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(NULL, f)); DUK__COMMA(); duk_fb_put_cstring(fb, "__lexenv:"); duk__print_hobject(st, DUK_HCOMPFUNC_GET_LEXENV(NULL, f)); diff --git a/src-input/duk_hbuffer.h b/src-input/duk_hbuffer.h index 1f6879dee6..f72510b8f4 100644 --- a/src-input/duk_hbuffer.h +++ b/src-input/duk_hbuffer.h @@ -101,7 +101,7 @@ #if defined(DUK_USE_HEAPPTR16) #define DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap,x) \ - ((void *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, ((duk_heaphdr *) (x))->h_extra16)) + ((duk_uint8_t *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, ((duk_heaphdr *) (x))->h_extra16)) #define DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(heap,x,v) do { \ ((duk_heaphdr *) (x))->h_extra16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) @@ -111,10 +111,10 @@ #else #define DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap,x) ((x)->curr_alloc) #define DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(heap,x,v) do { \ - (x)->curr_alloc = (void *) (v); \ + (x)->curr_alloc = (duk_uint8_t *) (v); \ } while (0) #define DUK_HBUFFER_DYNAMIC_SET_DATA_PTR_NULL(heap,x) do { \ - (x)->curr_alloc = (void *) NULL; \ + (x)->curr_alloc = (duk_uint8_t *) NULL; \ } while (0) #endif @@ -123,21 +123,21 @@ */ #if defined(DUK_USE_HEAPPTR16) #define DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(heap,x) \ - ((void *) (x)->curr_alloc) + ((duk_uint8_t *) (x)->curr_alloc) #define DUK_HBUFFER_EXTERNAL_SET_DATA_PTR(heap,x,v) do { \ - (x)->curr_alloc = (void *) (v); \ + (x)->curr_alloc = (duk_uint8_t *) (v); \ } while (0) #define DUK_HBUFFER_EXTERNAL_SET_DATA_PTR_NULL(heap,x) do { \ - (x)->curr_alloc = (void *) NULL; \ + (x)->curr_alloc = (duk_uint8_t *) NULL; \ } while (0) #else #define DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(heap,x) \ ((void *) (x)->curr_alloc) #define DUK_HBUFFER_EXTERNAL_SET_DATA_PTR(heap,x,v) do { \ - (x)->curr_alloc = (void *) (v); \ + (x)->curr_alloc = (duk_uint8_t *) (v); \ } while (0) #define DUK_HBUFFER_EXTERNAL_SET_DATA_PTR_NULL(heap,x) do { \ - (x)->curr_alloc = (void *) NULL; \ + (x)->curr_alloc = (duk_uint8_t *) NULL; \ } while (0) #endif @@ -282,7 +282,7 @@ struct duk_hbuffer_dynamic { #if defined(DUK_USE_HEAPPTR16) /* Stored in duk_heaphdr h_extra16. */ #else - void *curr_alloc; /* may be NULL if alloc_size == 0 */ + duk_uint8_t *curr_alloc; /* may be NULL if alloc_size == 0 */ #endif /* @@ -311,7 +311,7 @@ struct duk_hbuffer_external { /* Cannot be compressed as a heap pointer because may point to * an arbitrary address. */ - void *curr_alloc; /* may be NULL if alloc_size == 0 */ + duk_uint8_t *curr_alloc; /* may be NULL if alloc_size == 0 */ }; /* diff --git a/src-input/duk_hcompfunc.h b/src-input/duk_hcompfunc.h index c96d47b417..080a3e069b 100644 --- a/src-input/duk_hcompfunc.h +++ b/src-input/duk_hcompfunc.h @@ -16,7 +16,7 @@ #if defined(DUK_USE_HEAPPTR16) #define DUK_HCOMPFUNC_GET_DATA(heap,h) \ - ((duk_hbuffer_fixed *) (void *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->data16)) + ((duk_hbuffer *) (void *) DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->data16)) #define DUK_HCOMPFUNC_SET_DATA(heap,h,v) do { \ (h)->data16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) @@ -25,10 +25,9 @@ #define DUK_HCOMPFUNC_SET_FUNCS(heap,h,v) do { \ (h)->funcs16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) -#define DUK_HCOMPFUNC_GET_BYTECODE(heap,h) \ - ((duk_instr_t *) (void *) (DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->bytecode16))) +#define DUK_HCOMPFUNC_GET_BYTECODE(heap,h) (h)->bytecode #define DUK_HCOMPFUNC_SET_BYTECODE(heap,h,v) do { \ - (h)->bytecode16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ + (h)->bytecode = (v); \ } while (0) #define DUK_HCOMPFUNC_GET_LEXENV(heap,h) \ ((duk_hobject *) (void *) (DUK_USE_HEAPPTR_DEC16((heap)->heap_udata, (h)->lex_env16))) @@ -40,8 +39,9 @@ #define DUK_HCOMPFUNC_SET_VARENV(heap,h,v) do { \ (h)->var_env16 = DUK_USE_HEAPPTR_ENC16((heap)->heap_udata, (void *) (v)); \ } while (0) -#else -#define DUK_HCOMPFUNC_GET_DATA(heap,h) ((duk_hbuffer_fixed *) (void *) (h)->data) +#else /* DUK_USE_HEAPPTR16 */ +#define DUK_HCOMPFUNC_GET_DATA(heap,h) \ + ((duk_hbuffer *) (void *) (h)->data) #define DUK_HCOMPFUNC_SET_DATA(heap,h,v) do { \ (h)->data = (duk_hbuffer *) (v); \ } while (0) @@ -61,35 +61,38 @@ #define DUK_HCOMPFUNC_SET_VARENV(heap,h,v) do { \ (h)->var_env = (v); \ } while (0) -#endif +#endif /* DUK_USE_HEAPPTR16 */ /* * Accessor macros for function specific data areas */ -/* Note: assumes 'data' is always a fixed buffer */ +/* Without external bytecode check assumes 'data' is always a fixed buffer. */ #define DUK_HCOMPFUNC_GET_BUFFER_BASE(heap,h) \ DUK_HBUFFER_FIXED_GET_DATA_PTR((heap), DUK_HCOMPFUNC_GET_DATA((heap), (h))) #define DUK_HCOMPFUNC_GET_CONSTS_BASE(heap,h) \ ((duk_tval *) (void *) DUK_HCOMPFUNC_GET_BUFFER_BASE((heap), (h))) +#define DUK_HCOMPFUNC_GET_CONSTS_END(heap,h) \ + ((duk_tval *) (void *) DUK_HCOMPFUNC_GET_FUNCS((heap), (h))) #define DUK_HCOMPFUNC_GET_FUNCS_BASE(heap,h) \ DUK_HCOMPFUNC_GET_FUNCS((heap), (h)) +#define DUK_HCOMPFUNC_GET_FUNCS_END(heap,h) \ + ((duk_hobject **) ((duk_uint8_t *) DUK_HCOMPFUNC_GET_FUNCS((heap), (h)) + (h)->funcs_size)) #define DUK_HCOMPFUNC_GET_CODE_BASE(heap,h) \ DUK_HCOMPFUNC_GET_BYTECODE((heap), (h)) - -#define DUK_HCOMPFUNC_GET_CONSTS_END(heap,h) \ - ((duk_tval *) (void *) DUK_HCOMPFUNC_GET_FUNCS((heap), (h))) - -#define DUK_HCOMPFUNC_GET_FUNCS_END(heap,h) \ - ((duk_hobject **) (void *) DUK_HCOMPFUNC_GET_BYTECODE((heap), (h))) - /* XXX: double evaluation of DUK_HCOMPFUNC_GET_DATA() */ +#if defined(DUK_USE_EXTBC_CHECK) +/* XXX: change to use external buffer? depends on EXTBC lifecycle handling */ +#define DUK_HCOMPFUNC_GET_CODE_END(heap,h) \ + ((duk_instr_t *) (void *) ((duk_uint8_t *) (h)->bytecode + (h)->bytecode_size)) +#else #define DUK_HCOMPFUNC_GET_CODE_END(heap,h) \ ((duk_instr_t *) (void *) (DUK_HBUFFER_FIXED_GET_DATA_PTR((heap), DUK_HCOMPFUNC_GET_DATA((heap), (h))) + \ DUK_HBUFFER_GET_SIZE((duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA((heap), h)))) +#endif #define DUK_HCOMPFUNC_GET_CONSTS_SIZE(heap,h) \ ( \ @@ -157,6 +160,10 @@ struct duk_hcompfunc { * to the 'data' element. */ + /* Bytecode pointer for external bytecode. */ + /* XXX: conditional to extbc support? */ + duk_instr_t *bytecode; + /* Data area, fixed allocation, stable data ptrs. */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t data16; @@ -166,17 +173,23 @@ struct duk_hcompfunc { /* No need for constants pointer (= same as data). * - * When using 16-bit packing alignment to 4 is nice. 'funcs' will be - * 4-byte aligned because 'constants' are duk_tvals. For now the - * inner function pointers are not compressed, so that 'bytecode' will - * also be 4-byte aligned. + * When using 16-bit packing alignment to 4 is nice. For now constants + * are not compressed, so that 'bytecode' will also be 4-byte aligned. */ #if defined(DUK_USE_HEAPPTR16) duk_uint16_t funcs16; - duk_uint16_t bytecode16; #else duk_hobject **funcs; - duk_instr_t *bytecode; +#endif + +#if defined(DUK_USE_OBJSIZES16) + duk_uint16_t bytecode_size; + duk_uint16_t consts_size; + duk_uint16_t funcs_size; +#else + duk_size_t bytecode_size; + duk_size_t consts_size; + duk_size_t funcs_size; #endif /* Lexenv: lexical environment of closure, NULL for templates. diff --git a/src-input/duk_heap_alloc.c b/src-input/duk_heap_alloc.c index 23a9e5b207..a389f5bbb1 100644 --- a/src-input/duk_heap_alloc.c +++ b/src-input/duk_heap_alloc.c @@ -28,6 +28,7 @@ DUK_INTERNAL void duk_free_hobject(duk_heap *heap, duk_hobject *h) { duk_hcompfunc *f = (duk_hcompfunc *) h; DUK_UNREF(f); /* Currently nothing to free; 'data' is a heap object */ + /* XXX: EXTBC free macro */ } else if (DUK_HOBJECT_IS_NATFUNC(h)) { duk_hnatfunc *f = (duk_hnatfunc *) h; DUK_UNREF(f); diff --git a/src-input/duk_hobject_alloc.c b/src-input/duk_hobject_alloc.c index 284d44e97c..f694deefd3 100644 --- a/src-input/duk_hobject_alloc.c +++ b/src-input/duk_hobject_alloc.c @@ -102,11 +102,11 @@ DUK_INTERNAL duk_hcompfunc *duk_hcompfunc_alloc(duk_hthread *thr, duk_uint_t hob #else res->data = NULL; res->funcs = NULL; - res->bytecode = NULL; -#endif res->lex_env = NULL; res->var_env = NULL; #endif + res->bytecode = NULL; +#endif return res; } diff --git a/src-input/duk_js_compiler.c b/src-input/duk_js_compiler.c index 545d553126..665b373fd1 100644 --- a/src-input/duk_js_compiler.c +++ b/src-input/duk_js_compiler.c @@ -660,6 +660,7 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) { duk_context *ctx = (duk_context *) thr; duk_hcompfunc *h_res; duk_hbuffer_fixed *h_data; + duk_uint8_t *ext_bc_ptr = NULL; duk_size_t consts_count; duk_size_t funcs_count; duk_size_t code_count; @@ -669,7 +670,6 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) { duk_tval *p_const; duk_hobject **p_func; duk_instr_t *p_instr; - duk_compiler_instr *q_instr; duk_tval *tv; duk_bool_t keep_varmap; duk_bool_t keep_formals; @@ -743,6 +743,61 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) { DUK_HOBJECT_SET_CONSTRUCTABLE((duk_hobject *) h_res); } + /* + * When external bytecode support is enabled, offer the bytecode + * first to the application before allocating a buffer with all + * the parts in place. This keeps the memory usage peak as low + * as possible. + */ + +#if defined(DUK_USE_EXTBC_CHECK) +#if defined(DUK_USE_PC2LINE) + /* With PC2LINE, need a temporary buffer. */ + { + duk_instr_t *tmp_dst; + duk_compiler_instr *tmp_src; + duk_uint8_t *bc_ptr; + duk_size_t bc_size; + duk_size_t bc_count; + + tmp_dst = (duk_instr_t *) duk_push_fixed_buffer(ctx, DUK_BW_GET_SIZE(thr, &func->bw_code)); + DUK_ASSERT(tmp_dst != NULL); + tmp_src = (duk_compiler_instr *) (void *) DUK_BW_GET_BASEPTR(thr, &func->bw_code); + bc_size = DUK_BW_GET_SIZE(thr, &func->bw_code); + bc_count = bc_size / sizeof(duk_compiler_instr); + for (i = 0; i < bc_count; i++) { + tmp_dst[i] = tmp_src[i].ins; + } + + bc_ptr = (duk_uint8_t *) tmp_dst; + ext_bc_ptr = DUK_USE_EXTBC_CHECK(thr->heap->heap_udata, bc_ptr, bc_size); + if (ext_bc_ptr != NULL) { + DUK_DD(DUK_DDPRINT("bytecode %p (len %lu) moved to external buffer -> %p", + (void *) bc_ptr, (unsigned long) bc_size, (void *) ext_bc_ptr)); + } + + duk_pop(ctx); + } +#else /* DUK_USE_PC2LINE */ + /* Without PC2LINE, the temporary register list matches 1:1 with the + * final one, so no temporary buffer is needed. + */ + DUK_ASSERT(sizeof(duk_compiler_instr) == sizeof(duk_instr_t)); + { + duk_uint8_t *bc_ptr; + duk_size_t bc_size; + + bc_ptr = (duk_uint8_t *) DUK_BW_GET_BASEPTR(thr, &func->bw_code); + bc_size = DUK_BW_GET_SIZE(thr, &func->bw_code); + ext_bc_ptr = DUK_USE_EXTBC_CHECK(thr->heap->heap_udata, bc_ptr, bc_size); + if (ext_bc_ptr != NULL) { + DUK_DD(DUK_DDPRINT("bytecode %p (len %lu) moved to external buffer -> %p", + (void *) bc_ptr, (unsigned long) bc_size, (void *) ext_bc_ptr)); + } + } +#endif /* DUK_USE_PC2LINE */ +#endif /* DUK_USE_EXTBC_CHECK */ + /* * Build function fixed size 'data' buffer, which contains bytecode, * constants, and inner function references. @@ -757,9 +812,14 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) { code_count = DUK_BW_GET_SIZE(thr, &func->bw_code) / sizeof(duk_compiler_instr); code_size = code_count * sizeof(duk_instr_t); - data_size = consts_count * sizeof(duk_tval) + - funcs_count * sizeof(duk_hobject *) + - code_size; + if (ext_bc_ptr != NULL) { + data_size = consts_count * sizeof(duk_tval) + + funcs_count * sizeof(duk_hobject *); + } else { + data_size = consts_count * sizeof(duk_tval) + + funcs_count * sizeof(duk_hobject *) + + code_size; + } DUK_DDD(DUK_DDDPRINT("consts_count=%ld, funcs_count=%ld, code_size=%ld -> " "data_size=%ld*%ld + %ld*%ld + %ld = %ld", @@ -771,23 +831,22 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) { duk_push_fixed_buffer_nozero(ctx, data_size); h_data = (duk_hbuffer_fixed *) duk_known_hbuffer(ctx, -1); - DUK_HCOMPFUNC_SET_DATA(thr->heap, h_res, (duk_hbuffer *) h_data); - DUK_HEAPHDR_INCREF(thr, h_data); + /* Incrementing refcounts here before setting 'h_data' to the + * duk_hcompfunc instance is safe as long as no side effects + * are possible. + */ p_const = (duk_tval *) (void *) DUK_HBUFFER_FIXED_GET_DATA_PTR(thr->heap, h_data); for (i = 0; i < consts_count; i++) { DUK_ASSERT(i <= DUK_UARRIDX_MAX); /* const limits */ tv = duk_hobject_find_existing_array_entry_tval_ptr(thr->heap, func->h_consts, (duk_uarridx_t) i); DUK_ASSERT(tv != NULL); - DUK_TVAL_SET_TVAL(p_const, tv); - p_const++; + DUK_TVAL_SET_TVAL(p_const + i, tv); DUK_TVAL_INCREF(thr, tv); /* may be a string constant */ - DUK_DDD(DUK_DDDPRINT("constant: %!T", (duk_tval *) tv)); } - p_func = (duk_hobject **) p_const; - DUK_HCOMPFUNC_SET_FUNCS(thr->heap, h_res, p_func); + p_func = (duk_hobject **) (p_const + consts_count); for (i = 0; i < funcs_count; i++) { duk_hobject *h; DUK_ASSERT(i * 3 <= DUK_UARRIDX_MAX); /* func limits */ @@ -797,24 +856,54 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) { h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(h)); - *p_func++ = h; + p_func[i] = h; DUK_HOBJECT_INCREF(thr, h); DUK_DDD(DUK_DDDPRINT("inner function: %p -> %!iO", (void *) h, (duk_heaphdr *) h)); } - p_instr = (duk_instr_t *) p_func; - DUK_HCOMPFUNC_SET_BYTECODE(thr->heap, h_res, p_instr); + if (ext_bc_ptr != NULL) { + p_instr = (duk_instr_t *) ext_bc_ptr; + } else { + duk_compiler_instr *q_instr; - /* copy bytecode instructions one at a time */ - q_instr = (duk_compiler_instr *) (void *) DUK_BW_GET_BASEPTR(thr, &func->bw_code); - for (i = 0; i < code_count; i++) { - p_instr[i] = q_instr[i].ins; + p_instr = (duk_instr_t *) (p_func + funcs_count); + q_instr = (duk_compiler_instr *) (void *) DUK_BW_GET_BASEPTR(thr, &func->bw_code); +#if defined(DUK_USE_PC2LINE) + for (i = 0; i < code_count; i++) { + p_instr[i] = q_instr[i].ins; + } +#else + DUK_ASSERT(sizeof(duk_compiler_instr) == sizeof(duk_instr_t)); + DUK_MEMCPY((void *) p_instr, (const void *) q_instr, code_count * sizeof(duk_instr_t)); +#endif } - /* Note: 'q_instr' is still used below */ - DUK_ASSERT((duk_uint8_t *) (p_instr + code_count) == DUK_HBUFFER_FIXED_GET_DATA_PTR(thr->heap, h_data) + data_size); + /* XXX: range checks for compression, _size field assignment */ + DUK_HCOMPFUNC_SET_DATA(thr->heap, h_res, (duk_hbuffer *) h_data); + DUK_HEAPHDR_INCREF(thr, h_data); + DUK_HCOMPFUNC_SET_FUNCS(thr->heap, h_res, p_func); + DUK_HCOMPFUNC_SET_BYTECODE(thr->heap, h_res, p_instr); + +#if defined(DUK_USE_OBJSIZES16) + h_res->bytecode_size = (duk_uint16_t) (code_count * sizeof(duk_instr_t)); + h_res->consts_size = (duk_uint16_t) (consts_count * sizeof(duk_tval)); + h_res->funcs_size = (duk_uint16_t) (funcs_count * sizeof(duk_hobject *)); +#else + h_res->bytecode_size = (duk_size_t) (code_count * sizeof(duk_instr_t)); + h_res->consts_size = (duk_size_t) (consts_count * sizeof(duk_tval)); + h_res->funcs_size = (duk_size_t) (funcs_count * sizeof(duk_hobject *)); +#endif + + DUK_DD(DUK_DDPRINT("consts: %p %p %ld\n", + (void *) DUK_HCOMPFUNC_GET_CONSTS_BASE(thr->heap, h_res), + (void *) DUK_HCOMPFUNC_GET_CONSTS_END(thr->heap, h_res), + (long) DUK_HCOMPFUNC_GET_CONSTS_SIZE(thr->heap, h_res))); + DUK_DD(DUK_DDPRINT("bytecode: %p %p %ld\n", + (void *) DUK_HCOMPFUNC_GET_CODE_BASE(thr->heap, h_res), + (void *) DUK_HCOMPFUNC_GET_CODE_END(thr->heap, h_res), + (long) DUK_HCOMPFUNC_GET_CODE_SIZE(thr->heap, h_res))); duk_pop(ctx); /* 'data' (and everything in it) is reachable through h_res now */ @@ -854,7 +943,6 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) { * register mappings after a cleanup. When debugging is enabled, we * always need the varmap to be able to lookup variables at any point. */ - #if defined(DUK_USE_DEBUGGER_SUPPORT) DUK_DD(DUK_DDPRINT("keeping _Varmap because debugger support is enabled")); keep_varmap = 1; @@ -988,7 +1076,7 @@ DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) { */ DUK_ASSERT(code_count <= DUK_COMPILER_MAX_BYTECODE_LENGTH); - duk_hobject_pc2line_pack(thr, q_instr, (duk_uint_fast32_t) code_count); /* -> pushes fixed buffer */ + duk_hobject_pc2line_pack(thr, (duk_compiler_instr *) (void *) DUK_BW_GET_BASEPTR(thr, &func->bw_code), (duk_uint_fast32_t) code_count); /* -> pushes fixed buffer */ duk_xdef_prop_stridx_short(ctx, -2, DUK_STRIDX_INT_PC2LINE, DUK_PROPDESC_FLAGS_NONE); /* XXX: if assertions enabled, walk through all valid PCs diff --git a/src-input/duk_js_executor.c b/src-input/duk_js_executor.c index 78df61a703..04e55cc6b0 100644 --- a/src-input/duk_js_executor.c +++ b/src-input/duk_js_executor.c @@ -1335,7 +1335,9 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr, */ duk_catcher *cat; +#if defined(DUK_USE_COROUTINE_SUPPORT) duk_hthread *resumer; +#endif cat = thr->catchstack + thr->catchstack_top - 1; while (cat >= thr->catchstack) { @@ -1390,6 +1392,7 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr, /* Note: MUST NOT wipe_and_return here, as heap->lj must remain intact */ } +#if defined(DUK_USE_COROUTINE_SUPPORT) DUK_DD(DUK_DDPRINT("-> throw not caught by current thread, yield error to resumer and recheck longjmp")); /* not caught by current thread, thread terminates (yield error to resumer); @@ -1422,6 +1425,13 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr, DUK_HEAP_SWITCH_THREAD(thr->heap, resumer); thr = resumer; goto check_longjmp; +#else /* DUK_USE_COROUTINE_SUPPORT */ + /* Without coroutine support always an uncaught error and should + * be handled above. + */ + DUK_D(DUK_DPRINT("uncaught error without coroutine support; should not happen, cause internal error")); + goto convert_to_internal_error; +#endif /* DUK_USE_COROUTINE_SUPPORT */ } case DUK_LJ_TYPE_BREAK: /* pseudotypes, not used in actual longjmps */ @@ -2925,7 +2935,7 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread * duk_small_uint_t stridx; stridx = duk_js_typeof_stridx(DUK__REGP_BC(ins)); - DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS); + DUK_ASSERT_STRIDX_VALID(stridx); duk_push_hstring_stridx((duk_context *) thr, stridx); DUK__REPLACE_TOP_A_BREAK(); } diff --git a/src-input/duk_js_var.c b/src-input/duk_js_var.c index 00d2a3a70f..03a44b15d1 100644 --- a/src-input/duk_js_var.c +++ b/src-input/duk_js_var.c @@ -148,11 +148,15 @@ void duk_js_push_closure(duk_hthread *thr, DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) fun_clos)); DUK_ASSERT(DUK_HCOMPFUNC_GET_DATA(thr->heap, fun_clos) == NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_FUNCS(thr->heap, fun_clos) == NULL); +#if 0 /* XXX: these don't work with the offset approach because with a NULL base the offsetted pointers are != NULL */ DUK_ASSERT(DUK_HCOMPFUNC_GET_BYTECODE(thr->heap, fun_clos) == NULL); - +#endif DUK_HCOMPFUNC_SET_DATA(thr->heap, fun_clos, DUK_HCOMPFUNC_GET_DATA(thr->heap, fun_temp)); DUK_HCOMPFUNC_SET_FUNCS(thr->heap, fun_clos, DUK_HCOMPFUNC_GET_FUNCS(thr->heap, fun_temp)); DUK_HCOMPFUNC_SET_BYTECODE(thr->heap, fun_clos, DUK_HCOMPFUNC_GET_BYTECODE(thr->heap, fun_temp)); + fun_clos->bytecode_size = fun_temp->bytecode_size; + fun_clos->consts_size = fun_temp->consts_size; + fun_clos->funcs_size = fun_temp->funcs_size; /* Note: all references inside 'data' need to get their refcounts * upped too. This is the case because refcounts are decreased diff --git a/util/makeduk_ajduk.yaml b/util/makeduk_ajduk.yaml index 6a6415bc85..0603ba8f1d 100644 --- a/util/makeduk_ajduk.yaml +++ b/util/makeduk_ajduk.yaml @@ -38,8 +38,12 @@ DUK_USE_EXTSTR_FREE: # verbatim: "#define DUK_USE_EXTSTR_INTERN_CHECK(ud,ptr,len) ajsheap_extstr_check_3((ptr),(len))" #DUK_USE_EXTSTR_FREE: # verbatim: "#define DUK_USE_EXTSTR_FREE(ud,ptr) ajsheap_extstr_free_3((ptr))" +DUK_USE_EXTBC_CHECK: + verbatim: "#define DUK_USE_EXTBC_CHECK(ud,ptr,len) ajsheap_extbc_check((ptr),(len))" DUK_USE_EXEC_TIMEOUT_CHECK: verbatim: "#define DUK_USE_EXEC_TIMEOUT_CHECK(udata) ajsheap_exec_timeout_check(udata)" #DUK_USE_ROM_STRINGS: true #DUK_USE_ROM_OBJECTS: true #DUK_USE_ROM_GLOBAL_INHERIT: true +DUK_USE_PREFER_SIZE: true +DUK_USE_EXEC_PREFER_SIZE: true diff --git a/util/makeduk_ajduk_fixup.h b/util/makeduk_ajduk_fixup.h index 7ae7505f4f..e0c0203cb4 100644 --- a/util/makeduk_ajduk_fixup.h +++ b/util/makeduk_ajduk_fixup.h @@ -9,4 +9,5 @@ extern const void *ajsheap_extstr_check_3(const void *ptr, duk_size_t len); extern void ajsheap_extstr_free_1(const void *ptr); extern void ajsheap_extstr_free_2(const void *ptr); extern void ajsheap_extstr_free_3(const void *ptr); +extern void *ajsheap_extbc_check(void *ptr, duk_size_t len); extern duk_bool_t ajsheap_exec_timeout_check(void *udata);