Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental support for mapping bytecode to an external data area #1409

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions RELEASES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions config/config-options/DUK_USE_EXTBC_CHECK.yaml
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions config/examples/low_memory.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions doc/low-memory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down
54 changes: 48 additions & 6 deletions examples/cmdline/duk_cmdline_ajduk.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions src-input/duk_debug_vsnprintf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
20 changes: 10 additions & 10 deletions src-input/duk_hbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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

/*
Expand Down Expand Up @@ -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 */
};

/*
Expand Down
55 changes: 34 additions & 21 deletions src-input/duk_hcompfunc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)))
Expand All @@ -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)
Expand All @@ -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) \
( \
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src-input/duk_heap_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src-input/duk_hobject_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading