@mikrojs/native 0.12.0 → 0.14.0-pr-229.g0d8db1b

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/src/mikrojs.cpp CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include <quickjs.h>
4
4
 
5
+ #include <atomic>
5
6
  #include <cerrno>
6
7
  #include <cstdio>
7
8
  #include <fcntl.h>
@@ -91,7 +92,7 @@ static void mik__add_exports(JSContext* ctx, JSModuleDef* m, const char* const*
91
92
  static const char* const sys_exports[] = {
92
93
  "evalScript", "memoryUsage", "jsMemoryUsage", "gc", "setTime",
93
94
  "uptime", "restart", "version", "board", "firmware",
94
- "deviceId", "activeTimers", "unloadNamespace", "isUnloadableNamespace"};
95
+ "deviceId", "resetReason", "activeTimers", "unloadNamespace", "isUnloadableNamespace"};
95
96
 
96
97
  static int mik__sys_module_init(JSContext* ctx, JSModuleDef* m) {
97
98
  JSValue ns = JS_NewObjectProto(ctx, JS_NULL);
@@ -111,21 +112,19 @@ static int mik__stdio_module_init(JSContext* ctx, JSModuleDef* m) {
111
112
  return 0;
112
113
  }
113
114
 
114
- static JSValue mik__dispatch_event(JSContext* ctx, JSValue* event) {
115
- MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
116
- CHECK_NOT_NULL(mik_rt);
117
-
118
- if (mik_rt->freeing) {
119
- return JS_UNDEFINED;
120
- }
121
-
122
- JSValue global_obj = JS_GetGlobalObject(ctx);
123
- JSValue ret = JS_Call(ctx, mik_rt->builtins.dispatch_event_func, global_obj, 1, event);
124
- JS_FreeValue(ctx, global_obj);
125
-
126
- return ret;
127
- }
128
-
115
+ /* Host promise-rejection tracker. Rather than report eagerly the instant a
116
+ * promise rejects, defer the decision to the end-of-turn flush
117
+ * (mik__flush_unhandled_rejections): a promise that rejects without a
118
+ * handler is queued, and a promise that later gets a handler is removed
119
+ * from the queue. This mirrors the HTML/WinterCG unhandledrejection
120
+ * algorithm. It matters because some promises are born rejected before a
121
+ * handler can be attached. The clearest example is a module's evaluation
122
+ * promise, which the dynamic-import loader rejects (when the module body
123
+ * throws) and only then attaches its .then to. Reporting eagerly surfaced
124
+ * that transient rejection as a spurious second "Uncaught (in promise)".
125
+ *
126
+ * This is the same add-on-reject / remove-on-handle / report-after-drain
127
+ * pattern quickjs-libc's js_std_promise_rejection_tracker uses. */
129
128
  static void mik__promise_rejection_tracker(JSContext* ctx, JSValue promise, JSValue reason,
130
129
  bool is_handled, void* opaque) {
131
130
  MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
@@ -135,68 +134,109 @@ static void mik__promise_rejection_tracker(JSContext* ctx, JSValue promise, JSVa
135
134
  return;
136
135
  }
137
136
 
138
- if (!is_handled) {
139
- /* If the runtime hasn't loaded the event infrastructure yet,
140
- * report and bail. */
141
- if (JS_IsUndefined(mik_rt->builtins.promise_event_ctor) ||
142
- JS_IsUndefined(mik_rt->builtins.dispatch_event_func)) {
143
- /* During REPL eval, sync errors are wrapped by the async eval
144
- * wrapper and appear as promise rejections don't label them
145
- * "(in promise)". Outside eval (e.g. timer callbacks), they
146
- * are genuine unhandled promise rejections. */
147
- bool in_promise = !mik__repl_is_evaluating();
148
- mik__report_uncaught(ctx, reason, in_promise);
149
- /* Notify the error handler (e.g. host bridge) directly.
150
- * Don't JS_Throw here — we're inside a QuickJS callback
151
- * during promise resolution; throwing would corrupt engine
152
- * state and cause mik__execute_jobs to dump the error a
153
- * second time. MIK_Stop gates on whether we're inside an
154
- * interactive REPL eval (skipping both the stop request and
155
- * the restart) so a typo at the prompt doesn't reboot the
156
- * device. */
157
- if (mik_rt->error_handler_fn) {
158
- mik_rt->error_handler_fn(ctx, reason, mik_rt->error_handler_opaque);
137
+ auto& pending = mik_rt->pending_rejections;
138
+ void* key = JS_VALUE_GET_PTR(promise);
139
+
140
+ if (is_handled) {
141
+ /* A handler was attached to a previously-rejected promise: cancel its
142
+ * pending report. */
143
+ for (size_t i = 0; i < pending.size(); i++) {
144
+ if (JS_VALUE_GET_PTR(pending[i].promise) == key) {
145
+ JS_FreeValue(ctx, pending[i].promise);
146
+ JS_FreeValue(ctx, pending[i].reason);
147
+ pending.erase(pending.begin() + i);
148
+ return;
159
149
  }
160
- MIK_Stop(mik_rt);
161
- return;
162
150
  }
151
+ return;
152
+ }
153
+
154
+ /* Rejected with no handler (yet): queue it. Hold references to both the
155
+ * promise and the reason so they stay alive until the end-of-turn check. */
156
+ pending.push_back({JS_DupValue(ctx, promise), JS_DupValue(ctx, reason)});
157
+ }
163
158
 
164
- JSValue event_name = JS_NewString(ctx, "unhandledrejection");
165
- JSValue args[3];
166
- args[0] = event_name;
167
- args[1] = promise;
168
- args[2] = reason;
169
-
170
- JSValue event =
171
- JS_CallConstructor(ctx, mik_rt->builtins.promise_event_ctor, countof(args), args);
172
- if (JS_IsException(event)) {
173
- JS_FreeValue(ctx, event_name);
174
- mik_dump_error(ctx);
159
+ /* Drop a promise from the pending-rejection queue without reporting it. The
160
+ * REPL eval pump calls this for the result promise it polls and reports via
161
+ * the throw path, so the end-of-turn flush doesn't report it a second time. */
162
+ void mik__forget_rejection(JSContext* ctx, JSValue promise) {
163
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
164
+ if (!mik_rt) {
165
+ return;
166
+ }
167
+ auto& pending = mik_rt->pending_rejections;
168
+ void* key = JS_VALUE_GET_PTR(promise);
169
+ for (size_t i = 0; i < pending.size(); i++) {
170
+ if (JS_VALUE_GET_PTR(pending[i].promise) == key) {
171
+ JS_FreeValue(ctx, pending[i].promise);
172
+ JS_FreeValue(ctx, pending[i].reason);
173
+ pending.erase(pending.begin() + i);
175
174
  return;
176
175
  }
177
- JSValue ret = mik__dispatch_event(ctx, &event);
176
+ }
177
+ }
178
178
 
179
- JS_FreeValue(ctx, event);
180
- JS_FreeValue(ctx, event_name);
179
+ /* End-of-turn unhandled-rejection check. Any promise still queued after a
180
+ * microtask drain rejected and never got a handler: report it, notify the
181
+ * host error handler, and halt. */
182
+ void mik__flush_unhandled_rejections(JSContext* ctx) {
183
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
184
+ if (!mik_rt || mik_rt->freeing || mik_rt->pending_rejections.empty()) {
185
+ return;
186
+ }
187
+ /* During interactive REPL eval, the eval pump polls its result promise
188
+ * and reports rejections via the throw path; leave the queue alone until
189
+ * the eval finishes so we don't fight that model. */
190
+ if (mik__repl_is_evaluating()) {
191
+ return;
192
+ }
181
193
 
182
- if (JS_IsException(ret)) {
183
- mik_dump_error(ctx);
184
- goto fail;
185
- } else {
186
- if (JS_ToBool(ctx, ret)) {
187
- // The event wasn't cancelled, maybe abort.
188
- fail:;
189
- MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
190
- CHECK_NOT_NULL(mik_rt);
191
- mik__report_uncaught(ctx, reason, true);
192
- if (mik_rt->error_handler_fn) {
193
- mik_rt->error_handler_fn(ctx, reason, mik_rt->error_handler_opaque);
194
- }
195
- MIK_Stop(mik_rt);
194
+ /* Move the queue aside before reporting. Reporting doesn't enqueue JS
195
+ * jobs, but this keeps the runtime's vector in a clean state regardless. */
196
+ std::vector<MIKRejectedPromise> rejected;
197
+ rejected.swap(mik_rt->pending_rejections);
198
+
199
+ /* One failed dynamic import leaves two never-handled promises with the
200
+ * SAME reason: the module's internal sync-evaluation promise (QuickJS runs
201
+ * a module body as an async function and reads its rejected result by
202
+ * value, never attaching a handler) and the promise the error propagates
203
+ * to up the await chain. mik__report_uncaught dedups by reason-object
204
+ * identity, so the second is a no-op; only act on a fresh report so the
205
+ * host bridge and the halt don't fire twice. (quickjs-libc reports every
206
+ * entry; we collapse same-error duplicates.) */
207
+ for (const MIKRejectedPromise& rp : rejected) {
208
+ if (mik__report_uncaught(ctx, rp.reason, true)) {
209
+ /* Notify the error handler (e.g. host bridge) directly. Don't
210
+ * JS_Throw here: we're between jobs, and throwing would be picked
211
+ * up as a spurious pending exception. */
212
+ if (mik_rt->error_handler_fn) {
213
+ mik_rt->error_handler_fn(ctx, rp.reason, mik_rt->error_handler_opaque);
196
214
  }
215
+ MIK_Stop(mik_rt);
197
216
  }
217
+ JS_FreeValue(ctx, rp.promise);
218
+ JS_FreeValue(ctx, rp.reason);
219
+ }
220
+ }
198
221
 
199
- JS_FreeValue(ctx, ret);
222
+ /* QuickJS only runs the cycle collector automatically once malloc_size
223
+ * crosses the runtime's GC threshold (256 KB by default), and js_malloc_rt
224
+ * fails against the memory limit without attempting a GC first. On devices
225
+ * where mem_limit is below the default threshold the collector would never
226
+ * fire before allocations start failing, so cyclic garbage (promise chains,
227
+ * closures) accumulates straight into OOM with collectable memory still
228
+ * alive. Cap the threshold below the limit so collection happens first.
229
+ * QuickJS raises the threshold to 1.5x the live size after every GC pass,
230
+ * which can push it back above the limit, so MIK_Loop re-applies the cap
231
+ * each turn. */
232
+ static void mik__clamp_gc_threshold(MIKRuntime* mik_rt) {
233
+ if (mik_rt->options.mem_limit <= 0) {
234
+ return;
235
+ }
236
+ size_t limit = (size_t)mik_rt->options.mem_limit;
237
+ size_t cap = limit - limit / 8;
238
+ if (JS_GetGCThreshold(mik_rt->rt) > cap) {
239
+ JS_SetGCThreshold(mik_rt->rt, cap);
200
240
  }
201
241
  }
202
242
 
@@ -289,12 +329,25 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
289
329
  * exhaust the heap. Overridden via MIK_SetFSReadMax. */
290
330
  mik_rt->fs_read_max = 65536;
291
331
 
332
+ /* Reserve the unhandled-rejection queue up-front so the rejection tracker
333
+ * never has to grow a std::vector on the rejection path. That path fires
334
+ * for a QuickJS null-OOM rejection, and a reallocation there could throw
335
+ * std::bad_alloc — which, with CONFIG_COMPILER_CXX_EXCEPTIONS=n on ESP32,
336
+ * aborts and masks the very OOM we're trying to report. Four slots covers
337
+ * any realistic per-turn rejection count; the runtime halts on the first
338
+ * one anyway. */
339
+ mik_rt->pending_rejections.reserve(4);
340
+
292
341
  /* Check for duplicate native module registrations (global registry) */
293
342
  mik__check_module_collisions();
294
343
 
295
344
  /* Set memory limit */
296
345
  JS_SetMemoryLimit(rt, options->mem_limit);
297
346
 
347
+ /* Make sure the cycle collector fires before the memory limit does
348
+ * (see mik__clamp_gc_threshold). */
349
+ mik__clamp_gc_threshold(mik_rt);
350
+
298
351
  /* Set stack size */
299
352
  JS_SetMaxStackSize(rt, options->stack_size);
300
353
  /* loader for ES modules */
@@ -337,11 +390,6 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
337
390
  mik__text_encoding_init(ctx, global_obj);
338
391
  mik__abort_init(ctx, global_obj);
339
392
 
340
- /* Load some builtin references for easy access */
341
- mik_rt->builtins.dispatch_event_func = JS_GetPropertyStr(ctx, global_obj, "dispatchEvent");
342
- mik_rt->builtins.promise_event_ctor =
343
- JS_GetPropertyStr(mik_rt->ctx, global_obj, "PromiseRejectionEvent");
344
-
345
393
  /* Timers */
346
394
  mik_rt->timers = MIK_NewTimerRegistry();
347
395
  mik__timers_init(ctx, global_obj);
@@ -369,6 +417,13 @@ void MIK_FreeRuntime(MIKRuntime* mik_rt) {
369
417
  mik_rt->stdin_state.on_data = JS_UNDEFINED;
370
418
  }
371
419
 
420
+ /* Release any still-pending unhandled-rejection entries. */
421
+ for (const MIKRejectedPromise& rp : mik_rt->pending_rejections) {
422
+ JS_FreeValue(mik_rt->ctx, rp.promise);
423
+ JS_FreeValue(mik_rt->ctx, rp.reason);
424
+ }
425
+ mik_rt->pending_rejections.clear();
426
+
372
427
  /* Destroy registered loop consumers */
373
428
  for (const auto& consumer : mik_rt->loop_consumers) {
374
429
  if (consumer.destroy_fn) {
@@ -384,10 +439,6 @@ void MIK_FreeRuntime(MIKRuntime* mik_rt) {
384
439
  /* Destroy the JS engine. */
385
440
  JS_FreeValue(mik_rt->ctx, mik_rt->env_obj);
386
441
  mik_rt->env_obj = JS_UNDEFINED;
387
- JS_FreeValue(mik_rt->ctx, mik_rt->builtins.dispatch_event_func);
388
- mik_rt->builtins.dispatch_event_func = JS_UNDEFINED;
389
- JS_FreeValue(mik_rt->ctx, mik_rt->builtins.promise_event_ctor);
390
- mik_rt->builtins.promise_event_ctor = JS_UNDEFINED;
391
442
  JS_FreeValue(mik_rt->ctx, mik_rt->result_ok_void_singleton);
392
443
  mik_rt->result_ok_void_singleton = JS_UNDEFINED;
393
444
  JS_FreeValue(mik_rt->ctx, mik_rt->result_proto);
@@ -510,9 +561,43 @@ static void mik__check_module_collisions(void) {
510
561
  }
511
562
  }
512
563
 
564
+ /* A chain of already-settled promises drains as one uninterrupted storm of
565
+ * jobs, and the test supervisor strings whole files of such chains together
566
+ * with runtime recycles in between. On ESP32 the main task runs above the
567
+ * idle task, so a multi-second stretch like that starves idle: the task
568
+ * watchdog (5 s, watching IDLE0) prints a register dump, and FreeRTOS
569
+ * housekeeping (freeing TCBs of exited tasks, e.g. per-request HTTP tasks)
570
+ * stalls. Force one platform yield per second of continuous job execution
571
+ * so idle always gets a slice. The timestamp is global on purpose: the
572
+ * starvation accumulates across MIK_Loop calls and across runtime recycles.
573
+ * Costs at most one tick (~10 ms) per second of busy work. */
574
+ #define MIK__YIELD_INTERVAL_US (1000 * 1000)
575
+
576
+ /* Atomic because the Node addon can pump two runtimes' loops from
577
+ * different worker threads; relaxed ordering is enough — a missed or
578
+ * extra yield is harmless, a torn 64-bit write is not. */
579
+ static std::atomic<int64_t> mik__last_yield_us{0};
580
+
581
+ static void mik__maybe_yield(void) {
582
+ const MIKPlatform* platform = MIK_GetPlatform();
583
+ int64_t now = platform->get_boot_us();
584
+ int64_t last = mik__last_yield_us.load(std::memory_order_relaxed);
585
+ /* now < last happens when the boot clock resets (deep sleep) or a test
586
+ * swaps in a platform with a different clock; restart the interval. */
587
+ if (last == 0 || now < last) {
588
+ mik__last_yield_us.store(now, std::memory_order_relaxed);
589
+ return;
590
+ }
591
+ if (now - last >= MIK__YIELD_INTERVAL_US) {
592
+ platform->yield();
593
+ mik__last_yield_us.store(platform->get_boot_us(), std::memory_order_relaxed);
594
+ }
595
+ }
596
+
513
597
  void mik__execute_jobs(JSContext* ctx) {
514
598
  JSContext* ctx1;
515
599
  int err;
600
+ bool ran_any = false;
516
601
 
517
602
  /* execute the pending jobs */
518
603
  for (;;) {
@@ -529,7 +614,31 @@ void mik__execute_jobs(JSContext* ctx) {
529
614
  }
530
615
  break;
531
616
  }
617
+ ran_any = true;
618
+ /* A GC pass inside this job raises the threshold to 1.5x live size,
619
+ * which lands above mem_limit whenever live >= ~2/3 of the limit —
620
+ * silently disabling cycle GC for the rest of the storm. Re-clamp
621
+ * between jobs so collection keeps firing; the check is a field
622
+ * read + compare when no GC ran. */
623
+ MIKRuntime* job_rt = MIK_GetRuntime(ctx1);
624
+ if (job_rt) {
625
+ mik__clamp_gc_threshold(job_rt);
626
+ }
627
+ mik__maybe_yield();
532
628
  }
629
+
630
+ /* The yield interval measures continuous BUSY work. An empty drain
631
+ * means the loop is idling (the idle task is getting time between
632
+ * turns), so re-arm the interval — otherwise the first job after a
633
+ * quiet period pays a spurious ~10 ms yield for wall-clock time spent
634
+ * doing nothing. */
635
+ if (!ran_any) {
636
+ mik__last_yield_us.store(MIK_GetPlatform()->get_boot_us(), std::memory_order_relaxed);
637
+ }
638
+
639
+ /* Microtask checkpoint: every reject/handle transition for this turn has
640
+ * now run, so whatever is still queued is a genuine unhandled rejection. */
641
+ mik__flush_unhandled_rejections(ctx);
533
642
  }
534
643
 
535
644
  /* main loop which calls the user JS callbacks */
@@ -576,7 +685,18 @@ int MIK_Loop(MIKRuntime* mik_rt) {
576
685
  }
577
686
 
578
687
  mik__execute_jobs(mik_rt->ctx);
579
- return 0;
688
+
689
+ /* Backstop for GC passes outside the job drain (timer callbacks, loop
690
+ * consumers, top-level eval): a pass there may have raised the
691
+ * threshold above the memory limit; pull it back so the next turn's
692
+ * collection still fires before allocations fail. Within the job
693
+ * drain, mik__execute_jobs re-clamps after every job. */
694
+ mik__clamp_gc_threshold(mik_rt);
695
+
696
+ /* The end-of-turn unhandled-rejection flush inside mik__execute_jobs may
697
+ * have requested a stop; surface it this iteration rather than making the
698
+ * caller loop once more to notice. */
699
+ return mik_rt->stop_requested ? 1 : 0;
580
700
  }
581
701
 
582
702
  void MIK_SetConfig(MIKRuntime* mik_rt, const MIKConfig* config) {
@@ -961,6 +1081,13 @@ JSValue MIK_EvalModule(JSContext* ctx, const char* filename, bool is_main) {
961
1081
  }
962
1082
 
963
1083
  int MIK_RunEntry(MIKRuntime* mik_rt, const char* entry) {
1084
+ return MIK_RunEntryErr(mik_rt, entry, NULL, 0);
1085
+ }
1086
+
1087
+ int MIK_RunEntryErr(MIKRuntime* mik_rt, const char* entry, char* err_buf, size_t err_buf_size) {
1088
+ if (err_buf && err_buf_size > 0) {
1089
+ err_buf[0] = '\0';
1090
+ }
964
1091
  if (!mik_rt || !entry || entry[0] == '\0') {
965
1092
  return -EINVAL;
966
1093
  }
@@ -987,6 +1114,7 @@ int MIK_RunEntry(MIKRuntime* mik_rt, const char* entry) {
987
1114
 
988
1115
  JSValue result = MIK_EvalModule(ctx, entry, true);
989
1116
  bool failed = JS_IsException(result);
1117
+ bool rejected = false;
990
1118
  /* Modules with top-level await return a Promise. A module body that
991
1119
  * throws synchronously rejects that promise before JS_EvalFunction
992
1120
  * returns, but the value itself is still an Object (rejected Promise),
@@ -998,7 +1126,25 @@ int MIK_RunEntry(MIKRuntime* mik_rt, const char* entry) {
998
1126
  JSPromiseStateEnum state = JS_PromiseState(ctx, result);
999
1127
  if (state == JS_PROMISE_REJECTED) {
1000
1128
  failed = true;
1129
+ rejected = true;
1130
+ }
1131
+ }
1132
+ if (failed && err_buf && err_buf_size > 0) {
1133
+ /* Capture the failure's string form for the caller. Without an
1134
+ * err_buf the pending exception is intentionally left on ctx,
1135
+ * matching MIK_RunEntry's historical behavior. */
1136
+ JSValue exc = rejected ? JS_PromiseResult(ctx, result) : JS_GetException(ctx);
1137
+ const char* msg = JS_ToCString(ctx, exc);
1138
+ if (msg) {
1139
+ snprintf(err_buf, err_buf_size, "%s", msg);
1140
+ JS_FreeCString(ctx, msg);
1141
+ } else {
1142
+ /* Stringifying the exception itself threw (e.g. OOM) — drop
1143
+ * the secondary exception so it can't leak into later evals. */
1144
+ JSValue stray = JS_GetException(ctx);
1145
+ JS_FreeValue(ctx, stray);
1001
1146
  }
1147
+ JS_FreeValue(ctx, exc);
1002
1148
  }
1003
1149
  JS_FreeValue(ctx, result);
1004
1150
  return failed ? -EFAULT : 0;
package/src/modules.cpp CHANGED
@@ -245,11 +245,11 @@ static JSModuleDef* mik_module_loader_inner(JSContext* ctx, const char* module_n
245
245
  /* mik__load_builtin returns NULL either because the module isn't in
246
246
  * the table, or because deserialization failed (e.g. stack overflow
247
247
  * from deeply nested .bjs loading). If deserialization failed, the
248
- * real exception is already on ctx — don't replace it. */
249
- JSValue pending = JS_GetException(ctx);
250
- if (!JS_IsNull(pending)) {
251
- JS_Throw(ctx, pending);
252
- } else {
248
+ * real exception is already on ctx — don't replace it. Probe with
249
+ * JS_HasException, NOT JS_GetException + JS_IsNull: quickjs-ng
250
+ * returns JS_UNINITIALIZED from an empty exception slot, and
251
+ * re-throwing that surfaced as a bare "[uninitialized]" error. */
252
+ if (!JS_HasException(ctx)) {
253
253
  JS_ThrowReferenceError(ctx, "Builtin module '%s' is not available in this build",
254
254
  module_name);
255
255
  }
@@ -285,13 +285,8 @@ JSModuleDef* mik_module_loader(JSContext* ctx, const char* module_name, void* op
285
285
  JSModuleDef* result = mik_module_loader_inner(ctx, module_name, opaque);
286
286
  if (debug) {
287
287
  if (result == nullptr) {
288
- JSValue exc = JS_GetException(ctx);
289
- bool has_exc = !JS_IsNull(exc) && !JS_IsUndefined(exc);
290
288
  fprintf(stderr, "[mik-modules] FAIL: %s (exception=%s)\n", module_name,
291
- has_exc ? "set" : "NULL");
292
- /* Re-throw so the caller still sees the exception. */
293
- if (has_exc) JS_Throw(ctx, exc);
294
- else JS_FreeValue(ctx, exc);
289
+ JS_HasException(ctx) ? "set" : "NULL");
295
290
  } else {
296
291
  fprintf(stderr, "[mik-modules] ok: %s\n", module_name);
297
292
  }
@@ -319,12 +314,8 @@ JSModuleDef* mik_module_loader(JSContext* ctx, const char* module_name, void* op
319
314
  JSModuleDef* m = mik_module_loader_inner(ctx, module_name, opaque);
320
315
  if (debug) {
321
316
  if (m == nullptr) {
322
- JSValue exc = JS_GetException(ctx);
323
- bool has_exc = !JS_IsNull(exc) && !JS_IsUndefined(exc);
324
317
  fprintf(stderr, "[mik-modules] FAIL: %s (exception=%s)\n", module_name,
325
- has_exc ? "set" : "NULL");
326
- if (has_exc) JS_Throw(ctx, exc);
327
- else JS_FreeValue(ctx, exc);
318
+ JS_HasException(ctx) ? "set" : "NULL");
328
319
  } else {
329
320
  fprintf(stderr, "[mik-modules] ok: %s\n", module_name);
330
321
  }
@@ -116,6 +116,10 @@ static const char* posix_get_device_id(void) {
116
116
  return id;
117
117
  }
118
118
 
119
+ static const char* posix_get_reset_reason(void) {
120
+ return "unknown"; /* Desktop processes have no chip reset concept */
121
+ }
122
+
119
123
  static void posix_log(int level, const char* tag, const char* fmt, ...) {
120
124
  if (level < MIK_LOG_ERROR || level > MIK_LOG_VERBOSE) return;
121
125
  fprintf(stderr, "[%s] %s: ", mik_log_level_name(level), tag);
@@ -147,6 +151,7 @@ static const MIKPlatform posix_platform = {
147
151
  .stderr_write = posix_stderr_write,
148
152
  .stdin_read = posix_stdin_read,
149
153
  .get_device_id = posix_get_device_id,
154
+ .get_reset_reason = posix_get_reset_reason,
150
155
  };
151
156
 
152
157
  static const MIKPlatform* current_platform = &posix_platform;
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) Bjørge Næss
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.