@mikrojs/native 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +198 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/cmake/mikrojs_bytecode.cmake +146 -0
- package/cmake.js +22 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/include/byteorder_apple.h +11 -0
- package/include/byteorder_windows.h +12 -0
- package/include/mikrojs/cbor_helpers.h +24 -0
- package/include/mikrojs/cutils_wrap.h +59 -0
- package/include/mikrojs/errors.h +144 -0
- package/include/mikrojs/mem.h +11 -0
- package/include/mikrojs/mik_color.h +32 -0
- package/include/mikrojs/mikrojs.h +331 -0
- package/include/mikrojs/platform.h +82 -0
- package/include/mikrojs/private.h +281 -0
- package/include/mikrojs/utils.h +125 -0
- package/package.json +100 -0
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/runtime/ble/ble.ts +231 -0
- package/runtime/ble/types.ts +194 -0
- package/runtime/ble/uuid.ts +89 -0
- package/runtime/ble/validators.ts +61 -0
- package/runtime/cbor/cbor.ts +1 -0
- package/runtime/cbor/types.ts +8 -0
- package/runtime/console/types.ts +50 -0
- package/runtime/env/env.ts +17 -0
- package/runtime/env/types.ts +12 -0
- package/runtime/format/types.ts +4 -0
- package/runtime/fs/fs.ts +93 -0
- package/runtime/fs/types.ts +92 -0
- package/runtime/globals.d.ts +87 -0
- package/runtime/http/helpers.ts +222 -0
- package/runtime/http/native.ts +151 -0
- package/runtime/http/request.ts +25 -0
- package/runtime/i2c/i2c.ts +35 -0
- package/runtime/i2c/types.ts +55 -0
- package/runtime/inspect/types.ts +10 -0
- package/runtime/internal.d.ts +456 -0
- package/runtime/kv/nvs.ts +17 -0
- package/runtime/kv/rtc.ts +17 -0
- package/runtime/kv/shared.ts +107 -0
- package/runtime/kv/types.ts +150 -0
- package/runtime/neopixel/neopixel.ts +38 -0
- package/runtime/neopixel/types.ts +27 -0
- package/runtime/pin/pin.ts +51 -0
- package/runtime/pin/types.ts +49 -0
- package/runtime/pwm/pwm.ts +32 -0
- package/runtime/pwm/types.ts +29 -0
- package/runtime/reader/reader.ts +167 -0
- package/runtime/reader/types.ts +34 -0
- package/runtime/result/native-result.node-shim.ts +44 -0
- package/runtime/result/result.ts +26 -0
- package/runtime/result/types.ts +60 -0
- package/runtime/schema/schema.ts +321 -0
- package/runtime/schema/types.ts +152 -0
- package/runtime/sleep/sleep.ts +14 -0
- package/runtime/sleep/types.ts +44 -0
- package/runtime/sntp/sntp.ts +54 -0
- package/runtime/sntp/types.ts +38 -0
- package/runtime/spi/spi.ts +31 -0
- package/runtime/spi/types.ts +42 -0
- package/runtime/stdio/stdio.ts +44 -0
- package/runtime/stdio/types.ts +22 -0
- package/runtime/stream/stream.ts +150 -0
- package/runtime/stream/types.ts +47 -0
- package/runtime/sys/sys.ts +90 -0
- package/runtime/sys/types.ts +131 -0
- package/runtime/test/test.ts +595 -0
- package/runtime/test/types.ts +97 -0
- package/runtime/uart/types.ts +75 -0
- package/runtime/uart/uart.ts +51 -0
- package/runtime/wifi/types.ts +156 -0
- package/runtime/wifi/wifi.ts +208 -0
- package/scripts/bundle-runtime.js +149 -0
- package/scripts/compare-minifiers.js +189 -0
- package/scripts/compile-bytecode.sh +38 -0
- package/scripts/copy-prebuild.js +20 -0
- package/scripts/generate-symbol-map.js +146 -0
- package/src/builtins.cpp +82 -0
- package/src/cutils_compat.c +38 -0
- package/src/eval_bytecode.cpp +42 -0
- package/src/fs.cpp +878 -0
- package/src/mem.cpp +63 -0
- package/src/mik_abort.cpp +160 -0
- package/src/mik_app_config.cpp +358 -0
- package/src/mik_cbor.cpp +334 -0
- package/src/mik_color.cpp +46 -0
- package/src/mik_console.cpp +422 -0
- package/src/mik_inspect.cpp +850 -0
- package/src/mik_repl.cpp +1122 -0
- package/src/mik_result.cpp +344 -0
- package/src/mik_stdio.cpp +147 -0
- package/src/mik_sys.cpp +239 -0
- package/src/mik_text_encoding.cpp +443 -0
- package/src/mikrojs.cpp +942 -0
- package/src/modules.cpp +944 -0
- package/src/platform_posix.cpp +134 -0
- package/src/timers.cpp +208 -0
- package/src/utils.cpp +173 -0
package/src/mikrojs.cpp
ADDED
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
#include "mikrojs/mikrojs.h"
|
|
2
|
+
|
|
3
|
+
#include <quickjs.h>
|
|
4
|
+
|
|
5
|
+
#include <cerrno>
|
|
6
|
+
#include <cstdio>
|
|
7
|
+
#include <fcntl.h>
|
|
8
|
+
#include <sys/stat.h>
|
|
9
|
+
#include <sys/time.h>
|
|
10
|
+
#include <unistd.h>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
#include "mikrojs/mem.h"
|
|
14
|
+
#include "mikrojs/platform.h"
|
|
15
|
+
#include "mikrojs/private.h"
|
|
16
|
+
#include "mikrojs/utils.h"
|
|
17
|
+
|
|
18
|
+
#define MIK__DEFAULT_STACK_SIZE 1024 * 1024 // 1 MB
|
|
19
|
+
|
|
20
|
+
/* JS malloc functions */
|
|
21
|
+
|
|
22
|
+
static void* mik__mf_calloc(void* opaque, size_t count, size_t size) {
|
|
23
|
+
(void)opaque;
|
|
24
|
+
return mik__calloc(count, size);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static void* mik__mf_malloc(void* opaque, size_t size) {
|
|
28
|
+
(void)opaque;
|
|
29
|
+
return mik__malloc(size);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static void mik__mf_free(void* opaque, void* ptr) {
|
|
33
|
+
(void)opaque;
|
|
34
|
+
mik__free(ptr);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static void* mik__mf_realloc(void* opaque, void* ptr, size_t size) {
|
|
38
|
+
(void)opaque;
|
|
39
|
+
return mik__realloc(ptr, size);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static const JSMallocFunctions mik_mf = {
|
|
43
|
+
.js_calloc = mik__mf_calloc,
|
|
44
|
+
.js_malloc = mik__mf_malloc,
|
|
45
|
+
.js_free = mik__mf_free,
|
|
46
|
+
.js_realloc = mik__mf_realloc,
|
|
47
|
+
.js_malloc_usable_size = mik__malloc_usable_size,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/* Build a frozen object from stored env vars. */
|
|
51
|
+
static JSValue mik__build_env_object(JSContext* ctx) {
|
|
52
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
53
|
+
JSValue env = JS_NewObject(ctx);
|
|
54
|
+
|
|
55
|
+
for (const auto& [key, value] : mik_rt->env_vars) {
|
|
56
|
+
JS_DefinePropertyValueStr(ctx, env, key.c_str(), JS_NewString(ctx, value.c_str()),
|
|
57
|
+
JS_PROP_ENUMERABLE);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Freeze to prevent mutation */
|
|
61
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
62
|
+
JSValue object_ctor = JS_GetPropertyStr(ctx, global, "Object");
|
|
63
|
+
JSValue freeze = JS_GetPropertyStr(ctx, object_ctor, "freeze");
|
|
64
|
+
JSValue frozen = JS_Call(ctx, freeze, object_ctor, 1, &env);
|
|
65
|
+
JS_FreeValue(ctx, frozen);
|
|
66
|
+
JS_FreeValue(ctx, freeze);
|
|
67
|
+
JS_FreeValue(ctx, object_ctor);
|
|
68
|
+
JS_FreeValue(ctx, global);
|
|
69
|
+
|
|
70
|
+
return env;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Helper: copy named properties from a namespace object into module exports */
|
|
74
|
+
static void mik__export_from_ns(JSContext* ctx, JSModuleDef* m, JSValue ns,
|
|
75
|
+
const char* const* names, size_t count) {
|
|
76
|
+
for (size_t i = 0; i < count; i++) {
|
|
77
|
+
JS_SetModuleExport(ctx, m, names[i], JS_GetPropertyStr(ctx, ns, names[i]));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Helper: register export names on a module definition */
|
|
82
|
+
static void mik__add_exports(JSContext* ctx, JSModuleDef* m, const char* const* names,
|
|
83
|
+
size_t count) {
|
|
84
|
+
for (size_t i = 0; i < count; i++) {
|
|
85
|
+
JS_AddModuleExport(ctx, m, names[i]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static const char* const sys_exports[] = {
|
|
90
|
+
"evalScript", "memoryUsage", "jsMemoryUsage", "gc", "setTime", "uptime",
|
|
91
|
+
"restart", "version", "board", "firmware", "deviceId", "activeTimers"};
|
|
92
|
+
|
|
93
|
+
static int mik__sys_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
94
|
+
JSValue ns = JS_NewObjectProto(ctx, JS_NULL);
|
|
95
|
+
mik__sys_api_init(ctx, ns);
|
|
96
|
+
mik__export_from_ns(ctx, m, ns, sys_exports, countof(sys_exports));
|
|
97
|
+
JS_FreeValue(ctx, ns);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static const char* const stdio_exports[] = {"stdout", "stdin"};
|
|
102
|
+
|
|
103
|
+
static int mik__stdio_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
104
|
+
JSValue ns = JS_NewObjectProto(ctx, JS_NULL);
|
|
105
|
+
mik__stdio_init(ctx, ns);
|
|
106
|
+
mik__export_from_ns(ctx, m, ns, stdio_exports, countof(stdio_exports));
|
|
107
|
+
JS_FreeValue(ctx, ns);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static JSValue mik__dispatch_event(JSContext* ctx, JSValue* event) {
|
|
112
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
113
|
+
CHECK_NOT_NULL(mik_rt);
|
|
114
|
+
|
|
115
|
+
if (mik_rt->freeing) {
|
|
116
|
+
return JS_UNDEFINED;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
JSValue global_obj = JS_GetGlobalObject(ctx);
|
|
120
|
+
JSValue ret = JS_Call(ctx, mik_rt->builtins.dispatch_event_func, global_obj, 1, event);
|
|
121
|
+
JS_FreeValue(ctx, global_obj);
|
|
122
|
+
|
|
123
|
+
return ret;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static void mik__promise_rejection_tracker(JSContext* ctx, JSValue promise, JSValue reason,
|
|
127
|
+
bool is_handled, void* opaque) {
|
|
128
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
129
|
+
CHECK_NOT_NULL(mik_rt);
|
|
130
|
+
|
|
131
|
+
if (mik_rt->freeing) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!is_handled) {
|
|
136
|
+
/* If the runtime hasn't loaded the event infrastructure yet,
|
|
137
|
+
* report and bail. */
|
|
138
|
+
if (JS_IsUndefined(mik_rt->builtins.promise_event_ctor) ||
|
|
139
|
+
JS_IsUndefined(mik_rt->builtins.dispatch_event_func)) {
|
|
140
|
+
/* During REPL eval, sync errors are wrapped by the async eval
|
|
141
|
+
* wrapper and appear as promise rejections — don't label them
|
|
142
|
+
* "(in promise)". Outside eval (e.g. timer callbacks), they
|
|
143
|
+
* are genuine unhandled promise rejections. */
|
|
144
|
+
bool in_promise = !mik__repl_is_evaluating();
|
|
145
|
+
mik__report_uncaught(ctx, reason, in_promise);
|
|
146
|
+
if (!MIK_IsReplActive()) {
|
|
147
|
+
/* Notify the error handler (e.g. host bridge) directly.
|
|
148
|
+
* Don't JS_Throw here — we're inside a QuickJS callback
|
|
149
|
+
* during promise resolution; throwing would corrupt engine
|
|
150
|
+
* state and cause mik__execute_jobs to dump the error a
|
|
151
|
+
* second time. */
|
|
152
|
+
if (mik_rt->error_handler_fn) {
|
|
153
|
+
mik_rt->error_handler_fn(ctx, reason, mik_rt->error_handler_opaque);
|
|
154
|
+
}
|
|
155
|
+
mik_rt->stop_requested = true;
|
|
156
|
+
MIK_Stop(mik_rt);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
JSValue event_name = JS_NewString(ctx, "unhandledrejection");
|
|
162
|
+
JSValue args[3];
|
|
163
|
+
args[0] = event_name;
|
|
164
|
+
args[1] = promise;
|
|
165
|
+
args[2] = reason;
|
|
166
|
+
|
|
167
|
+
JSValue event =
|
|
168
|
+
JS_CallConstructor(ctx, mik_rt->builtins.promise_event_ctor, countof(args), args);
|
|
169
|
+
if (JS_IsException(event)) {
|
|
170
|
+
JS_FreeValue(ctx, event_name);
|
|
171
|
+
mik_dump_error(ctx);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
JSValue ret = mik__dispatch_event(ctx, &event);
|
|
175
|
+
|
|
176
|
+
JS_FreeValue(ctx, event);
|
|
177
|
+
JS_FreeValue(ctx, event_name);
|
|
178
|
+
|
|
179
|
+
if (JS_IsException(ret)) {
|
|
180
|
+
mik_dump_error(ctx);
|
|
181
|
+
goto fail;
|
|
182
|
+
} else {
|
|
183
|
+
if (JS_ToBool(ctx, ret)) {
|
|
184
|
+
// The event wasn't cancelled, maybe abort.
|
|
185
|
+
fail:;
|
|
186
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
187
|
+
CHECK_NOT_NULL(mik_rt);
|
|
188
|
+
mik__report_uncaught(ctx, reason, true);
|
|
189
|
+
if (mik_rt->error_handler_fn) {
|
|
190
|
+
mik_rt->error_handler_fn(ctx, reason, mik_rt->error_handler_opaque);
|
|
191
|
+
}
|
|
192
|
+
mik_rt->stop_requested = true;
|
|
193
|
+
MIK_Stop(mik_rt);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
JS_FreeValue(ctx, ret);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
void MIK_DefaultOptions(MIKRunOptions* options) {
|
|
202
|
+
static MIKRunOptions default_options = {.mem_limit = 0, .stack_size = MIK__DEFAULT_STACK_SIZE};
|
|
203
|
+
|
|
204
|
+
memcpy(options, &default_options, sizeof(*options));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
MIKRuntime* MIK_NewRuntime(void) {
|
|
208
|
+
MIKRunOptions options;
|
|
209
|
+
MIK_DefaultOptions(&options);
|
|
210
|
+
return MIK_NewRuntimeInternal(&options);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
MIKRuntime* MIK_NewRuntimeOptions(MIKRunOptions* options) {
|
|
214
|
+
return MIK_NewRuntimeInternal(options);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static void mik__check_module_collisions(void);
|
|
218
|
+
|
|
219
|
+
MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
|
|
220
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
221
|
+
JSRuntime* rt = NULL;
|
|
222
|
+
JSContext* ctx = NULL;
|
|
223
|
+
MIKRuntime* mik_rt = static_cast<MIKRuntime*>(mik__mallocz(sizeof(*mik_rt)));
|
|
224
|
+
|
|
225
|
+
/* Placement-new to initialize C++ members (vectors, strings) */
|
|
226
|
+
new (mik_rt) MIKRuntime();
|
|
227
|
+
|
|
228
|
+
memcpy(&mik_rt->options, options, sizeof(*options));
|
|
229
|
+
MIK_DefaultConfig(&mik_rt->config);
|
|
230
|
+
|
|
231
|
+
rt = JS_NewRuntime2(&mik_mf, NULL);
|
|
232
|
+
CHECK_NOT_NULL(rt);
|
|
233
|
+
mik_rt->rt = rt;
|
|
234
|
+
|
|
235
|
+
/* Seed Math.random() with real entropy.
|
|
236
|
+
* On ESP32 the system clock starts near epoch 0 on every boot, producing
|
|
237
|
+
* the same seed. Use platform RNG to inject entropy. */
|
|
238
|
+
struct timeval tv_orig;
|
|
239
|
+
gettimeofday(&tv_orig, NULL);
|
|
240
|
+
struct timeval tv_rand = {
|
|
241
|
+
.tv_sec = tv_orig.tv_sec,
|
|
242
|
+
.tv_usec = static_cast<suseconds_t>(platform->random() % 1000000),
|
|
243
|
+
};
|
|
244
|
+
settimeofday(&tv_rand, NULL);
|
|
245
|
+
|
|
246
|
+
/* Replacement for JS_NewContext that drops DOMException. Per-intrinsic
|
|
247
|
+
* measurement showed DOMException alone costs ~4.3 KB heap per runtime,
|
|
248
|
+
* while the other optional intrinsics (Proxy ~274 B, Performance ~303 B)
|
|
249
|
+
* are cheap enough to keep for language/standards completeness. Eval
|
|
250
|
+
* must be kept because the JS_Eval C API crashes without it. */
|
|
251
|
+
ctx = JS_NewContextRaw(rt);
|
|
252
|
+
CHECK_NOT_NULL(ctx);
|
|
253
|
+
if (JS_AddIntrinsicBaseObjects(ctx) || JS_AddIntrinsicDate(ctx) ||
|
|
254
|
+
JS_AddIntrinsicEval(ctx) || JS_AddIntrinsicRegExp(ctx) ||
|
|
255
|
+
JS_AddIntrinsicJSON(ctx) || JS_AddIntrinsicProxy(ctx) ||
|
|
256
|
+
JS_AddIntrinsicMapSet(ctx) || JS_AddIntrinsicTypedArrays(ctx) ||
|
|
257
|
+
JS_AddIntrinsicPromise(ctx) || JS_AddIntrinsicWeakRef(ctx) ||
|
|
258
|
+
JS_AddPerformance(ctx)) {
|
|
259
|
+
JS_FreeContext(ctx);
|
|
260
|
+
ctx = NULL;
|
|
261
|
+
}
|
|
262
|
+
CHECK_NOT_NULL(ctx);
|
|
263
|
+
|
|
264
|
+
settimeofday(&tv_orig, NULL);
|
|
265
|
+
mik_rt->ctx = ctx;
|
|
266
|
+
|
|
267
|
+
JS_SetRuntimeOpaque(rt, mik_rt);
|
|
268
|
+
JS_SetContextOpaque(ctx, mik_rt);
|
|
269
|
+
|
|
270
|
+
/* Initialize lazily-assigned JSValue fields so module init code can
|
|
271
|
+
* distinguish "unset" from "set to some object". */
|
|
272
|
+
mik_rt->result_proto = JS_UNDEFINED;
|
|
273
|
+
mik_rt->result_ok_void_singleton = JS_UNDEFINED;
|
|
274
|
+
|
|
275
|
+
/* Default fs read cap: 64 KiB. Large enough for typical config/JSON
|
|
276
|
+
* payloads on MCU; small enough that a runaway readFile() can't
|
|
277
|
+
* exhaust the heap. Overridden via MIK_SetFSReadMax. */
|
|
278
|
+
mik_rt->fs_read_max = 65536;
|
|
279
|
+
|
|
280
|
+
/* Check for duplicate native module registrations (global registry) */
|
|
281
|
+
mik__check_module_collisions();
|
|
282
|
+
|
|
283
|
+
/* Set memory limit */
|
|
284
|
+
JS_SetMemoryLimit(rt, options->mem_limit);
|
|
285
|
+
|
|
286
|
+
/* Set stack size */
|
|
287
|
+
JS_SetMaxStackSize(rt, options->stack_size);
|
|
288
|
+
/* loader for ES modules */
|
|
289
|
+
JS_SetModuleLoaderFunc(rt, mik_module_normalizer, mik_module_loader, mik_rt);
|
|
290
|
+
|
|
291
|
+
/* unhandled promise rejection tracker */
|
|
292
|
+
JS_SetHostPromiseRejectionTracker(rt, mik__promise_rejection_tracker, NULL);
|
|
293
|
+
|
|
294
|
+
/* Register internal C modules */
|
|
295
|
+
JSModuleDef* sys_mod = JS_NewCModule(ctx, "native:sys", mik__sys_module_init);
|
|
296
|
+
CHECK_NOT_NULL(sys_mod);
|
|
297
|
+
mik__add_exports(ctx, sys_mod, sys_exports, countof(sys_exports));
|
|
298
|
+
|
|
299
|
+
/* Core native modules registered explicitly (not via MIK_REGISTER_MODULE
|
|
300
|
+
* which relies on linker magic that doesn't work in all build contexts).
|
|
301
|
+
*
|
|
302
|
+
* Result must be initialized before anything that might allocate a
|
|
303
|
+
* Result object — the helpers mik__result_ok/mik__result_err rely on
|
|
304
|
+
* rt->result_proto being set. */
|
|
305
|
+
mik__result_init(ctx);
|
|
306
|
+
mik__cbor_init(ctx);
|
|
307
|
+
|
|
308
|
+
/* Native mikrojs modules (replace bytecode builtins) */
|
|
309
|
+
mik__inspect_register(ctx);
|
|
310
|
+
mik__pub_fs_register(ctx);
|
|
311
|
+
|
|
312
|
+
/* Call registered native module init functions */
|
|
313
|
+
for (const auto& mod : mik_rt->native_modules) {
|
|
314
|
+
mod.init_fn(ctx);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
JSModuleDef* stdio_mod = JS_NewCModule(ctx, "native:stdio", mik__stdio_module_init);
|
|
318
|
+
CHECK_NOT_NULL(stdio_mod);
|
|
319
|
+
mik__add_exports(ctx, stdio_mod, stdio_exports, countof(stdio_exports));
|
|
320
|
+
|
|
321
|
+
/* Web standard globals */
|
|
322
|
+
JSValue global_obj = JS_GetGlobalObject(ctx);
|
|
323
|
+
mik__text_encoding_init(ctx, global_obj);
|
|
324
|
+
mik__abort_init(ctx, global_obj);
|
|
325
|
+
|
|
326
|
+
/* Load some builtin references for easy access */
|
|
327
|
+
mik_rt->builtins.dispatch_event_func = JS_GetPropertyStr(ctx, global_obj, "dispatchEvent");
|
|
328
|
+
mik_rt->builtins.promise_event_ctor =
|
|
329
|
+
JS_GetPropertyStr(mik_rt->ctx, global_obj, "PromiseRejectionEvent");
|
|
330
|
+
|
|
331
|
+
/* Timers */
|
|
332
|
+
mik_rt->timers = MIK_NewTimerRegistry();
|
|
333
|
+
mik__timers_init(ctx, global_obj);
|
|
334
|
+
|
|
335
|
+
/* Console global (native C++) */
|
|
336
|
+
mik__console_init(ctx, global_obj);
|
|
337
|
+
|
|
338
|
+
JS_FreeValue(ctx, global_obj);
|
|
339
|
+
|
|
340
|
+
/* Build import.meta.env as a frozen object from stored env vars. */
|
|
341
|
+
mik_rt->env_obj = mik__build_env_object(ctx);
|
|
342
|
+
|
|
343
|
+
/* Stdin */
|
|
344
|
+
mik_rt->stdin_state.on_data = JS_UNDEFINED;
|
|
345
|
+
|
|
346
|
+
return mik_rt;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
void MIK_FreeRuntime(MIKRuntime* mik_rt) {
|
|
350
|
+
mik_rt->freeing = true;
|
|
351
|
+
|
|
352
|
+
/* Release stdin handler */
|
|
353
|
+
if (!JS_IsUndefined(mik_rt->stdin_state.on_data)) {
|
|
354
|
+
JS_FreeValue(mik_rt->ctx, mik_rt->stdin_state.on_data);
|
|
355
|
+
mik_rt->stdin_state.on_data = JS_UNDEFINED;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* Destroy registered loop consumers */
|
|
359
|
+
for (const auto& consumer : mik_rt->loop_consumers) {
|
|
360
|
+
if (consumer.destroy_fn) {
|
|
361
|
+
consumer.destroy_fn(mik_rt->ctx);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* Destroy all timers */
|
|
366
|
+
mik__timers_destroy(mik_rt->ctx);
|
|
367
|
+
delete mik_rt->timers;
|
|
368
|
+
mik_rt->timers = nullptr;
|
|
369
|
+
|
|
370
|
+
/* Destroy the JS engine. */
|
|
371
|
+
JS_FreeValue(mik_rt->ctx, mik_rt->env_obj);
|
|
372
|
+
mik_rt->env_obj = JS_UNDEFINED;
|
|
373
|
+
JS_FreeValue(mik_rt->ctx, mik_rt->builtins.dispatch_event_func);
|
|
374
|
+
mik_rt->builtins.dispatch_event_func = JS_UNDEFINED;
|
|
375
|
+
JS_FreeValue(mik_rt->ctx, mik_rt->builtins.promise_event_ctor);
|
|
376
|
+
mik_rt->builtins.promise_event_ctor = JS_UNDEFINED;
|
|
377
|
+
JS_FreeValue(mik_rt->ctx, mik_rt->result_ok_void_singleton);
|
|
378
|
+
mik_rt->result_ok_void_singleton = JS_UNDEFINED;
|
|
379
|
+
JS_FreeValue(mik_rt->ctx, mik_rt->result_proto);
|
|
380
|
+
mik_rt->result_proto = JS_UNDEFINED;
|
|
381
|
+
JS_FreeContext(mik_rt->ctx);
|
|
382
|
+
JS_FreeRuntime(mik_rt->rt);
|
|
383
|
+
|
|
384
|
+
/* Call destructor for C++ members, then free */
|
|
385
|
+
mik_rt->~MIKRuntime();
|
|
386
|
+
mik__free(mik_rt);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
JSContext* MIK_GetJSContext(MIKRuntime* mik_rt) { return mik_rt->ctx; }
|
|
390
|
+
|
|
391
|
+
MIKRuntime* MIK_GetRuntime(JSContext* ctx) {
|
|
392
|
+
return static_cast<MIKRuntime*>(JS_GetContextOpaque(ctx));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
void MIK_SetFSBasePath(MIKRuntime* mik_rt, const char* base_path) {
|
|
396
|
+
mik_rt->fs_base_path = base_path;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
void MIK_SetFSRoot(MIKRuntime* mik_rt, const char* fs_root) {
|
|
400
|
+
mik_rt->fs_root = fs_root;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
void MIK_SetFSLimit(MIKRuntime* mik_rt, size_t limit) {
|
|
404
|
+
mik_rt->fs_limit = limit;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
void MIK_SetFSReadMax(MIKRuntime* mik_rt, size_t bytes) {
|
|
408
|
+
mik_rt->fs_read_max = bytes ? bytes : 65536;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
void MIK_SetEnvVar(MIKRuntime* mik_rt, const char* key, const char* value) {
|
|
412
|
+
for (auto& [k, v] : mik_rt->env_vars) {
|
|
413
|
+
if (k == key) {
|
|
414
|
+
v = value;
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
mik_rt->env_vars.emplace_back(key, value);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
void MIK_RebuildEnv(MIKRuntime* mik_rt) {
|
|
422
|
+
JS_FreeValue(mik_rt->ctx, mik_rt->env_obj);
|
|
423
|
+
mik_rt->env_obj = mik__build_env_object(mik_rt->ctx);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
void MIK_RegisterVirtualModule(MIKRuntime* mik_rt, const char* name, const char* source,
|
|
427
|
+
size_t source_len) {
|
|
428
|
+
mik_rt->virtual_modules[name] = std::string(source, source_len);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
void MIK_SetPreprocessor(MIKRuntime* mik_rt, MIKPreprocessFn fn, void* opaque) {
|
|
432
|
+
mik_rt->preprocess_fn = fn;
|
|
433
|
+
mik_rt->preprocess_opaque = opaque;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
void MIK_SetErrorHandler(MIKRuntime* mik_rt, MIKErrorHandlerFn fn, void* opaque) {
|
|
437
|
+
mik_rt->error_handler_fn = fn;
|
|
438
|
+
mik_rt->error_handler_opaque = opaque;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
void MIK_RegisterNativeModuleInit(MIKRuntime* mik_rt, const char* name,
|
|
442
|
+
MIKNativeModuleInitFn init_fn) {
|
|
443
|
+
mik_rt->native_modules.push_back({name, init_fn});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
void MIK_RegisterLoopConsumer(MIKRuntime* mik_rt, MIKLoopConsumeFn consume_fn,
|
|
447
|
+
MIKLoopDestroyFn destroy_fn) {
|
|
448
|
+
mik_rt->loop_consumers.push_back({consume_fn, destroy_fn});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
void MIK_SetModuleData(MIKRuntime* mik_rt, int slot, void* data) {
|
|
452
|
+
if (slot >= 0 && slot < MIK_MODULE_DATA_SLOTS) {
|
|
453
|
+
mik_rt->module_data[slot] = data;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
void* MIK_GetModuleData(MIKRuntime* mik_rt, int slot) {
|
|
458
|
+
if (slot >= 0 && slot < MIK_MODULE_DATA_SLOTS) {
|
|
459
|
+
return mik_rt->module_data[slot];
|
|
460
|
+
}
|
|
461
|
+
return nullptr;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* ── Global module registry (populated by MIK_REGISTER_MODULE constructors) ── */
|
|
465
|
+
|
|
466
|
+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
467
|
+
mik_module_desc_t* mik__module_registry_head = nullptr;
|
|
468
|
+
|
|
469
|
+
int MIK_AllocModuleSlot(MIKRuntime* mik_rt) {
|
|
470
|
+
int slot = mik_rt->next_module_slot++;
|
|
471
|
+
CHECK(slot < MIK_MODULE_DATA_SLOTS);
|
|
472
|
+
return slot;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
static void mik__check_module_collisions(void) {
|
|
476
|
+
for (mik_module_desc_t* a = mik__module_registry_head; a != nullptr; a = a->next) {
|
|
477
|
+
for (mik_module_desc_t* b = a->next; b != nullptr; b = b->next) {
|
|
478
|
+
if (strcmp(a->name, b->name) == 0) {
|
|
479
|
+
fprintf(stderr, "FATAL: native module name collision: \"%s\"\n", a->name);
|
|
480
|
+
abort();
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
for (mik_ext_builtin_t* a = mik__ext_builtin_head; a != nullptr; a = a->next) {
|
|
485
|
+
for (mik_ext_builtin_t* b = a->next; b != nullptr; b = b->next) {
|
|
486
|
+
if (strcmp(a->name, b->name) == 0) {
|
|
487
|
+
fprintf(stderr, "FATAL: builtin module name collision: \"%s\"\n", a->name);
|
|
488
|
+
abort();
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
void mik__execute_jobs(JSContext* ctx) {
|
|
495
|
+
JSContext* ctx1;
|
|
496
|
+
int err;
|
|
497
|
+
|
|
498
|
+
/* execute the pending jobs */
|
|
499
|
+
for (;;) {
|
|
500
|
+
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
|
|
501
|
+
if (err <= 0) {
|
|
502
|
+
if (err < 0) {
|
|
503
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx1);
|
|
504
|
+
if (mik_rt && mik_rt->error_handler_fn && JS_HasException(ctx1)) {
|
|
505
|
+
JSValue exc = JS_GetException(ctx1);
|
|
506
|
+
mik_rt->error_handler_fn(ctx1, exc, mik_rt->error_handler_opaque);
|
|
507
|
+
JS_Throw(ctx1, exc);
|
|
508
|
+
}
|
|
509
|
+
mik_dump_error(ctx1);
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* main loop which calls the user JS callbacks */
|
|
517
|
+
int MIK_Loop(MIKRuntime* mik_rt) {
|
|
518
|
+
if (mik_rt->stop_requested) {
|
|
519
|
+
return 1;
|
|
520
|
+
}
|
|
521
|
+
if (JS_HasException(mik_rt->ctx)) {
|
|
522
|
+
if (mik_rt->error_handler_fn) {
|
|
523
|
+
JSValue exc = JS_GetException(mik_rt->ctx);
|
|
524
|
+
mik_rt->error_handler_fn(mik_rt->ctx, exc, mik_rt->error_handler_opaque);
|
|
525
|
+
/* Re-set the exception so mik_dump_error can consume it */
|
|
526
|
+
JS_Throw(mik_rt->ctx, exc);
|
|
527
|
+
}
|
|
528
|
+
mik_dump_error(mik_rt->ctx);
|
|
529
|
+
return 1;
|
|
530
|
+
}
|
|
531
|
+
mik__stdin_consume(mik_rt->ctx);
|
|
532
|
+
mik__timers_consume(mik_rt->ctx);
|
|
533
|
+
|
|
534
|
+
/* Call registered loop consumers */
|
|
535
|
+
for (const auto& consumer : mik_rt->loop_consumers) {
|
|
536
|
+
consumer.consume_fn(mik_rt->ctx);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
mik__execute_jobs(mik_rt->ctx);
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
void MIK_SetConfig(MIKRuntime* mik_rt, const MIKConfig* config) {
|
|
544
|
+
CHECK_NOT_NULL(mik_rt);
|
|
545
|
+
CHECK_NOT_NULL(config);
|
|
546
|
+
memcpy(&mik_rt->config, config, sizeof(*config));
|
|
547
|
+
if (config->fs_read_max > 0) {
|
|
548
|
+
mik_rt->fs_read_max = config->fs_read_max;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/* Record a profile entry for the entry module loaded via MIK_EvalModule.
|
|
553
|
+
* Uses the same exclusive-delta accounting as the module loader. */
|
|
554
|
+
static void mik__record_entry_profile(MIKRuntime* mik_rt,
|
|
555
|
+
JSRuntime* rt,
|
|
556
|
+
const char* name,
|
|
557
|
+
int64_t malloc_before,
|
|
558
|
+
size_t entries_before) {
|
|
559
|
+
JSMemoryUsage after;
|
|
560
|
+
JS_ComputeMemoryUsage(rt, &after);
|
|
561
|
+
size_t total_delta = after.malloc_size > malloc_before
|
|
562
|
+
? (size_t)(after.malloc_size - malloc_before)
|
|
563
|
+
: 0;
|
|
564
|
+
size_t children_delta = 0;
|
|
565
|
+
for (size_t i = entries_before; i < mik_rt->profile_entries.size(); i++) {
|
|
566
|
+
children_delta += mik_rt->profile_entries[i].delta_bytes;
|
|
567
|
+
}
|
|
568
|
+
size_t exclusive_delta = total_delta > children_delta ? total_delta - children_delta : 0;
|
|
569
|
+
|
|
570
|
+
MIKProfileEntry entry = {};
|
|
571
|
+
size_t name_len = strlen(name);
|
|
572
|
+
if (name_len >= MIK_PROFILE_NAME_MAX) {
|
|
573
|
+
name_len = MIK_PROFILE_NAME_MAX - 1;
|
|
574
|
+
}
|
|
575
|
+
memcpy(entry.name, name, name_len);
|
|
576
|
+
entry.name[name_len] = '\0';
|
|
577
|
+
entry.delta_bytes = exclusive_delta;
|
|
578
|
+
entry.order = mik_rt->profile_entries.size();
|
|
579
|
+
if (mik_rt->profile_entries.size() < mik_rt->profile_entries.capacity()) {
|
|
580
|
+
mik_rt->profile_entries.push_back(entry);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
void MIK_EnableProfiling(MIKRuntime* mik_rt) {
|
|
585
|
+
CHECK_NOT_NULL(mik_rt);
|
|
586
|
+
mik_rt->profile_enabled = true;
|
|
587
|
+
/* Capture the QuickJS heap baseline (runtime + context + built-in
|
|
588
|
+
* prototypes). Everything profiled after this is module cost. */
|
|
589
|
+
JSMemoryUsage mu;
|
|
590
|
+
JS_ComputeMemoryUsage(mik_rt->rt, &mu);
|
|
591
|
+
mik_rt->profile_baseline = mu.malloc_size > 0 ? (size_t)mu.malloc_size : 0;
|
|
592
|
+
/* Reserve up-front so push_back during module loads never reallocates —
|
|
593
|
+
* a reallocation mid-load would be invisible to JS_ComputeMemoryUsage
|
|
594
|
+
* but could still blip memory the app doesn't see, which we want to
|
|
595
|
+
* avoid even in the "profile buffer" itself. 256 slots covers every
|
|
596
|
+
* realistic app import graph. */
|
|
597
|
+
mik_rt->profile_entries.reserve(256);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
size_t MIK_GetProfileEntryCount(MIKRuntime* mik_rt) {
|
|
601
|
+
CHECK_NOT_NULL(mik_rt);
|
|
602
|
+
return mik_rt->profile_entries.size();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const MIKProfileEntry* MIK_GetProfileEntries(MIKRuntime* mik_rt) {
|
|
606
|
+
CHECK_NOT_NULL(mik_rt);
|
|
607
|
+
return mik_rt->profile_entries.data();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
size_t MIK_GetProfileBaseline(MIKRuntime* mik_rt) {
|
|
611
|
+
CHECK_NOT_NULL(mik_rt);
|
|
612
|
+
return mik_rt->profile_baseline;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
void MIK_SetOOMHandler(MIKRuntime* mik_rt, MIKOOMHandlerFn fn, void* opaque) {
|
|
616
|
+
CHECK_NOT_NULL(mik_rt);
|
|
617
|
+
mik_rt->oom_handler_fn = fn;
|
|
618
|
+
mik_rt->oom_handler_opaque = opaque;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
bool MIK_ConsumeOOMFlag(MIKRuntime* mik_rt) {
|
|
622
|
+
CHECK_NOT_NULL(mik_rt);
|
|
623
|
+
bool was_set = mik_rt->oom_flagged;
|
|
624
|
+
mik_rt->oom_flagged = false;
|
|
625
|
+
return was_set;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
void MIK_ReportOOM(MIKRuntime* mik_rt, const MIKOOMEvent* event) {
|
|
629
|
+
CHECK_NOT_NULL(mik_rt);
|
|
630
|
+
CHECK_NOT_NULL(event);
|
|
631
|
+
mik_rt->oom_flagged = true;
|
|
632
|
+
if (mik_rt->oom_handler_fn) {
|
|
633
|
+
mik_rt->oom_handler_fn(event, mik_rt->oom_handler_opaque);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
bool MIK_IsStopRequested(MIKRuntime* mik_rt) {
|
|
638
|
+
return mik_rt && mik_rt->stop_requested;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
void MIK_Stop(MIKRuntime* mik_rt) {
|
|
642
|
+
CHECK_NOT_NULL(mik_rt);
|
|
643
|
+
if (MIK_IsReplActive()) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
647
|
+
if (mik_rt->config.restart_on_uncaught_exception) {
|
|
648
|
+
printf("Restarting in %d ms...\n", mik_rt->config.restart_delay_ms);
|
|
649
|
+
usleep(mik_rt->config.restart_delay_ms * 1000);
|
|
650
|
+
platform->restart();
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
int mik__load_file(JSContext* ctx, DynBuf* dbuf, const char* filename) {
|
|
655
|
+
int fd = open(filename, O_RDONLY);
|
|
656
|
+
if (fd < 0) {
|
|
657
|
+
return -1;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
uint8_t buf[512];
|
|
661
|
+
ssize_t n;
|
|
662
|
+
while ((n = read(fd, buf, sizeof(buf))) > 0) {
|
|
663
|
+
if (dbuf_put(dbuf, buf, n)) {
|
|
664
|
+
close(fd);
|
|
665
|
+
return -1;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (n < 0) {
|
|
670
|
+
close(fd);
|
|
671
|
+
return -1;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
close(fd);
|
|
675
|
+
return 0;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
void mik__resolve_fs_path(JSContext* ctx, const char* module_name, char* out, size_t out_size) {
|
|
679
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
680
|
+
if (mik_rt && mik_rt->fs_base_path) {
|
|
681
|
+
snprintf(out, out_size, "%s%s", mik_rt->fs_base_path, module_name);
|
|
682
|
+
} else {
|
|
683
|
+
snprintf(out, out_size, "%s", module_name);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/* Minimum QuickJS heap budget that should be available after the module
|
|
688
|
+
* graph is fully compiled, before entering the evaluation phase. Top-level
|
|
689
|
+
* code in each module typically allocates closures, atoms, and any
|
|
690
|
+
* eagerly-constructed data structures, so the eval phase needs its own
|
|
691
|
+
* headroom separate from what the compiled bytecode already consumed.
|
|
692
|
+
*
|
|
693
|
+
* Falling below this threshold is the #1 cause of `Error: null` failures —
|
|
694
|
+
* QuickJS OOMs partway through eval, and the recursive-OOM guard in
|
|
695
|
+
* JS_ThrowOutOfMemory leaves the exception slot holding whatever garbage
|
|
696
|
+
* was there. Emitting a proactive warning here gives the user a fighting
|
|
697
|
+
* chance to diagnose it before they see the cryptic symptom.
|
|
698
|
+
*
|
|
699
|
+
* 16 KB is a deliberate middle-ground: large enough that normal apps won't
|
|
700
|
+
* trigger it, small enough that any realistic app running this close to
|
|
701
|
+
* the ceiling IS actually at risk. */
|
|
702
|
+
#define MIK__PRE_EVAL_HEADROOM_WARN 16384
|
|
703
|
+
|
|
704
|
+
static void mik__check_pre_eval_headroom(JSContext* ctx, const char* filename) {
|
|
705
|
+
JSMemoryUsage mem;
|
|
706
|
+
JS_ComputeMemoryUsage(JS_GetRuntime(ctx), &mem);
|
|
707
|
+
if (mem.malloc_limit <= 0) return; /* No limit configured — nothing to warn about. */
|
|
708
|
+
|
|
709
|
+
long headroom = (long)mem.malloc_limit - (long)mem.malloc_size;
|
|
710
|
+
if (headroom >= MIK__PRE_EVAL_HEADROOM_WARN) return;
|
|
711
|
+
|
|
712
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
713
|
+
platform->log(MIK_LOG_WARN, "mikrojs",
|
|
714
|
+
"Low QuickJS heap headroom before evaluating '%s': %ld bytes left "
|
|
715
|
+
"(%ld / %ld used). Module evaluation may fail with OOM. Lower "
|
|
716
|
+
"`memReserved` in mikro.config.ts or split the module graph with "
|
|
717
|
+
"dynamic imports.",
|
|
718
|
+
filename, headroom, (long)mem.malloc_size, (long)mem.malloc_limit);
|
|
719
|
+
|
|
720
|
+
/* Signal the host so it can take application-level action (flash LED,
|
|
721
|
+
* log metric, trigger reset, forward to telemetry). Operates on C state
|
|
722
|
+
* only, safe to call even though the JS heap is nearly exhausted. */
|
|
723
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
724
|
+
if (mik_rt != nullptr) {
|
|
725
|
+
MIKOOMEvent event = {};
|
|
726
|
+
event.phase = MIK_OOM_PRE_EVAL_WARN;
|
|
727
|
+
event.filename = filename;
|
|
728
|
+
event.malloc_size = (size_t)mem.malloc_size;
|
|
729
|
+
event.malloc_limit = (size_t)mem.malloc_limit;
|
|
730
|
+
event.headroom = headroom > 0 ? (size_t)headroom : 0;
|
|
731
|
+
MIK_ReportOOM(mik_rt, &event);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
JSValue MIK_EvalModuleContent(JSContext* ctx, const char* filename, const char* content,
|
|
736
|
+
size_t len) {
|
|
737
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
738
|
+
char* pp_source = NULL;
|
|
739
|
+
size_t pp_len = 0;
|
|
740
|
+
if (mik_rt && mik_rt->preprocess_fn) {
|
|
741
|
+
pp_source =
|
|
742
|
+
mik_rt->preprocess_fn(filename, content, len, &pp_len, mik_rt->preprocess_opaque);
|
|
743
|
+
}
|
|
744
|
+
const char* eval_src = pp_source ? pp_source : content;
|
|
745
|
+
size_t eval_len = pp_source ? pp_len : len;
|
|
746
|
+
|
|
747
|
+
/* Compile then run to be able to set import.meta */
|
|
748
|
+
JSValue ret =
|
|
749
|
+
JS_Eval(ctx, eval_src, eval_len, filename, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
750
|
+
free(pp_source);
|
|
751
|
+
if (!JS_IsException(ret)) {
|
|
752
|
+
js_module_set_import_meta(ctx, ret, true, true);
|
|
753
|
+
mik__check_pre_eval_headroom(ctx, filename);
|
|
754
|
+
ret = JS_EvalFunction(ctx, ret);
|
|
755
|
+
}
|
|
756
|
+
return ret;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
JSValue MIK_EvalScriptContent(JSContext* ctx, const char* content, size_t len) {
|
|
760
|
+
return JS_Eval(ctx, content, len, "<eval>", JS_EVAL_TYPE_GLOBAL);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
JSValue MIK_EvalScript(JSContext* ctx, const char* filename) {
|
|
764
|
+
DynBuf dbuf;
|
|
765
|
+
size_t dbuf_size;
|
|
766
|
+
int r;
|
|
767
|
+
JSValue ret;
|
|
768
|
+
|
|
769
|
+
mik_dbuf_init(ctx, &dbuf);
|
|
770
|
+
r = mik__load_file(ctx, &dbuf, filename);
|
|
771
|
+
if (r != 0) {
|
|
772
|
+
dbuf_free(&dbuf);
|
|
773
|
+
JS_ThrowReferenceError(ctx, "could not load '%s': %s", filename, strerror(errno));
|
|
774
|
+
return JS_EXCEPTION;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
dbuf_size = dbuf.size;
|
|
778
|
+
|
|
779
|
+
/* Add null termination, required by JS_Eval. */
|
|
780
|
+
dbuf_putc(&dbuf, '\0');
|
|
781
|
+
|
|
782
|
+
ret = JS_Eval(ctx, (char*)dbuf.buf, dbuf_size - 1, filename, JS_EVAL_TYPE_GLOBAL);
|
|
783
|
+
|
|
784
|
+
dbuf_free(&dbuf);
|
|
785
|
+
return ret;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
JSValue MIK_EvalModule(JSContext* ctx, const char* filename, bool is_main) {
|
|
789
|
+
DynBuf dbuf;
|
|
790
|
+
size_t dbuf_size;
|
|
791
|
+
int r;
|
|
792
|
+
JSValue ret;
|
|
793
|
+
|
|
794
|
+
/* Resolve logical module name to filesystem path */
|
|
795
|
+
char fs_path[PATH_MAX];
|
|
796
|
+
mik__resolve_fs_path(ctx, filename, fs_path, sizeof(fs_path));
|
|
797
|
+
|
|
798
|
+
/* Prefer pre-compiled bytecode (.bjs) over source (.js), mirroring
|
|
799
|
+
* the same fallback in mik_module_loader_inner. This lets callers
|
|
800
|
+
* always pass .js names; the profile then records consistent .js
|
|
801
|
+
* names for both the entry and its imports. */
|
|
802
|
+
bool is_bjs = js__has_suffix(filename, ".bjs");
|
|
803
|
+
if (!is_bjs && js__has_suffix(filename, ".js")) {
|
|
804
|
+
char bjs_path[PATH_MAX];
|
|
805
|
+
size_t len = strlen(fs_path);
|
|
806
|
+
memcpy(bjs_path, fs_path, len - 3);
|
|
807
|
+
strcpy(bjs_path + len - 3, ".bjs");
|
|
808
|
+
struct stat st;
|
|
809
|
+
if (stat(bjs_path, &st) == 0) {
|
|
810
|
+
memcpy(fs_path, bjs_path, strlen(bjs_path) + 1);
|
|
811
|
+
is_bjs = true;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
mik_dbuf_init(ctx, &dbuf);
|
|
816
|
+
r = mik__load_file(ctx, &dbuf, fs_path);
|
|
817
|
+
if (r != 0) {
|
|
818
|
+
dbuf_free(&dbuf);
|
|
819
|
+
JS_ThrowReferenceError(ctx, "could not load '%s': %s", filename, strerror(errno));
|
|
820
|
+
|
|
821
|
+
return JS_EXCEPTION;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
dbuf_size = dbuf.size;
|
|
825
|
+
|
|
826
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
827
|
+
bool profile = mik_rt != nullptr && mik_rt->profile_enabled;
|
|
828
|
+
int64_t malloc_before = 0;
|
|
829
|
+
size_t entries_before = 0;
|
|
830
|
+
if (profile) {
|
|
831
|
+
JSMemoryUsage before;
|
|
832
|
+
JS_ComputeMemoryUsage(JS_GetRuntime(ctx), &before);
|
|
833
|
+
malloc_before = before.malloc_size;
|
|
834
|
+
entries_before = mik_rt->profile_entries.size();
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/* Pre-compiled bytecode (.bjs) — deserialize directly */
|
|
838
|
+
if (is_bjs) {
|
|
839
|
+
ret = JS_ReadObject(ctx, dbuf.buf, dbuf_size, JS_READ_OBJ_BYTECODE);
|
|
840
|
+
dbuf_free(&dbuf);
|
|
841
|
+
if (JS_IsException(ret)) {
|
|
842
|
+
return ret;
|
|
843
|
+
}
|
|
844
|
+
if (JS_ResolveModule(ctx, ret) < 0) {
|
|
845
|
+
JS_FreeValue(ctx, ret);
|
|
846
|
+
return JS_EXCEPTION;
|
|
847
|
+
}
|
|
848
|
+
js_module_set_import_meta(ctx, ret, true, is_main);
|
|
849
|
+
|
|
850
|
+
/* Record the entry's load cost before JS_EvalFunction. The entry
|
|
851
|
+
* bypasses the module loader, so it needs its own profile entry.
|
|
852
|
+
* JS_ResolveModule above triggered the loader for all imports,
|
|
853
|
+
* which are profiled as children and subtracted. */
|
|
854
|
+
if (profile) {
|
|
855
|
+
mik__record_entry_profile(mik_rt, JS_GetRuntime(ctx), filename, malloc_before,
|
|
856
|
+
entries_before);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
mik__check_pre_eval_headroom(ctx, filename);
|
|
860
|
+
return JS_EvalFunction(ctx, ret);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/* Source module — compile, resolve, evaluate.
|
|
864
|
+
* JS_Eval with COMPILE_ONLY + JS_ResolveModule triggers the module
|
|
865
|
+
* loader for imports (profiled as children). Record the entry's own
|
|
866
|
+
* load cost, then call JS_EvalFunction separately. */
|
|
867
|
+
dbuf_putc(&dbuf, '\0');
|
|
868
|
+
{
|
|
869
|
+
const char* src = (char*)dbuf.buf;
|
|
870
|
+
size_t src_len = dbuf_size - 1;
|
|
871
|
+
char* pp_source = NULL;
|
|
872
|
+
size_t pp_len = 0;
|
|
873
|
+
if (mik_rt && mik_rt->preprocess_fn) {
|
|
874
|
+
pp_source = mik_rt->preprocess_fn(filename, src, src_len, &pp_len,
|
|
875
|
+
mik_rt->preprocess_opaque);
|
|
876
|
+
}
|
|
877
|
+
const char* eval_src = pp_source ? pp_source : src;
|
|
878
|
+
size_t eval_len = pp_source ? pp_len : src_len;
|
|
879
|
+
|
|
880
|
+
ret = JS_Eval(ctx, eval_src, eval_len, filename,
|
|
881
|
+
JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
882
|
+
free(pp_source);
|
|
883
|
+
dbuf_free(&dbuf);
|
|
884
|
+
|
|
885
|
+
if (JS_IsException(ret)) {
|
|
886
|
+
return ret;
|
|
887
|
+
}
|
|
888
|
+
js_module_set_import_meta(ctx, ret, true, is_main);
|
|
889
|
+
|
|
890
|
+
if (profile) {
|
|
891
|
+
mik__record_entry_profile(mik_rt, JS_GetRuntime(ctx), filename, malloc_before,
|
|
892
|
+
entries_before);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
mik__check_pre_eval_headroom(ctx, filename);
|
|
896
|
+
return JS_EvalFunction(ctx, ret);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
int MIK_RunEntry(MIKRuntime* mik_rt, const char* entry) {
|
|
901
|
+
if (!mik_rt || !entry || entry[0] == '\0') {
|
|
902
|
+
return -EINVAL;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
JSContext* ctx = MIK_GetJSContext(mik_rt);
|
|
906
|
+
|
|
907
|
+
char fs_path[PATH_MAX];
|
|
908
|
+
mik__resolve_fs_path(ctx, entry, fs_path, sizeof(fs_path));
|
|
909
|
+
|
|
910
|
+
struct stat st;
|
|
911
|
+
bool found = stat(fs_path, &st) == 0;
|
|
912
|
+
if (!found && js__has_suffix(entry, ".js")) {
|
|
913
|
+
char bjs_path[PATH_MAX];
|
|
914
|
+
size_t len = strlen(fs_path);
|
|
915
|
+
if (len >= 3 && len - 3 + sizeof(".bjs") <= sizeof(bjs_path)) {
|
|
916
|
+
memcpy(bjs_path, fs_path, len - 3);
|
|
917
|
+
memcpy(bjs_path + len - 3, ".bjs", sizeof(".bjs"));
|
|
918
|
+
found = stat(bjs_path, &st) == 0;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (!found) {
|
|
922
|
+
return -ENOENT;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
JSValue result = MIK_EvalModule(ctx, entry, true);
|
|
926
|
+
bool failed = JS_IsException(result);
|
|
927
|
+
/* Modules with top-level await return a Promise. A module body that
|
|
928
|
+
* throws synchronously rejects that promise before JS_EvalFunction
|
|
929
|
+
* returns, but the value itself is still an Object (rejected Promise),
|
|
930
|
+
* not a JS_EXCEPTION sentinel. Treat sync-rejected as an eval failure
|
|
931
|
+
* so callers (e.g. the test supervisor) don't mistake a fatal module
|
|
932
|
+
* for a successful launch and wait for events that will never come.
|
|
933
|
+
* Pending promises (real TLA awaiting something) continue as normal. */
|
|
934
|
+
if (!failed && JS_IsObject(result)) {
|
|
935
|
+
JSPromiseStateEnum state = JS_PromiseState(ctx, result);
|
|
936
|
+
if (state == JS_PROMISE_REJECTED) {
|
|
937
|
+
failed = true;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
JS_FreeValue(ctx, result);
|
|
941
|
+
return failed ? -EFAULT : 0;
|
|
942
|
+
}
|