/* * Examples for low memory techniques */ #if defined(DUK_CMDLINE_LOWMEM) #include #include #include #include #include "duktape.h" #include "duk_cmdline.h" #include "duk_alloc_pool.h" #if defined(DUK_USE_ROM_OBJECTS) && defined(DUK_USE_HEAPPTR16) /* Pointer compression with ROM strings/objects: * * For now, use DUK_USE_ROM_OBJECTS to signal the need for compressed ROM * pointers. DUK_USE_ROM_PTRCOMP_FIRST is provided for the ROM pointer * compression range minimum to avoid duplication in user code. */ #if 0 /* This extern declaration is provided by duktape.h, array provided by duktape.c. */ extern const void * const duk_rom_compressed_pointers[]; #endif static const void *duk__romptr_low = NULL; static const void *duk__romptr_high = NULL; #define DUK__ROMPTR_COMPRESSION #define DUK__ROMPTR_FIRST ((duk_uint_t) DUK_USE_ROM_PTRCOMP_FIRST) #endif #define LOWMEM_NUM_POOLS 28 #define LOWMEM_HEAP_SIZE (255 * 1024) static const duk_pool_config lowmem_config[LOWMEM_NUM_POOLS] = { { 8, 10 * 8, 0 }, { 12, 600 * 12, 0 }, { 16, 300 * 16, 0 }, { 20, 300 * 20, 0 }, { 24, 300 * 24, 0 }, { 28, 250 * 28, 0 }, { 32, 150 * 32, 0 }, { 40, 150 * 40, 0 }, { 48, 50 * 48, 0 }, { 52, 50 * 52, 0 }, { 56, 50 * 56, 0 }, { 60, 50 * 60, 0 }, { 64, 50 * 64, 0 }, { 96, 50 * 96, 0 }, { 196, 0, 196 }, /* duk_heap, with heap ptr compression, ROM strings+objects */ { 232, 0, 232 }, /* duk_hthread, with heap ptr compression, ROM strings+objects */ { 256, 16 * 256, 0 }, { 288, 1 * 288, 0 }, { 320, 1 * 320, 0 }, { 400, 0, 400 }, /* duk_hthread, with heap ptr compression, RAM strings+objects */ { 520, 0, 520 }, /* duk_heap, with heap ptr compression, RAM strings+objects */ { 512, 16 * 512, 0 }, { 768, 0, 768 }, /* initial value stack for packed duk_tval */ { 1024, 6 * 1024, 0 }, { 2048, 5 * 2048, 0 }, { 4096, 3 * 4096, 0 }, { 8192, 3 * 8192, 0 }, { 16384, 1 * 16384, 0 }, }; static duk_pool_state lowmem_state[LOWMEM_NUM_POOLS]; static duk_pool_global lowmem_global; void *lowmem_pool_ptr = NULL; uint8_t *lowmem_ram = NULL; static void *duk__lose_const(const void *ptr) { /* Somewhat portable way of losing a const without warnings. * Another approach is to cast through intptr_t, but that * type is not always available. */ union { const void *p; void *q; } u; u.p = ptr; return u.q; } static void duk__safe_print_chars(const char *p, duk_size_t len, int until_nul) { duk_size_t i; fprintf(stderr, "\""); for (i = 0; i < len; i++) { unsigned char x = (unsigned char) p[i]; if (until_nul && x == 0U) { break; } if (x < 0x20 || x >= 0x7e || x == '"' || x == '\'' || x == '\\') { fprintf(stderr, "\\x%02x", (int) x); } else { fprintf(stderr, "%c", (char) x); } } fprintf(stderr, "\""); } void lowmem_init(void) { void *ptr; lowmem_ram = (uint8_t *) malloc(LOWMEM_HEAP_SIZE); if (lowmem_ram == NULL) { fprintf(stderr, "Failed to allocate lowmem heap\n"); fflush(stderr); exit(1); } ptr = duk_alloc_pool_init((char *) lowmem_ram, LOWMEM_HEAP_SIZE, lowmem_config, lowmem_state, LOWMEM_NUM_POOLS, &lowmem_global); if (ptr == NULL) { free(lowmem_ram); lowmem_ram = NULL; fprintf(stderr, "Failed to init lowmem pool\n"); fflush(stderr); exit(1); } lowmem_pool_ptr = ptr; #if defined(DUK__ROMPTR_COMPRESSION) /* Scan ROM pointer range for faster detection of "is 'p' a ROM pointer" * later on. */ if (1) { const void * const * ptrs = (const void * const *) duk_rom_compressed_pointers; duk__romptr_low = duk__romptr_high = (const void *) *ptrs; while (*ptrs) { if (*ptrs > duk__romptr_high) { duk__romptr_high = (const void *) *ptrs; } if (*ptrs < duk__romptr_low) { duk__romptr_low = (const void *) *ptrs; } ptrs++; } fprintf(stderr, "romptrs: low=%p high=%p\n", (const void *) duk__romptr_low, (const void *) duk__romptr_high); fflush(stderr); } #endif } void lowmem_free(void) { if (lowmem_ram != NULL) { free(lowmem_ram); lowmem_ram = NULL; } lowmem_pool_ptr = NULL; } static duk_ret_t lowmem__dump_binding(duk_context *ctx) { lowmem_dump(); return 0; } void lowmem_dump(void) { int i; duk_pool_global_stats global_stats; for (i = 0; i < LOWMEM_NUM_POOLS; i++) { duk_pool_state *s = &lowmem_state[i]; duk_pool_stats stats; duk_alloc_pool_get_pool_stats(s, &stats); fprintf(stderr, " %2ld: %4ld %5ldB | free %4ld %5ldB | used %4ld %5ldB | waste %5ldB | hwm %4ld (%3ld%%)%s\n", (long) i, (long) s->count, (long) s->size, (long) stats.free_count, (long) stats.free_bytes, (long) stats.used_count, (long) stats.used_bytes, (long) stats.waste_bytes, (long) stats.hwm_used_count, (long) ((double) stats.hwm_used_count / (double) s->count * 100.0), (stats.hwm_used_count == s->count ? " !" : "")); } /* This causes another walk over the individual pools which is a bit * inelegant, but we want the highwater mark stats too. */ duk_alloc_pool_get_global_stats(&lowmem_global, &global_stats); fprintf(stderr, " TOTAL: %ld bytes used, %ld bytes waste, %ld bytes free, %ld bytes total; highwater %ld used, %ld waste\n", (long) global_stats.used_bytes, (long) global_stats.waste_bytes, (long) global_stats.free_bytes, (long) (global_stats.used_bytes + global_stats.free_bytes), (long) global_stats.hwm_used_bytes, (long) global_stats.hwm_waste_bytes); fflush(stderr); } void lowmem_register(duk_context *ctx) { duk_push_global_object(ctx); duk_push_string(ctx, "dumpHeap"); duk_push_c_function(ctx, lowmem__dump_binding, 0); duk_def_prop(ctx, -3, DUK_DEFPROP_SET_WRITABLE | DUK_DEFPROP_CLEAR_ENUMERABLE | DUK_DEFPROP_SET_CONFIGURABLE | DUK_DEFPROP_HAVE_VALUE); duk_pop(ctx); } /* * Wrapped alloc functions * * Used to write an alloc log. */ static FILE *lowmem_alloc_log = NULL; static void lowmem_write_alloc_log(const char *fmt, ...) { va_list ap; char buf[256]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); buf[sizeof(buf) - 1] = (char) 0; va_end(ap); if (lowmem_alloc_log == NULL) { lowmem_alloc_log = fopen("/tmp/lowmem-alloc-log.txt", "wb"); if (lowmem_alloc_log == NULL) { fprintf(stderr, "WARNING: failed to write alloc log, ignoring\n"); fflush(stderr); return; } } (void) fwrite((const void *) buf, 1, strlen(buf), lowmem_alloc_log); (void) fflush(lowmem_alloc_log); } void *lowmem_alloc_wrapped(void *udata, duk_size_t size) { void *ret = duk_alloc_pool(udata, size); if (size > 0 && ret == NULL) { lowmem_write_alloc_log("A FAIL %ld\n", (long) size); } else if (ret == NULL) { lowmem_write_alloc_log("A NULL %ld\n", (long) size); } else { lowmem_write_alloc_log("A %p %ld\n", ret, (long) size); } return ret; } void *lowmem_realloc_wrapped(void *udata, void *ptr, duk_size_t size) { void *ret = duk_realloc_pool(udata, ptr, size); if (size > 0 && ret == NULL) { if (ptr == NULL) { lowmem_write_alloc_log("R NULL -1 FAIL %ld\n", (long) size); } else { lowmem_write_alloc_log("R %p -1 FAIL %ld\n", ptr, (long) size); } } else if (ret == NULL) { if (ptr == NULL) { lowmem_write_alloc_log("R NULL -1 NULL %ld\n", (long) size); } else { lowmem_write_alloc_log("R %p -1 NULL %ld\n", ptr, (long) size); } } else { if (ptr == NULL) { lowmem_write_alloc_log("R NULL -1 %p %ld\n", ret, (long) size); } else { lowmem_write_alloc_log("R %p -1 %p %ld\n", ptr, ret, (long) size); } } return ret; } void lowmem_free_wrapped(void *udata, void *ptr) { duk_free_pool(udata, ptr); if (ptr == NULL) { /* Ignore. */ } else { lowmem_write_alloc_log("F %p -1\n", ptr); } } /* * Example pointer compression functions. * * 'base' is chosen so that no non-NULL pointer results in a zero result * which is reserved for NULL pointers. */ duk_uint16_t lowmem_enc16(void *ud, void *p) { duk_uint32_t ret; char *base = (char *) lowmem_ram - 4; #if defined(DUK__ROMPTR_COMPRESSION) if (p >= duk__romptr_low && p <= duk__romptr_high) { /* The if-condition should be the fastest possible check * for "is 'p' in ROM?". If pointer is in ROM, we'd like * to compress it quickly. Here we just scan a ~1K array * which is very bad for performance and for illustration * only. */ const void * const * ptrs = duk_rom_compressed_pointers; while (*ptrs) { if (*ptrs == p) { ret = (duk_uint32_t) DUK__ROMPTR_FIRST + (duk_uint32_t) (ptrs - duk_rom_compressed_pointers); #if 0 fprintf(stderr, "lowmem_enc16: rom pointer: %p -> 0x%04lx\n", (void *) p, (long) ret); fflush(stderr); #endif return (duk_uint16_t) ret; } ptrs++; } /* We should really never be here: Duktape should only be * compressing pointers which are in the ROM compressed * pointers list, which are known at 'make dist' time. * We go on, causing a pointer compression error. */ fprintf(stderr, "lowmem_enc16: rom pointer: %p could not be compressed, should never happen\n", (void *) p); fflush(stderr); } #endif /* Userdata is not needed in this case but would be useful if heap * pointer compression were used for multiple heaps. The userdata * allows the callback to distinguish between heaps and their base * pointers. * * If not needed, the userdata can be left out during compilation * by simply ignoring the userdata argument of the pointer encode * and decode macros. It is kept here so that any bugs in actually * providing the value inside Duktape are revealed during compilation. */ (void) ud; #if 1 /* Ensure that we always get the heap_udata given in heap creation. * (Useful for Duktape development, not needed for user programs.) */ if (ud != (void *) lowmem_pool_ptr) { fprintf(stderr, "invalid udata for lowmem_enc16: %p\n", ud); fflush(stderr); } #endif if (p == NULL) { ret = 0; } else { ret = (duk_uint32_t) (((char *) p - base) >> 2); } #if 0 fprintf(stderr, "lowmem_enc16: %p -> %u\n", p, (unsigned int) ret); #endif if (ret > 0xffffUL) { fprintf(stderr, "Failed to compress pointer: %p (ret was %ld)\n", (void *) p, (long) ret); fflush(stderr); abort(); } #if defined(DUK__ROMPTR_COMPRESSION) if (ret >= (duk_uint32_t) DUK__ROMPTR_FIRST) { fprintf(stderr, "Failed to compress pointer, in 16-bit range but matches romptr range: %p (ret was %ld)\n", (void *) p, (long) ret); fflush(stderr); abort(); } #endif return (duk_uint16_t) ret; } void *lowmem_dec16(void *ud, duk_uint16_t x) { void *ret; char *base = (char *) lowmem_ram - 4; #if defined(DUK__ROMPTR_COMPRESSION) if (x >= (duk_uint16_t) DUK__ROMPTR_FIRST) { /* This is a blind lookup, could check index validity. * Duktape should never decompress a pointer which would * be out-of-bounds here. */ ret = (void *) duk__lose_const(duk_rom_compressed_pointers[x - (duk_uint16_t) DUK__ROMPTR_FIRST]); #if 0 fprintf(stderr, "lowmem_dec16: rom pointer: 0x%04lx -> %p\n", (long) x, ret); fflush(stderr); #endif return ret; } #endif /* See userdata discussion in lowmem_enc16(). */ (void) ud; #if 1 /* Ensure that we always get the heap_udata given in heap creation. */ if (ud != (void *) lowmem_pool_ptr) { fprintf(stderr, "invalid udata for lowmem_dec16: %p\n", ud); fflush(stderr); } #endif if (x == 0) { ret = NULL; } else { ret = (void *) (base + (((duk_uint32_t) x) << 2)); } #if 0 fprintf(stderr, "lowmem_dec16: %u -> %p\n", (unsigned int) x, ret); #endif return ret; } /* * Simplified example of an external strings strategy where incoming strings * are written sequentially into a fixed, memory mapped flash area. * * The example first scans if the string is already in the flash (which may * happen if the same string is interned multiple times), then adds it to * flash if there is space. * * This example is too slow to be used in a real world application: there * should be e.g. a hash table to quickly check for strings that are already * present in the string data (similarly to how string interning works in * Duktape itself). */ static uint8_t lowmem_strdata[65536]; static size_t lowmem_strdata_used = 0; const void *lowmem_extstr_check_1(const void *ptr, duk_size_t len) { uint8_t *p, *p_end; uint8_t initial; uint8_t *ret; size_t left; (void) duk__safe_print_chars; /* potentially unused */ if (len <= 3) { /* It's not worth it to make very small strings external, as * they would take the same space anyway. Also avoids zero * length degenerate case. */ return NULL; } /* * Check if we already have the string. Be careful to compare for * NUL terminator too, it is NOT present in 'ptr'. This algorithm * is too simplistic and way too slow for actual use. */ initial = ((const uint8_t *) ptr)[0]; for (p = lowmem_strdata, p_end = p + lowmem_strdata_used; p != p_end; p++) { if (*p != initial) { continue; } left = (size_t) (p_end - p); if (left >= len + 1 && memcmp(p, ptr, len) == 0 && p[len] == 0) { ret = p; #if 0 fprintf(stderr, "lowmem_extstr_check_1: ptr=%p, len=%ld ", (void *) ptr, (long) len); duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/); fprintf(stderr, " -> existing %p (used=%ld)\n", (void *) ret, (long) lowmem_strdata_used); #endif return ret; } } /* * Not present yet, check if we have space. Again, be careful to * ensure there is space for a NUL following the input data. */ if (lowmem_strdata_used + len + 1 > sizeof(lowmem_strdata)) { #if 0 fprintf(stderr, "lowmem_extstr_check_1: ptr=%p, len=%ld ", (void *) ptr, (long) len); duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/); fprintf(stderr, " -> no space (used=%ld)\n", (long) lowmem_strdata_used); #endif return NULL; } /* * There is space, add the string to our collection, being careful * to append the NUL. */ ret = lowmem_strdata + lowmem_strdata_used; memcpy(ret, ptr, len); ret[len] = (uint8_t) 0; lowmem_strdata_used += len + 1; #if 0 fprintf(stderr, "lowmem_extstr_check_1: ptr=%p, len=%ld -> ", (void *) ptr, (long) len); duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/); fprintf(stderr, " -> %p (used=%ld)\n", (void *) ret, (long) lowmem_strdata_used); #endif return (const void *) ret; } void lowmem_extstr_free_1(const void *ptr) { (void) ptr; #if 0 fprintf(stderr, "lowmem_extstr_free_1: freeing extstr %p -> ", ptr); duk__safe_print_chars((const char *) ptr, DUK_SIZE_MAX, 1 /*until_nul*/); fprintf(stderr, "\n"); #endif } /* * Simplified example of an external strings strategy where a set of strings * is gathered during application compile time and baked into the application * binary. * * Duktape built-in strings are available from duk_source_meta.json in a * prepared source directory, see tools/duk_meta_to_strarray.py. There * may also be a lot of application specific strings, e.g. those used by * application specific APIs. These must be gathered through some other * means, see e.g. tools/scan_strings.py. */ static const char *strdata_duk_builtin_strings[] = { /* * These strings are from tools/duk_meta_to_strarray.py */ "Logger", "Thread", "Pointer", "Buffer", "DecEnv", "ObjEnv", "", "global", "Arguments", "JSON", "Math", "Error", "RegExp", "Date", "Number", "Boolean", "String", "Array", "Function", "Object", "Null", "Undefined", "{_func:true}", "{\x22" "_func\x22" ":true}", "{\x22" "_ninf\x22" ":true}", "{\x22" "_inf\x22" ":true}", "{\x22" "_nan\x22" ":true}", "{\x22" "_undef\x22" ":true}", "toLogString", "clog", "l", "n", "fatal", "error", "warn", "debug", "trace", "raw", "fmt", "current", "resume", "compact", "jc", "jx", "base64", "hex", "dec", "enc", "fin", "gc", "act", "info", "version", "env", "modLoaded", "modSearch", "errThrow", "errCreate", "compile", "\x82" "Regbase", "\x82" "Thread", "\x82" "Handler", "\x82" "Finalizer", "\x82" "Callee", "\x82" "Map", "\x82" "Args", "\x82" "This", "\x82" "Pc2line", "\x82" "Source", "\x82" "Varenv", "\x82" "Lexenv", "\x82" "Varmap", "\x82" "Formals", "\x82" "Bytecode", "\x82" "Next", "\x82" "Target", "\x82" "Value", "pointer", "buffer", "\x82" "Tracedata", "lineNumber", "fileName", "pc", "stack", "ThrowTypeError", "Duktape", "id", "require", "__proto__", "setPrototypeOf", "ownKeys", "enumerate", "deleteProperty", "has", "Proxy", "callee", "Invalid Date", "[...]", "\x0a" "\x09", " ", ",", "-0", "+0", "0", "-Infinity", "+Infinity", "Infinity", "object", "string", "number", "boolean", "undefined", "stringify", "tan", "sqrt", "sin", "round", "random", "pow", "min", "max", "log", "floor", "exp", "cos", "ceil", "atan2", "atan", "asin", "acos", "abs", "SQRT2", "SQRT1_2", "PI", "LOG10E", "LOG2E", "LN2", "LN10", "E", "message", "name", "input", "index", "(?:)", "lastIndex", "multiline", "ignoreCase", "source", "test", "exec", "toGMTString", "setYear", "getYear", "toJSON", "toISOString", "toUTCString", "setUTCFullYear", "setFullYear", "setUTCMonth", "setMonth", "setUTCDate", "setDate", "setUTCHours", "setHours", "setUTCMinutes", "setMinutes", "setUTCSeconds", "setSeconds", "setUTCMilliseconds", "setMilliseconds", "setTime", "getTimezoneOffset", "getUTCMilliseconds", "getMilliseconds", "getUTCSeconds", "getSeconds", "getUTCMinutes", "getMinutes", "getUTCHours", "getHours", "getUTCDay", "getDay", "getUTCDate", "getDate", "getUTCMonth", "getMonth", "getUTCFullYear", "getFullYear", "getTime", "toLocaleTimeString", "toLocaleDateString", "toTimeString", "toDateString", "now", "UTC", "parse", "toPrecision", "toExponential", "toFixed", "POSITIVE_INFINITY", "NEGATIVE_INFINITY", "NaN", "MIN_VALUE", "MAX_VALUE", "substr", "trim", "toLocaleUpperCase", "toUpperCase", "toLocaleLowerCase", "toLowerCase", "substring", "split", "search", "replace", "match", "localeCompare", "charCodeAt", "charAt", "fromCharCode", "reduceRight", "reduce", "filter", "map", "forEach", "some", "every", "lastIndexOf", "indexOf", "unshift", "splice", "sort", "slice", "shift", "reverse", "push", "pop", "join", "concat", "isArray", "arguments", "caller", "bind", "call", "apply", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "valueOf", "toLocaleString", "toString", "constructor", "set", "get", "enumerable", "configurable", "writable", "value", "keys", "isExtensible", "isFrozen", "isSealed", "preventExtensions", "freeze", "seal", "defineProperties", "defineProperty", "create", "getOwnPropertyNames", "getOwnPropertyDescriptor", "getPrototypeOf", "prototype", "length", "alert", "print", "unescape", "escape", "encodeURIComponent", "encodeURI", "decodeURIComponent", "decodeURI", "isFinite", "isNaN", "parseFloat", "parseInt", "eval", "URIError", "TypeError", "SyntaxError", "ReferenceError", "RangeError", "EvalError", "break", "case", "catch", "continue", "debugger", "default", "delete", "do", "else", "finally", "for", "function", "if", "in", "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "class", "const", "enum", "export", "extends", "import", "super", "null", "true", "false", "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield", /* * These strings are manually added, and would be gathered in some * application specific manner. */ "foo", "bar", "quux", "enableFrob", "disableFrob" /* ... */ }; const void *lowmem_extstr_check_2(const void *ptr, duk_size_t len) { int i, n; (void) duk__safe_print_chars; /* potentially unused */ /* Linear scan. An actual implementation would need some acceleration * structure, e.g. select a sublist based on first character. * * NOTE: input string (behind 'ptr' with 'len' bytes) DOES NOT have a * trailing NUL character. Any strings returned from this function * MUST have a trailing NUL character. */ n = (int) (sizeof(strdata_duk_builtin_strings) / sizeof(const char *)); for (i = 0; i < n; i++) { const char *str; str = strdata_duk_builtin_strings[i]; if (strlen(str) == len && memcmp(ptr, (const void *) str, len) == 0) { #if 0 fprintf(stderr, "lowmem_extstr_check_2: ptr=%p, len=%ld ", (void *) ptr, (long) len); duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/); fprintf(stderr, " -> constant string index %ld\n", (long) i); #endif return (void *) duk__lose_const(strdata_duk_builtin_strings[i]); } } #if 0 fprintf(stderr, "lowmem_extstr_check_2: ptr=%p, len=%ld ", (void *) ptr, (long) len); duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/); fprintf(stderr, " -> not found\n"); #endif return NULL; } void lowmem_extstr_free_2(const void *ptr) { (void) ptr; #if 0 fprintf(stderr, "lowmem_extstr_free_2: freeing extstr %p -> ", ptr); duk__safe_print_chars((const char *) ptr, DUK_SIZE_MAX, 1 /*until_nul*/); fprintf(stderr, "\n"); #endif } /* * External strings strategy intended for valgrind testing: external strings * are allocated using malloc()/free() so that valgrind can be used to ensure * that strings are e.g. freed exactly once. */ const void *lowmem_extstr_check_3(const void *ptr, duk_size_t len) { duk_uint8_t *ret; (void) duk__safe_print_chars; /* potentially unused */ ret = malloc((size_t) len + 1); if (ret == NULL) { #if 0 fprintf(stderr, "lowmem_extstr_check_3: ptr=%p, len=%ld ", (void *) ptr, (long) len); duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/); fprintf(stderr, " -> malloc failed, return NULL\n"); #endif return (const void *) NULL; } if (len > 0) { memcpy((void *) ret, ptr, (size_t) len); } ret[len] = (duk_uint8_t) 0; #if 0 fprintf(stderr, "lowmem_extstr_check_3: ptr=%p, len=%ld ", (void *) ptr, (long) len); duk__safe_print_chars((const char *) ptr, len, 0 /*until_nul*/); fprintf(stderr, " -> %p\n", (void *) ret); #endif return (const void *) ret; } void lowmem_extstr_free_3(const void *ptr) { (void) ptr; #if 0 fprintf(stderr, "lowmem_extstr_free_3: freeing extstr %p -> ", ptr); duk__safe_print_chars((const char *) ptr, DUK_SIZE_MAX, 1 /*until_nul*/); fprintf(stderr, "\n"); #endif free((void *) duk__lose_const(ptr)); } /* * Execution timeout example */ #define AJSHEAP_EXEC_TIMEOUT 5 /* seconds */ static time_t curr_pcall_start = 0; static long exec_timeout_check_counter = 0; void lowmem_start_exec_timeout(void) { curr_pcall_start = time(NULL); } void lowmem_clear_exec_timeout(void) { curr_pcall_start = 0; } duk_bool_t lowmem_exec_timeout_check(void *udata) { time_t now = time(NULL); time_t diff = now - curr_pcall_start; (void) udata; /* not needed */ exec_timeout_check_counter++; #if 0 fprintf(stderr, "exec timeout check %ld: now=%ld, start=%ld, diff=%ld\n", (long) exec_timeout_check_counter, (long) now, (long) curr_pcall_start, (long) diff); fflush(stderr); #endif if (curr_pcall_start == 0) { /* protected call not yet running */ return 0; } if (diff > AJSHEAP_EXEC_TIMEOUT) { return 1; } return 0; } #else /* DUK_CMDLINE_LOWMEM */ int duk_lowmem_dummy = 0; /* to avoid empty source file */ #endif /* DUK_CMDLINE_LOWMEM */