2 * 'ajduk' specific functionality, examples for low memory techniques
5 #ifdef DUK_CMDLINE_AJSHEAP
14 extern uint8_t dbgHEAPDUMP
;
20 static void safe_print_chars(const char *p
, duk_size_t len
, int until_nul
) {
24 for (i
= 0; i
< len
; i
++) {
25 unsigned char x
= (unsigned char) p
[i
];
26 if (until_nul
&& x
== 0U) {
29 if (x
< 0x20 || x
>= 0x7e || x
== '"' || x
== '\'' || x
== '\\') {
30 printf("\\x%02x", (int) x
);
32 printf("%c", (char) x
);
39 * Heap initialization when using AllJoyn.js pool allocator (without any
40 * other AllJoyn.js integration). This serves as an example of how to
41 * integrate Duktape with a pool allocator and is useful for low memory
44 * The pool sizes are not optimized here. The sizes are chosen so that
45 * you can look at the high water mark (hwm) and use counts (use) and see
46 * how much allocations are needed for each pool size. To optimize pool
47 * sizes more accurately, you can use --alloc-logging and inspect the memory
48 * allocation log which provides exact byte counts etc.
50 * https://git.allseenalliance.org/cgit/core/alljoyn-js.git
51 * https://git.allseenalliance.org/cgit/core/alljoyn-js.git/tree/ajs.c
54 static const AJS_HeapConfig ajsheap_config
[] = {
55 { 8, 10, AJS_POOL_BORROW
, 0 },
56 { 12, 10, AJS_POOL_BORROW
, 0 },
57 { 16, 200, AJS_POOL_BORROW
, 0 },
58 { 20, 400, AJS_POOL_BORROW
, 0 },
59 { 24, 400, AJS_POOL_BORROW
, 0 },
60 { 28, 200, AJS_POOL_BORROW
, 0 },
61 { 32, 200, AJS_POOL_BORROW
, 0 },
62 { 40, 200, AJS_POOL_BORROW
, 0 },
63 { 48, 50, AJS_POOL_BORROW
, 0 },
64 { 52, 50, AJS_POOL_BORROW
, 0 },
65 { 56, 50, AJS_POOL_BORROW
, 0 },
66 { 60, 50, AJS_POOL_BORROW
, 0 },
67 { 64, 50, AJS_POOL_BORROW
, 0 },
68 { 128, 80, AJS_POOL_BORROW
, 0 },
69 { 256, 16, AJS_POOL_BORROW
, 0 },
70 { 512, 16, AJS_POOL_BORROW
, 0 },
71 { 1024, 6, AJS_POOL_BORROW
, 0 },
72 { 1360, 1, AJS_POOL_BORROW
, 0 }, /* duk_heap, with heap ptr compression */
73 { 2048, 5, AJS_POOL_BORROW
, 0 },
80 uint8_t *ajsheap_ram
= NULL
;
82 void ajsheap_init(void) {
84 uint8_t *heap_array
[1];
88 num_pools
= (uint8_t) (sizeof(ajsheap_config
) / sizeof(AJS_HeapConfig
));
89 heap_sz
[0] = AJS_HeapRequired(ajsheap_config
, /* heapConfig */
90 num_pools
, /* numPools */
92 ajsheap_ram
= (uint8_t *) malloc(heap_sz
[0]);
93 if (ajsheap_ram
== NULL
) {
94 fprintf(stderr
, "Failed to allocate AJS heap\n");
98 heap_array
[0] = ajsheap_ram
;
100 fprintf(stderr
, "Allocated AJS heap of %ld bytes, pools:", (long) heap_sz
[0]);
101 for (i
= 0; i
< num_pools
; i
++) {
102 fprintf(stderr
, " (sz:%ld,num:%ld,brw:%ld,idx:%ld)",
103 (long) ajsheap_config
[i
].size
, (long) ajsheap_config
[i
].entries
,
104 (long) ajsheap_config
[i
].borrow
, (long) ajsheap_config
[i
].heapIndex
);
106 fprintf(stderr
, "\n");
109 ret
= AJS_HeapInit((void **) heap_array
, /* heap */
110 (size_t *) heap_sz
, /* heapSz */
111 ajsheap_config
, /* heapConfig */
112 num_pools
, /* numPools */
114 fprintf(stderr
, "AJS_HeapInit() -> %ld\n", (long) ret
);
117 /* Enable heap dumps */
121 /* AjsHeap.dump(), allows Ecmascript code to dump heap status at suitable
124 duk_ret_t
ajsheap_dump_binding(duk_context
*ctx
) {
130 void ajsheap_dump(void) {
135 void ajsheap_register(duk_context
*ctx
) {
136 duk_push_object(ctx
);
137 duk_push_c_function(ctx
, ajsheap_dump_binding
, 0);
138 duk_put_prop_string(ctx
, -2, "dump");
139 duk_put_global_string(ctx
, "AjsHeap");
143 * Wrapped ajs_heap.c alloc functions
145 * Used to write an alloc log.
148 static FILE *ajsheap_alloc_log
= NULL
;
150 static void ajsheap_write_alloc_log(const char *fmt
, ...) {
155 vsnprintf(buf
, sizeof(buf
), fmt
, ap
);
156 buf
[sizeof(buf
) - 1] = (char) 0;
159 if (ajsheap_alloc_log
== NULL
) {
160 ajsheap_alloc_log
= fopen("/tmp/ajduk-alloc-log.txt", "wb");
161 if (ajsheap_alloc_log
== NULL
) {
162 fprintf(stderr
, "WARNING: failed to write alloc log, ignoring\n");
168 (void) fwrite((const void *) buf
, 1, strlen(buf
), ajsheap_alloc_log
);
169 (void) fflush(ajsheap_alloc_log
);
172 void *ajsheap_alloc_wrapped(void *udata
, duk_size_t size
) {
173 void *ret
= AJS_Alloc(udata
, size
);
174 if (size
> 0 && ret
== NULL
) {
175 ajsheap_write_alloc_log("A FAIL %ld\n", (long) size
);
176 } else if (ret
== NULL
) {
177 ajsheap_write_alloc_log("A NULL %ld\n", (long) size
);
179 ajsheap_write_alloc_log("A %p %ld\n", ret
, (long) size
);
184 void *ajsheap_realloc_wrapped(void *udata
, void *ptr
, duk_size_t size
) {
185 void *ret
= AJS_Realloc(udata
, ptr
, size
);
186 if (size
> 0 && ret
== NULL
) {
188 ajsheap_write_alloc_log("R NULL -1 FAIL %ld\n", (long) size
);
190 ajsheap_write_alloc_log("R %p -1 FAIL %ld\n", ptr
, (long) size
);
192 } else if (ret
== NULL
) {
194 ajsheap_write_alloc_log("R NULL -1 NULL %ld\n", (long) size
);
196 ajsheap_write_alloc_log("R %p -1 NULL %ld\n", ptr
, (long) size
);
200 ajsheap_write_alloc_log("R NULL -1 %p %ld\n", ret
, (long) size
);
202 ajsheap_write_alloc_log("R %p -1 %p %ld\n", ptr
, ret
, (long) size
);
208 void ajsheap_free_wrapped(void *udata
, void *ptr
) {
209 AJS_Free(udata
, ptr
);
212 ajsheap_write_alloc_log("F %p -1\n", ptr
);
217 * Example pointer compression functions.
219 * 'base' is chosen so that no non-NULL pointer results in a zero result
220 * which is reserved for NULL pointers.
223 duk_uint16_t
ajsheap_enc16(void *ud
, void *p
) {
225 char *base
= (char *) ajsheap_ram
- 4;
227 /* Userdata is not needed in this case but would be useful if heap
228 * pointer compression were used for multiple heaps. The userdata
229 * allows the callback to distinguish between heaps and their base
232 * If not needed, the userdata can be left out during compilation
233 * by simply ignoring the userdata argument of the pointer encode
234 * and decode macros. It is kept here so that any bugs in actually
235 * providing the value inside Duktape are revealed during compilation.
239 /* Ensure that we always get the heap_udata given in heap creation.
240 * (Useful for Duktape development, not needed for user programs.)
242 if (ud
!= (void *) 0xdeadbeef) {
243 fprintf(stderr
, "invalid udata for ajsheap_enc16: %p\n", ud
);
251 ret
= (duk_uint32_t
) (((char *) p
- base
) >> 2);
254 printf("ajsheap_enc16: %p -> %u\n", p
, (unsigned int) ret
);
256 if (ret
> 0xffffUL
) {
257 fprintf(stderr
, "Failed to compress pointer\n");
261 return (duk_uint16_t
) ret
;
263 void *ajsheap_dec16(void *ud
, duk_uint16_t x
) {
265 char *base
= (char *) ajsheap_ram
- 4;
267 /* See userdata discussion in ajsheap_enc16(). */
270 /* Ensure that we always get the heap_udata given in heap creation. */
271 if (ud
!= (void *) 0xdeadbeef) {
272 fprintf(stderr
, "invalid udata for ajsheap_dec16: %p\n", ud
);
280 ret
= (void *) (base
+ (((duk_uint32_t
) x
) << 2));
283 printf("ajsheap_dec16: %u -> %p\n", (unsigned int) x
, ret
);
289 * Simplified example of an external strings strategy where incoming strings
290 * are written sequentially into a fixed, memory mapped flash area.
292 * The example first scans if the string is already in the flash (which may
293 * happen if the same string is interned multiple times), then adds it to
294 * flash if there is space.
296 * This example is too slow to be used in a real world application: there
297 * should be e.g. a hash table to quickly check for strings that are already
298 * present in the string data (similarly to how string interning works in
302 static uint8_t ajsheap_strdata
[65536];
303 static size_t ajsheap_strdata_used
= 0;
305 const void *ajsheap_extstr_check_1(const void *ptr
, duk_size_t len
) {
311 (void) safe_print_chars
; /* potentially unused */
314 /* It's not worth it to make very small strings external, as
315 * they would take the same space anyway. Also avoids zero
316 * length degenerate case.
322 * Check if we already have the string. Be careful to compare for
323 * NUL terminator too, it is NOT present in 'ptr'. This algorithm
324 * is too simplistic and way too slow for actual use.
327 initial
= ((const uint8_t *) ptr
)[0];
328 for (p
= ajsheap_strdata
, p_end
= p
+ ajsheap_strdata_used
; p
!= p_end
; p
++) {
332 left
= (size_t) (p_end
- p
);
333 if (left
>= len
+ 1 &&
334 memcmp(p
, ptr
, len
) == 0 &&
338 printf("ajsheap_extstr_check_1: ptr=%p, len=%ld ",
339 (void *) ptr
, (long) len
);
340 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
341 printf(" -> existing %p (used=%ld)\n",
342 (void *) ret
, (long) ajsheap_strdata_used
);
349 * Not present yet, check if we have space. Again, be careful to
350 * ensure there is space for a NUL following the input data.
353 if (ajsheap_strdata_used
+ len
+ 1 > sizeof(ajsheap_strdata
)) {
355 printf("ajsheap_extstr_check_1: ptr=%p, len=%ld ", (void *) ptr
, (long) len
);
356 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
357 printf(" -> no space (used=%ld)\n", (long) ajsheap_strdata_used
);
363 * There is space, add the string to our collection, being careful
367 ret
= ajsheap_strdata
+ ajsheap_strdata_used
;
368 memcpy(ret
, ptr
, len
);
369 ret
[len
] = (uint8_t) 0;
370 ajsheap_strdata_used
+= len
+ 1;
373 printf("ajsheap_extstr_check_1: ptr=%p, len=%ld -> ", (void *) ptr
, (long) len
);
374 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
375 printf(" -> %p (used=%ld)\n", (void *) ret
, (long) ajsheap_strdata_used
);
377 return (const void *) ret
;
380 void ajsheap_extstr_free_1(const void *ptr
) {
383 printf("ajsheap_extstr_free_1: freeing extstr %p -> ", ptr
);
384 safe_print_chars((const char *) ptr
, DUK_SIZE_MAX
, 1 /*until_nul*/);
390 * Simplified example of an external strings strategy where a set of strings
391 * is gathered during application compile time and baked into the application
394 * Duktape built-in strings are available from duk_build_meta.json, see
395 * util/duk_meta_to_strarray.py. There may also be a lot of application
396 * specific strings, e.g. those used by application specific APIs. These
397 * must be gathered through some other means, see e.g. util/scan_strings.py.
400 static const char *strdata_duk_builtin_strings
[] = {
402 * These strings are from util/duk_meta_to_strarray.py
428 "{\x22" "_func\x22" ":true}",
429 "{\x22" "_ninf\x22" ":true}",
430 "{\x22" "_inf\x22" ":true}",
431 "{\x22" "_nan\x22" ":true}",
432 "{\x22" "_undef\x22" ":true}",
573 "setUTCMilliseconds",
577 "getUTCMilliseconds",
594 "toLocaleTimeString",
595 "toLocaleDateString",
649 "propertyIsEnumerable",
672 "getOwnPropertyNames",
673 "getOwnPropertyDescriptor",
681 "encodeURIComponent",
683 "decodeURIComponent",
743 * These strings are manually added, and would be gathered in some
744 * application specific manner.
755 const void *ajsheap_extstr_check_2(const void *ptr
, duk_size_t len
) {
758 (void) safe_print_chars
; /* potentially unused */
760 /* Linear scan. An actual implementation would need some acceleration
761 * structure, e.g. select a sublist based on first character.
763 * NOTE: input string (behind 'ptr' with 'len' bytes) DOES NOT have a
764 * trailing NUL character. Any strings returned from this function
765 * MUST have a trailing NUL character.
768 n
= (int) (sizeof(strdata_duk_builtin_strings
) / sizeof(const char *));
769 for (i
= 0; i
< n
; i
++) {
772 str
= strdata_duk_builtin_strings
[i
];
773 if (strlen(str
) == len
&& memcmp(ptr
, (const void *) str
, len
) == 0) {
775 printf("ajsheap_extstr_check_2: ptr=%p, len=%ld ",
776 (void *) ptr
, (long) len
);
777 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
778 printf(" -> constant string index %ld\n", (long) i
);
780 return (void *) strdata_duk_builtin_strings
[i
];
785 printf("ajsheap_extstr_check_2: ptr=%p, len=%ld ",
786 (void *) ptr
, (long) len
);
787 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
788 printf(" -> not found\n");
793 void ajsheap_extstr_free_2(const void *ptr
) {
796 printf("ajsheap_extstr_free_2: freeing extstr %p -> ", ptr
);
797 safe_print_chars((const char *) ptr
, DUK_SIZE_MAX
, 1 /*until_nul*/);
803 * External strings strategy intended for valgrind testing: external strings
804 * are allocated using malloc()/free() so that valgrind can be used to ensure
805 * that strings are e.g. freed exactly once.
808 const void *ajsheap_extstr_check_3(const void *ptr
, duk_size_t len
) {
811 (void) safe_print_chars
; /* potentially unused */
813 ret
= malloc((size_t) len
+ 1);
816 printf("ajsheap_extstr_check_3: ptr=%p, len=%ld ",
817 (void *) ptr
, (long) len
);
818 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
819 printf(" -> malloc failed, return NULL\n");
821 return (const void *) NULL
;
825 memcpy((void *) ret
, ptr
, (size_t) len
);
827 ret
[len
] = (duk_uint8_t
) 0;
830 printf("ajsheap_extstr_check_3: ptr=%p, len=%ld ",
831 (void *) ptr
, (long) len
);
832 safe_print_chars((const char *) ptr
, len
, 0 /*until_nul*/);
833 printf(" -> %p\n", (void *) ret
);
835 return (const void *) ret
;
838 void ajsheap_extstr_free_3(const void *ptr
) {
841 printf("ajsheap_extstr_free_3: freeing extstr %p -> ", ptr
);
842 safe_print_chars((const char *) ptr
, DUK_SIZE_MAX
, 1 /*until_nul*/);
849 * Execution timeout example
852 #define AJSHEAP_EXEC_TIMEOUT 5 /* seconds */
854 static time_t curr_pcall_start
= 0;
855 static long exec_timeout_check_counter
= 0;
857 void ajsheap_start_exec_timeout(void) {
858 curr_pcall_start
= time(NULL
);
861 void ajsheap_clear_exec_timeout(void) {
862 curr_pcall_start
= 0;
865 duk_bool_t
ajsheap_exec_timeout_check(void *udata
) {
866 time_t now
= time(NULL
);
867 time_t diff
= now
- curr_pcall_start
;
869 (void) udata
; /* not needed */
871 exec_timeout_check_counter
++;
873 printf("exec timeout check %ld: now=%ld, start=%ld, diff=%ld\n",
874 (long) exec_timeout_check_counter
, (long) now
, (long) curr_pcall_start
, (long) diff
);
878 if (curr_pcall_start
== 0) {
879 /* protected call not yet running */
882 if (diff
> AJSHEAP_EXEC_TIMEOUT
) {
888 #else /* DUK_CMDLINE_AJSHEAP */
890 int ajs_dummy
= 0; /* to avoid empty source file */
892 #endif /* DUK_CMDLINE_AJSHEAP */