2 * 'ajduk' specific functionality, examples for low memory techniques
5 #ifdef DUK_CMDLINE_AJSHEAP
15 extern uint8_t dbgHEAPDUMP
;
17 #if defined(DUK_USE_ROM_OBJECTS) && defined(DUK_USE_HEAPPTR16)
18 /* Pointer compression with ROM strings/objects:
20 * For now, use DUK_USE_ROM_OBJECTS to signal the need for compressed ROM
21 * pointers. DUK_USE_ROM_PTRCOMP_FIRST is provided for the ROM pointer
22 * compression range minimum to avoid duplication in user code.
24 #if 0 /* This extern declaration is provided by duktape.h, array provided by duktape.c. */
25 extern const void * const duk_rom_compressed_pointers
[];
27 static const void *duk__romptr_low
= NULL
;
28 static const void *duk__romptr_high
= NULL
;
29 #define DUK__ROMPTR_COMPRESSION
30 #define DUK__ROMPTR_FIRST DUK_USE_ROM_PTRCOMP_FIRST
37 static void *ajduk__lose_const(const void *ptr
) {
38 /* Somewhat portable way of losing a const without warnings.
39 * Another approach is to cast through intptr_t, but that
40 * type is not always available.
50 static void safe_print_chars(const char *p
, duk_size_t len
, int until_nul
) {
54 for (i
= 0; i
< len
; i
++) {
55 unsigned char x
= (unsigned char) p
[i
];
56 if (until_nul
&& x
== 0U) {
59 if (x
< 0x20 || x
>= 0x7e || x
== '"' || x
== '\'' || x
== '\\') {
60 printf("\\x%02x", (int) x
);
62 printf("%c", (char) x
);
69 * Heap initialization when using AllJoyn.js pool allocator (without any
70 * other AllJoyn.js integration). This serves as an example of how to
71 * integrate Duktape with a pool allocator and is useful for low memory
74 * The pool sizes are not optimized here. The sizes are chosen so that
75 * you can look at the high water mark (hwm) and use counts (use) and see
76 * how much allocations are needed for each pool size. To optimize pool
77 * sizes more accurately, you can use --alloc-logging and inspect the memory
78 * allocation log which provides exact byte counts etc.
80 * https://git.allseenalliance.org/cgit/core/alljoyn-js.git
81 * https://git.allseenalliance.org/cgit/core/alljoyn-js.git/tree/ajs.c
84 static const AJS_HeapConfig ajsheap_config
[] = {
85 { 8, 10, AJS_POOL_BORROW
, 0 },
86 { 12, 600, AJS_POOL_BORROW
, 0 },
87 { 16, 300, AJS_POOL_BORROW
, 0 },
88 { 20, 300, AJS_POOL_BORROW
, 0 },
89 { 24, 300, AJS_POOL_BORROW
, 0 },
90 { 28, 150, AJS_POOL_BORROW
, 0 },
91 { 32, 150, AJS_POOL_BORROW
, 0 },
92 { 40, 150, AJS_POOL_BORROW
, 0 },
93 { 48, 50, AJS_POOL_BORROW
, 0 },
94 { 52, 50, AJS_POOL_BORROW
, 0 },
95 { 56, 50, AJS_POOL_BORROW
, 0 },
96 { 60, 50, AJS_POOL_BORROW
, 0 },
97 { 64, 50, AJS_POOL_BORROW
, 0 },
98 { 128, 80, AJS_POOL_BORROW
, 0 },
99 { 256, 16, AJS_POOL_BORROW
, 0 },
100 { 320, 1, AJS_POOL_BORROW
, 0 },
101 { 392, 1, AJS_POOL_BORROW
, 0 }, /* duk_hthread, with heap ptr compression, ROM strings+objects */
102 { 512, 16, AJS_POOL_BORROW
, 0 },
103 { 964, 1, AJS_POOL_BORROW
, 0 }, /* duk_heap, with heap ptr compression, ROM strings+objects */
104 { 1024, 6, AJS_POOL_BORROW
, 0 },
105 { 1344, 1, AJS_POOL_BORROW
, 0 }, /* duk_heap, with heap ptr compression, RAM strings+objects */
106 { 2048, 5, AJS_POOL_BORROW
, 0 },
113 uint8_t *ajsheap_ram
= NULL
;
115 void ajsheap_init(void) {
117 uint8_t *heap_array
[1];
118 uint8_t num_pools
, i
;
121 num_pools
= (uint8_t) (sizeof(ajsheap_config
) / sizeof(AJS_HeapConfig
));
122 heap_sz
[0] = AJS_HeapRequired(ajsheap_config
, /* heapConfig */
123 num_pools
, /* numPools */
125 ajsheap_ram
= (uint8_t *) malloc(heap_sz
[0]);
126 if (ajsheap_ram
== NULL
) {
127 fprintf(stderr
, "Failed to allocate AJS heap\n");
131 heap_array
[0] = ajsheap_ram
;
133 fprintf(stderr
, "Allocated AJS heap of %ld bytes, pools:", (long) heap_sz
[0]);
134 for (i
= 0; i
< num_pools
; i
++) {
135 fprintf(stderr
, " (sz:%ld,num:%ld,brw:%ld,idx:%ld)",
136 (long) ajsheap_config
[i
].size
, (long) ajsheap_config
[i
].entries
,
137 (long) ajsheap_config
[i
].borrow
, (long) ajsheap_config
[i
].heapIndex
);
139 fprintf(stderr
, "\n");
142 ret
= AJS_HeapInit((void **) heap_array
, /* heap */
143 (size_t *) heap_sz
, /* heapSz */
144 ajsheap_config
, /* heapConfig */
145 num_pools
, /* numPools */
147 fprintf(stderr
, "AJS_HeapInit() -> %ld\n", (long) ret
);
150 /* Enable heap dumps */
153 #if defined(DUK__ROMPTR_COMPRESSION)
154 /* Scan ROM pointer range for faster detection of "is 'p' a ROM pointer"
158 const void * const * ptrs
= (const void * const *) duk_rom_compressed_pointers
;
159 duk__romptr_low
= duk__romptr_high
= (const void *) *ptrs
;
161 if (*ptrs
> duk__romptr_high
) {
162 duk__romptr_high
= (const void *) *ptrs
;
164 if (*ptrs
< duk__romptr_low
) {
165 duk__romptr_low
= (const void *) *ptrs
;
169 fprintf(stderr
, "romptrs: low=%p high=%p\n",
170 (const void *) duk__romptr_low
, (const void *) duk__romptr_high
);
176 void ajsheap_free(void) {
177 if (ajsheap_ram
!= NULL
) {
183 /* AjsHeap.dump(), allows Ecmascript code to dump heap status at suitable
186 duk_ret_t
ajsheap_dump_binding(duk_context
*ctx
) {
192 void ajsheap_dump(void) {
197 void ajsheap_register(duk_context
*ctx
) {
198 duk_push_object(ctx
);
199 duk_push_c_function(ctx
, ajsheap_dump_binding
, 0);
200 duk_put_prop_string(ctx
, -2, "dump");
201 duk_put_global_string(ctx
, "AjsHeap");
205 * Wrapped ajs_heap.c alloc functions
207 * Used to write an alloc log.
210 static FILE *ajsheap_alloc_log
= NULL
;
212 static void ajsheap_write_alloc_log(const char *fmt
, ...) {
217 vsnprintf(buf
, sizeof(buf
), fmt
, ap
);
218 buf
[sizeof(buf
) - 1] = (char) 0;
221 if (ajsheap_alloc_log
== NULL
) {
222 ajsheap_alloc_log
= fopen("/tmp/ajduk-alloc-log.txt", "wb");
223 if (ajsheap_alloc_log
== NULL
) {
224 fprintf(stderr
, "WARNING: failed to write alloc log, ignoring\n");
230 (void) fwrite((const void *) buf
, 1, strlen(buf
), ajsheap_alloc_log
);
231 (void) fflush(ajsheap_alloc_log
);
234 void *ajsheap_alloc_wrapped(void *udata
, duk_size_t size
) {
235 void *ret
= AJS_Alloc(udata
, size
);
236 if (size
> 0 && ret
== NULL
) {
237 ajsheap_write_alloc_log("A FAIL %ld\n", (long) size
);
238 } else if (ret
== NULL
) {
239 ajsheap_write_alloc_log("A NULL %ld\n", (long) size
);
241 ajsheap_write_alloc_log("A %p %ld\n", ret
, (long) size
);
246 void *ajsheap_realloc_wrapped(void *udata
, void *ptr
, duk_size_t size
) {
247 void *ret
= AJS_Realloc(udata
, ptr
, size
);
248 if (size
> 0 && ret
== NULL
) {
250 ajsheap_write_alloc_log("R NULL -1 FAIL %ld\n", (long) size
);
252 ajsheap_write_alloc_log("R %p -1 FAIL %ld\n", ptr
, (long) size
);
254 } else if (ret
== NULL
) {
256 ajsheap_write_alloc_log("R NULL -1 NULL %ld\n", (long) size
);
258 ajsheap_write_alloc_log("R %p -1 NULL %ld\n", ptr
, (long) size
);
262 ajsheap_write_alloc_log("R NULL -1 %p %ld\n", ret
, (long) size
);
264 ajsheap_write_alloc_log("R %p -1 %p %ld\n", ptr
, ret
, (long) size
);
270 void ajsheap_free_wrapped(void *udata
, void *ptr
) {
271 AJS_Free(udata
, ptr
);
274 ajsheap_write_alloc_log("F %p -1\n", ptr
);
279 * Example pointer compression functions.
281 * 'base' is chosen so that no non-NULL pointer results in a zero result
282 * which is reserved for NULL pointers.
285 duk_uint16_t
ajsheap_enc16(void *ud
, void *p
) {
287 char *base
= (char *) ajsheap_ram
- 4;
289 #if defined(DUK__ROMPTR_COMPRESSION)
290 if (p
>= duk__romptr_low
&& p
<= duk__romptr_high
) {
291 /* The if-condition should be the fastest possible check
292 * for "is 'p' in ROM?". If pointer is in ROM, we'd like
293 * to compress it quickly. Here we just scan a ~1K array
294 * which is very bad for performance and for illustration
297 const void * const * ptrs
= duk_rom_compressed_pointers
;
300 ret
= DUK__ROMPTR_FIRST
+ (ptrs
- duk_rom_compressed_pointers
);
302 fprintf(stderr
, "ajsheap_enc16: rom pointer: %p -> 0x%04lx\n", (void *) p
, (long) ret
);
305 return (duk_uint16_t
) ret
;
310 /* We should really never be here: Duktape should only be
311 * compressing pointers which are in the ROM compressed
312 * pointers list, which are known at 'make dist' time.
313 * We go on, causing a pointer compression error.
315 fprintf(stderr
, "ajsheap_enc16: rom pointer: %p could not be compressed, should never happen\n", (void *) p
);
320 /* Userdata is not needed in this case but would be useful if heap
321 * pointer compression were used for multiple heaps. The userdata
322 * allows the callback to distinguish between heaps and their base
325 * If not needed, the userdata can be left out during compilation
326 * by simply ignoring the userdata argument of the pointer encode
327 * and decode macros. It is kept here so that any bugs in actually
328 * providing the value inside Duktape are revealed during compilation.
332 /* Ensure that we always get the heap_udata given in heap creation.
333 * (Useful for Duktape development, not needed for user programs.)
335 if (ud
!= (void *) 0xdeadbeef) {
336 fprintf(stderr
, "invalid udata for ajsheap_enc16: %p\n", ud
);
344 ret
= (duk_uint32_t
) (((char *) p
- base
) >> 2);
347 printf("ajsheap_enc16: %p -> %u\n", p
, (unsigned int) ret
);
349 if (ret
> 0xffffUL
) {
350 fprintf(stderr
, "Failed to compress pointer: %p (ret was %ld)\n", (void *) p
, (long) ret
);
354 #if defined(DUK__ROMPTR_COMPRESSION)
355 if (ret
>= DUK__ROMPTR_FIRST
) {
356 fprintf(stderr
, "Failed to compress pointer, in 16-bit range but matches romptr range: %p (ret was %ld)\n", (void *) p
, (long) ret
);
361 return (duk_uint16_t
) ret
;
364 void *ajsheap_dec16(void *ud
, duk_uint16_t x
) {
366 char *base
= (char *) ajsheap_ram
- 4;
368 #if defined(DUK__ROMPTR_COMPRESSION)
369 if (x
>= DUK__ROMPTR_FIRST
) {
370 /* This is a blind lookup, could check index validity.
371 * Duktape should never decompress a pointer which would
372 * be out-of-bounds here.
374 ret
= (void *) ajduk__lose_const(duk_rom_compressed_pointers
[x
- DUK__ROMPTR_FIRST
]);
376 fprintf(stderr
, "ajsheap_dec16: rom pointer: 0x%04lx -> %p\n", (long) x
, ret
);
383 /* See userdata discussion in ajsheap_enc16(). */
386 /* Ensure that we always get the heap_udata given in heap creation. */
387 if (ud
!= (void *) 0xdeadbeef) {
388 fprintf(stderr
, "invalid udata for ajsheap_dec16: %p\n", ud
);
396 ret
= (void *) (base
+ (((duk_uint32_t
) x
) << 2));
399 printf("ajsheap_dec16: %u -> %p\n", (unsigned int) x
, ret
);
405 * Simplified example of an external strings strategy where incoming strings
406 * are written sequentially into a fixed, memory mapped flash area.
408 * The example first scans if the string is already in the flash (which may
409 * happen if the same string is interned multiple times), then adds it to
410 * flash if there is space.
412 * This example is too slow to be used in a real world application: there
413 * should be e.g. a hash table to quickly check for strings that are already
414 * present in the string data (similarly to how string interning works in
418 static uint8_t ajsheap_strdata
[65536];
419 static size_t ajsheap_strdata_used
= 0;
421 const void *ajsheap_extstr_check_1(const void *ptr
, duk_size_t len
) {
427 (void) safe_print_chars
; /* potentially unused */
430 /* It's not worth it to make very small strings external, as
431 * they would take the same space anyway. Also avoids zero
432 * length degenerate case.
438 * Check if we already have the string. Be careful to compare for
439 * NUL terminator too, it is NOT present in 'ptr'. This algorithm
440 * is too simplistic and way too slow for actual use.
443 initial
= ((const uint8_t *) ptr
)[0];
444 for (p
= ajsheap_strdata
, p_end
= p
+ ajsheap_strdata_used
; p
!= p_end
; p
++) {
448 left
= (size_t) (p_end
- p
);
449 if (left
>= len
+ 1 &&
450 memcmp(p
, ptr
, len
) == 0 &&
454 printf("ajsheap_extstr_check_1: ptr=%p, len=%ld ",
455 (void *) ptr
, (long) len
);
456 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
457 printf(" -> existing %p (used=%ld)\n",
458 (void *) ret
, (long) ajsheap_strdata_used
);
465 * Not present yet, check if we have space. Again, be careful to
466 * ensure there is space for a NUL following the input data.
469 if (ajsheap_strdata_used
+ len
+ 1 > sizeof(ajsheap_strdata
)) {
471 printf("ajsheap_extstr_check_1: ptr=%p, len=%ld ", (void *) ptr
, (long) len
);
472 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
473 printf(" -> no space (used=%ld)\n", (long) ajsheap_strdata_used
);
479 * There is space, add the string to our collection, being careful
483 ret
= ajsheap_strdata
+ ajsheap_strdata_used
;
484 memcpy(ret
, ptr
, len
);
485 ret
[len
] = (uint8_t) 0;
486 ajsheap_strdata_used
+= len
+ 1;
489 printf("ajsheap_extstr_check_1: ptr=%p, len=%ld -> ", (void *) ptr
, (long) len
);
490 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
491 printf(" -> %p (used=%ld)\n", (void *) ret
, (long) ajsheap_strdata_used
);
493 return (const void *) ret
;
496 void ajsheap_extstr_free_1(const void *ptr
) {
499 printf("ajsheap_extstr_free_1: freeing extstr %p -> ", ptr
);
500 safe_print_chars((const char *) ptr
, DUK_SIZE_MAX
, 1 /*until_nul*/);
506 * Simplified example of an external strings strategy where a set of strings
507 * is gathered during application compile time and baked into the application
510 * Duktape built-in strings are available from duk_build_meta.json, see
511 * util/duk_meta_to_strarray.py. There may also be a lot of application
512 * specific strings, e.g. those used by application specific APIs. These
513 * must be gathered through some other means, see e.g. util/scan_strings.py.
516 static const char *strdata_duk_builtin_strings
[] = {
518 * These strings are from util/duk_meta_to_strarray.py
544 "{\x22" "_func\x22" ":true}",
545 "{\x22" "_ninf\x22" ":true}",
546 "{\x22" "_inf\x22" ":true}",
547 "{\x22" "_nan\x22" ":true}",
548 "{\x22" "_undef\x22" ":true}",
689 "setUTCMilliseconds",
693 "getUTCMilliseconds",
710 "toLocaleTimeString",
711 "toLocaleDateString",
765 "propertyIsEnumerable",
788 "getOwnPropertyNames",
789 "getOwnPropertyDescriptor",
797 "encodeURIComponent",
799 "decodeURIComponent",
859 * These strings are manually added, and would be gathered in some
860 * application specific manner.
871 const void *ajsheap_extstr_check_2(const void *ptr
, duk_size_t len
) {
874 (void) safe_print_chars
; /* potentially unused */
876 /* Linear scan. An actual implementation would need some acceleration
877 * structure, e.g. select a sublist based on first character.
879 * NOTE: input string (behind 'ptr' with 'len' bytes) DOES NOT have a
880 * trailing NUL character. Any strings returned from this function
881 * MUST have a trailing NUL character.
884 n
= (int) (sizeof(strdata_duk_builtin_strings
) / sizeof(const char *));
885 for (i
= 0; i
< n
; i
++) {
888 str
= strdata_duk_builtin_strings
[i
];
889 if (strlen(str
) == len
&& memcmp(ptr
, (const void *) str
, len
) == 0) {
891 printf("ajsheap_extstr_check_2: ptr=%p, len=%ld ",
892 (void *) ptr
, (long) len
);
893 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
894 printf(" -> constant string index %ld\n", (long) i
);
896 return (void *) ajduk__lose_const(strdata_duk_builtin_strings
[i
]);
901 printf("ajsheap_extstr_check_2: ptr=%p, len=%ld ",
902 (void *) ptr
, (long) len
);
903 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
904 printf(" -> not found\n");
909 void ajsheap_extstr_free_2(const void *ptr
) {
912 printf("ajsheap_extstr_free_2: freeing extstr %p -> ", ptr
);
913 safe_print_chars((const char *) ptr
, DUK_SIZE_MAX
, 1 /*until_nul*/);
919 * External strings strategy intended for valgrind testing: external strings
920 * are allocated using malloc()/free() so that valgrind can be used to ensure
921 * that strings are e.g. freed exactly once.
924 const void *ajsheap_extstr_check_3(const void *ptr
, duk_size_t len
) {
927 (void) safe_print_chars
; /* potentially unused */
929 ret
= malloc((size_t) len
+ 1);
932 printf("ajsheap_extstr_check_3: ptr=%p, len=%ld ",
933 (void *) ptr
, (long) len
);
934 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
935 printf(" -> malloc failed, return NULL\n");
937 return (const void *) NULL
;
941 memcpy((void *) ret
, ptr
, (size_t) len
);
943 ret
[len
] = (duk_uint8_t
) 0;
946 printf("ajsheap_extstr_check_3: ptr=%p, len=%ld ",
947 (void *) ptr
, (long) len
);
948 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
949 printf(" -> %p\n", (void *) ret
);
951 return (const void *) ret
;
954 void ajsheap_extstr_free_3(const void *ptr
) {
957 printf("ajsheap_extstr_free_3: freeing extstr %p -> ", ptr
);
958 safe_print_chars((const char *) ptr
, DUK_SIZE_MAX
, 1 /*until_nul*/);
961 free((void *) ajduk__lose_const(ptr
));
965 * Execution timeout example
968 #define AJSHEAP_EXEC_TIMEOUT 5 /* seconds */
970 static time_t curr_pcall_start
= 0;
971 static long exec_timeout_check_counter
= 0;
973 void ajsheap_start_exec_timeout(void) {
974 curr_pcall_start
= time(NULL
);
977 void ajsheap_clear_exec_timeout(void) {
978 curr_pcall_start
= 0;
981 duk_bool_t
ajsheap_exec_timeout_check(void *udata
) {
982 time_t now
= time(NULL
);
983 time_t diff
= now
- curr_pcall_start
;
985 (void) udata
; /* not needed */
987 exec_timeout_check_counter
++;
989 printf("exec timeout check %ld: now=%ld, start=%ld, diff=%ld\n",
990 (long) exec_timeout_check_counter
, (long) now
, (long) curr_pcall_start
, (long) diff
);
994 if (curr_pcall_start
== 0) {
995 /* protected call not yet running */
998 if (diff
> AJSHEAP_EXEC_TIMEOUT
) {
1004 #else /* DUK_CMDLINE_AJSHEAP */
1006 int ajs_dummy
= 0; /* to avoid empty source file */
1008 #endif /* DUK_CMDLINE_AJSHEAP */