@mikrojs/native 0.21.1-next.20260927212749 → 0.22.0
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 +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1 -2
- package/dist/types.d.ts.map +1 -1
- package/include/mikrojs/mem.h +0 -1
- package/include/mikrojs/mikrojs.h +6 -6
- package/include/mikrojs/platform.h +13 -0
- package/include/mikrojs/private.h +60 -19
- package/include/mikrojs/utils.h +13 -8
- package/package.json +4 -4
- 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/modules.json +1 -1
- package/runtime/sys/types.ts +5 -2
- package/src/mem.cpp +2 -6
- package/src/mik_http_client.cpp +49 -32
- package/src/mik_inspect.cpp +101 -86
- package/src/mik_observable.cpp +26 -6
- package/src/mik_ota_client.cpp +3 -0
- package/src/mik_ota_js_hooks.cpp +3 -0
- package/src/mik_repl.cpp +40 -25
- package/src/mik_result.cpp +51 -43
- package/src/mik_sys.cpp +32 -1
- package/src/mik_test.cpp +1334 -0
- package/src/mik_text_encoding.cpp +77 -87
- package/src/mik_udp.cpp +11 -1
- package/src/mikrojs.cpp +55 -10
- package/src/modules.cpp +1 -0
- package/src/platform_posix.cpp +36 -0
- package/src/timers.cpp +3 -3
- package/src/utils.cpp +14 -9
- package/runtime/test/test.ts +0 -686
- package/src/eval_bytecode.cpp +0 -43
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#include <string.h>
|
|
3
3
|
|
|
4
4
|
#include "mikrojs/mikrojs.h"
|
|
5
|
+
#include "mikrojs/private.h"
|
|
5
6
|
#include "mikrojs/utils.h"
|
|
6
7
|
|
|
7
8
|
/* ---- TextEncoder ---- */
|
|
@@ -23,11 +24,12 @@ static JSValue mik__text_encoder_constructor(JSContext* ctx, JSValue new_target,
|
|
|
23
24
|
|
|
24
25
|
static JSValue mik__text_encoder_encode(JSContext* ctx, JSValue this_val, int argc,
|
|
25
26
|
JSValue* argv) {
|
|
26
|
-
|
|
27
|
+
size_t len = 0;
|
|
28
|
+
const char* str = JS_ToCStringLen(ctx, &len, argv[0]);
|
|
27
29
|
if (!str) {
|
|
28
30
|
return JS_EXCEPTION;
|
|
29
31
|
}
|
|
30
|
-
JSValue ret = JS_NewUint8ArrayCopy(ctx, (const uint8_t*)str,
|
|
32
|
+
JSValue ret = JS_NewUint8ArrayCopy(ctx, (const uint8_t*)str, len);
|
|
31
33
|
JS_FreeCString(ctx, str);
|
|
32
34
|
return ret;
|
|
33
35
|
}
|
|
@@ -135,11 +137,38 @@ static bool utf8_seq_valid(const uint8_t* p, int len) {
|
|
|
135
137
|
return false;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
/*
|
|
139
|
-
static void
|
|
140
|
-
out
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
/* Copy `n` bytes to the output when there is one; always count them. */
|
|
141
|
+
static void utf8_put(uint8_t* out, size_t* out_len, const uint8_t* src, size_t n) {
|
|
142
|
+
if (out) memcpy(out + *out_len, src, n);
|
|
143
|
+
*out_len += n;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
size_t mik__utf8_replace(const uint8_t* in, size_t len, bool hold_tail, uint8_t* out,
|
|
147
|
+
size_t* tail) {
|
|
148
|
+
static const uint8_t replacement[3] = {0xEF, 0xBF, 0xBD};
|
|
149
|
+
size_t out_len = 0;
|
|
150
|
+
size_t i = 0;
|
|
151
|
+
*tail = 0;
|
|
152
|
+
while (i < len) {
|
|
153
|
+
int seq = utf8_seq_len(in[i]);
|
|
154
|
+
if (seq > 0 && i + seq > len) {
|
|
155
|
+
/* Incomplete trailing sequence: hold it, or one U+FFFD per byte. */
|
|
156
|
+
if (hold_tail) {
|
|
157
|
+
*tail = len - i;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
for (; i < len; i++) utf8_put(out, &out_len, replacement, 3);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (seq == 0 || !utf8_seq_valid(in + i, seq)) {
|
|
164
|
+
utf8_put(out, &out_len, replacement, 3);
|
|
165
|
+
i++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
utf8_put(out, &out_len, in + i, seq);
|
|
169
|
+
i += seq;
|
|
170
|
+
}
|
|
171
|
+
return out_len;
|
|
143
172
|
}
|
|
144
173
|
|
|
145
174
|
static JSValue mik__text_decoder_decode(JSContext* ctx, JSValue this_val, int argc,
|
|
@@ -185,53 +214,28 @@ static JSValue mik__text_decoder_decode(JSContext* ctx, JSValue this_val, int ar
|
|
|
185
214
|
memcpy(heap_input + state->pending_len, data, len);
|
|
186
215
|
input = heap_input;
|
|
187
216
|
}
|
|
188
|
-
state->pending_len = 0;
|
|
189
|
-
|
|
190
|
-
/* Worst case output: every byte becomes U+FFFD (3 bytes). */
|
|
191
|
-
size_t out_cap = total * 3;
|
|
192
|
-
if (out_cap == 0) out_cap = 1;
|
|
193
|
-
uint8_t* out = static_cast<uint8_t*>(js_malloc(ctx, out_cap));
|
|
194
|
-
if (!out) {
|
|
195
|
-
if (heap_input) js_free(ctx, heap_input);
|
|
196
|
-
return JS_EXCEPTION;
|
|
197
|
-
}
|
|
198
|
-
size_t out_len = 0;
|
|
199
217
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
for (size_t k = 0; k < held; k++) state->pending[k] = input[i + k];
|
|
214
|
-
state->pending_len = (uint8_t)held;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
/* Flush: one U+FFFD per held byte. */
|
|
218
|
-
for (size_t k = i; k < total; k++) append_replacement(out, &out_len);
|
|
219
|
-
i = total;
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
if (!utf8_seq_valid(input + i, seq)) {
|
|
223
|
-
append_replacement(out, &out_len);
|
|
224
|
-
i++;
|
|
225
|
-
continue;
|
|
218
|
+
/* Valid input (the usual case) becomes a string as is; only invalid input
|
|
219
|
+
* pays for a repaired copy, sized by the counting pass. */
|
|
220
|
+
size_t tail = 0;
|
|
221
|
+
size_t out_len = mik__utf8_replace(input, total, stream, nullptr, &tail);
|
|
222
|
+
size_t used = total - tail;
|
|
223
|
+
JSValue ret;
|
|
224
|
+
if (out_len == used) {
|
|
225
|
+
ret = JS_NewStringLen(ctx, (const char*)input, used);
|
|
226
|
+
} else {
|
|
227
|
+
uint8_t* out = static_cast<uint8_t*>(js_malloc(ctx, out_len));
|
|
228
|
+
if (!out) {
|
|
229
|
+
if (heap_input) js_free(ctx, heap_input);
|
|
230
|
+
return JS_EXCEPTION;
|
|
226
231
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
232
|
+
mik__utf8_replace(input, total, stream, out, &tail);
|
|
233
|
+
ret = JS_NewStringLen(ctx, (const char*)out, out_len);
|
|
234
|
+
js_free(ctx, out);
|
|
230
235
|
}
|
|
231
|
-
|
|
236
|
+
if (tail) memcpy(state->pending, input + used, tail);
|
|
237
|
+
state->pending_len = (uint8_t)tail;
|
|
232
238
|
if (heap_input) js_free(ctx, heap_input);
|
|
233
|
-
JSValue ret = JS_NewStringLen(ctx, (const char*)out, out_len);
|
|
234
|
-
js_free(ctx, out);
|
|
235
239
|
return ret;
|
|
236
240
|
}
|
|
237
241
|
|
|
@@ -353,55 +357,41 @@ static size_t b64_decode(const char* src, size_t len, uint8_t* dst, int* err) {
|
|
|
353
357
|
|
|
354
358
|
/* btoa: encode a binary string to base64.
|
|
355
359
|
* Per the web spec, the argument is first coerced to a string (ToString), then
|
|
356
|
-
* each character's code point is treated as a raw byte.
|
|
357
|
-
*
|
|
358
|
-
*
|
|
360
|
+
* each character's code point is treated as a raw byte. The string arrives as
|
|
361
|
+
* UTF-8, so a code point is one byte (ASCII), two bytes (U+0080..U+00FF), or
|
|
362
|
+
* out of range. */
|
|
359
363
|
static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
JSValue len_val = JS_GetPropertyStr(ctx, str_val, "length");
|
|
364
|
-
int64_t len;
|
|
365
|
-
if (JS_ToInt64(ctx, &len, len_val)) {
|
|
366
|
-
JS_FreeValue(ctx, len_val);
|
|
367
|
-
JS_FreeValue(ctx, str_val);
|
|
368
|
-
return JS_EXCEPTION;
|
|
369
|
-
}
|
|
370
|
-
JS_FreeValue(ctx, len_val);
|
|
364
|
+
size_t str_len = 0;
|
|
365
|
+
const char* str = JS_ToCStringLen(ctx, &str_len, argv[0]);
|
|
366
|
+
if (!str) return JS_EXCEPTION;
|
|
371
367
|
|
|
372
|
-
/*
|
|
373
|
-
uint8_t* bytes = static_cast<uint8_t*>(js_malloc(ctx,
|
|
368
|
+
/* Never more code points than UTF-8 bytes. */
|
|
369
|
+
uint8_t* bytes = static_cast<uint8_t*>(js_malloc(ctx, str_len > 0 ? str_len : 1));
|
|
374
370
|
if (!bytes) {
|
|
375
|
-
|
|
371
|
+
JS_FreeCString(ctx, str);
|
|
376
372
|
return JS_EXCEPTION;
|
|
377
373
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return JS_EXCEPTION;
|
|
374
|
+
size_t len = 0;
|
|
375
|
+
for (size_t i = 0; i < str_len;) {
|
|
376
|
+
uint8_t b = (uint8_t)str[i];
|
|
377
|
+
uint32_t cp = 0x100;
|
|
378
|
+
if (b < 0x80) {
|
|
379
|
+
cp = b;
|
|
380
|
+
i += 1;
|
|
381
|
+
} else if ((b & 0xE0) == 0xC0 && i + 1 < str_len) {
|
|
382
|
+
cp = ((b & 0x1Fu) << 6) | ((uint8_t)str[i + 1] & 0x3Fu);
|
|
383
|
+
i += 2;
|
|
389
384
|
}
|
|
390
|
-
int32_t cp;
|
|
391
|
-
JS_ToInt32(ctx, &cp, code);
|
|
392
|
-
JS_FreeValue(ctx, code);
|
|
393
385
|
if (cp > 0xFF) {
|
|
394
|
-
JS_FreeAtom(ctx, charCodeAt_atom);
|
|
395
386
|
js_free(ctx, bytes);
|
|
396
|
-
|
|
387
|
+
JS_FreeCString(ctx, str);
|
|
397
388
|
return JS_ThrowRangeError(ctx,
|
|
398
389
|
"The string to be encoded contains characters outside of the "
|
|
399
390
|
"Latin1 range");
|
|
400
391
|
}
|
|
401
|
-
bytes[
|
|
392
|
+
bytes[len++] = (uint8_t)cp;
|
|
402
393
|
}
|
|
403
|
-
|
|
404
|
-
JS_FreeValue(ctx, str_val);
|
|
394
|
+
JS_FreeCString(ctx, str);
|
|
405
395
|
|
|
406
396
|
size_t out_len = 4 * ((len + 2) / 3);
|
|
407
397
|
char* out = static_cast<char*>(js_malloc(ctx, out_len + 1));
|
|
@@ -411,7 +401,7 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
411
401
|
}
|
|
412
402
|
|
|
413
403
|
size_t j = 0;
|
|
414
|
-
for (
|
|
404
|
+
for (size_t i = 0; i < len; i += 3) {
|
|
415
405
|
uint32_t a = bytes[i];
|
|
416
406
|
uint32_t b = (i + 1 < len) ? bytes[i + 1] : 0;
|
|
417
407
|
uint32_t c = (i + 2 < len) ? bytes[i + 2] : 0;
|
package/src/mik_udp.cpp
CHANGED
|
@@ -520,7 +520,10 @@ static JSValue mik__udp_bind(JSContext* ctx, JSValue this_val, int argc, JSValue
|
|
|
520
520
|
JS_FreeValue(ctx, rq_val);
|
|
521
521
|
if (recv_queue < 1) recv_queue = 1;
|
|
522
522
|
|
|
523
|
-
/* Create socket
|
|
523
|
+
/* Create socket. The stack may not be running yet on platforms that
|
|
524
|
+
* start it on demand (nothing else in this file needs an interface). */
|
|
525
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
526
|
+
if (platform->net_init) platform->net_init();
|
|
524
527
|
int fd = socket(family, SOCK_DGRAM, 0);
|
|
525
528
|
if (fd < 0) {
|
|
526
529
|
if (address) JS_FreeCString(ctx, address);
|
|
@@ -650,6 +653,13 @@ static JSValue mik__udp_bind(JSContext* ctx, JSValue this_val, int argc, JSValue
|
|
|
650
653
|
|
|
651
654
|
/* ── Loop consumer ───────────────────────────────────────────────────*/
|
|
652
655
|
|
|
656
|
+
bool mik__udp_has_open_sockets(JSContext* ctx) {
|
|
657
|
+
for (UdpSocketState* s : g_open_sockets) {
|
|
658
|
+
if (s && !s->dead && !s->closed && s->ctx == ctx) return true;
|
|
659
|
+
}
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
|
|
653
663
|
static void mik__udp_consume(JSContext* ctx) {
|
|
654
664
|
g_iteration_depth++;
|
|
655
665
|
|
package/src/mikrojs.cpp
CHANGED
|
@@ -316,6 +316,7 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
|
|
|
316
316
|
mik_rt->result_proto = JS_UNDEFINED;
|
|
317
317
|
mik_rt->result_ok_void_singleton = JS_UNDEFINED;
|
|
318
318
|
mik_rt->http_body_consumed_ctor = JS_UNDEFINED;
|
|
319
|
+
mik_rt->inspect_cache.object_proto = JS_UNDEFINED;
|
|
319
320
|
|
|
320
321
|
/* Default fs read cap: 64 KiB. Large enough for typical config/JSON
|
|
321
322
|
* payloads on MCU; small enough that a runaway readFile() can't
|
|
@@ -379,11 +380,6 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
|
|
|
379
380
|
mik__inspect_register(ctx);
|
|
380
381
|
mik__pub_fs_register(ctx);
|
|
381
382
|
|
|
382
|
-
/* Call registered native module init functions */
|
|
383
|
-
for (const auto& mod : mik_rt->native_modules) {
|
|
384
|
-
mod.init_fn(ctx);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
383
|
JSModuleDef* stdio_mod = JS_NewCModule(ctx, "native:mikro/stdio", mik__stdio_module_init);
|
|
388
384
|
CHECK_NOT_NULL(stdio_mod);
|
|
389
385
|
mik__add_exports(ctx, stdio_mod, stdio_exports, countof(stdio_exports));
|
|
@@ -455,7 +451,16 @@ void MIK_FreeRuntime(MIKRuntime* mik_rt) {
|
|
|
455
451
|
mik_rt->http_body_consumed_ctor = JS_UNDEFINED;
|
|
456
452
|
JS_FreeValue(mik_rt->ctx, mik_rt->result_proto);
|
|
457
453
|
mik_rt->result_proto = JS_UNDEFINED;
|
|
454
|
+
MIKInspectCache& ic = mik_rt->inspect_cache;
|
|
455
|
+
JS_FreeValue(mik_rt->ctx, ic.object_proto);
|
|
456
|
+
ic.object_proto = JS_UNDEFINED;
|
|
458
457
|
JS_FreeContext(mik_rt->ctx);
|
|
458
|
+
MIKResultAtoms& ra = mik_rt->result_atoms;
|
|
459
|
+
JS_FreeAtomsRT(mik_rt->rt, ra.ok, ra.value, ra.error, ra.err, ra.code, ra.message, ra.name);
|
|
460
|
+
ra = {};
|
|
461
|
+
JS_FreeAtomsRT(mik_rt->rt, ic.inspect_symbol, ic.to_string_tag);
|
|
462
|
+
ic.inspect_symbol = JS_ATOM_NULL;
|
|
463
|
+
ic.to_string_tag = JS_ATOM_NULL;
|
|
459
464
|
JS_FreeRuntime(mik_rt->rt);
|
|
460
465
|
|
|
461
466
|
/* Call destructor for C++ members, then free */
|
|
@@ -531,11 +536,6 @@ void MIK_SetTestEmitHandler(MIKRuntime* mik_rt, MIKTestEmitHandlerFn fn, void* o
|
|
|
531
536
|
mik_rt->test_emit_opaque = opaque;
|
|
532
537
|
}
|
|
533
538
|
|
|
534
|
-
void MIK_RegisterNativeModuleInit(MIKRuntime* mik_rt, const char* name,
|
|
535
|
-
MIKNativeModuleInitFn init_fn) {
|
|
536
|
-
mik_rt->native_modules.push_back({name, init_fn});
|
|
537
|
-
}
|
|
538
|
-
|
|
539
539
|
void MIK_RegisterLoopConsumer(MIKRuntime* mik_rt, MIKLoopConsumeFn consume_fn,
|
|
540
540
|
MIKLoopDestroyFn destroy_fn) {
|
|
541
541
|
/* Idempotent: a module can be brought up twice — once by native code that
|
|
@@ -768,6 +768,51 @@ int MIK_Loop(MIKRuntime* mik_rt) {
|
|
|
768
768
|
return mik_rt->stop_requested ? 1 : 0;
|
|
769
769
|
}
|
|
770
770
|
|
|
771
|
+
/* The nearer of a wake (-1 = none) and a deadline `delay` us away. */
|
|
772
|
+
static int64_t mik__nearer_wake(int64_t wake, int64_t delay) {
|
|
773
|
+
if (delay <= 0) return 0;
|
|
774
|
+
return (wake < 0 || delay < wake) ? delay : wake;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/* The UDP consumer polls recvfrom: bound sockets get this poll period. */
|
|
778
|
+
static constexpr int64_t MIK__UDP_POLL_US = 10 * 1000;
|
|
779
|
+
|
|
780
|
+
int64_t mik__next_wake_us(MIKRuntime* mik_rt) {
|
|
781
|
+
int64_t now = MIK_GetPlatform()->get_boot_us();
|
|
782
|
+
/* The grace window has one job, the restart, and MIK_Loop takes it at
|
|
783
|
+
* the deadline; nothing else runs meanwhile. */
|
|
784
|
+
if (mik_rt->restart_at_us > 0) {
|
|
785
|
+
return mik__nearer_wake(-1, mik_rt->restart_at_us - now);
|
|
786
|
+
}
|
|
787
|
+
if (mik_rt->stop_requested || JS_IsJobPending(mik_rt->rt)) {
|
|
788
|
+
return 0;
|
|
789
|
+
}
|
|
790
|
+
int64_t wake = -1;
|
|
791
|
+
for (const auto& entry : mik_rt->timers->entries) {
|
|
792
|
+
wake = mik__nearer_wake(wake, entry.next_deadline - now);
|
|
793
|
+
if (wake == 0) return 0;
|
|
794
|
+
}
|
|
795
|
+
/* The feed and awake checks run at the top of the pass, so a wait must
|
|
796
|
+
* end by their deadlines for them to fire on time. */
|
|
797
|
+
if (mik_rt->feed_armed) {
|
|
798
|
+
wake = mik__nearer_wake(
|
|
799
|
+
wake, mik_rt->feed_last_us + (int64_t)mik_rt->config.feed_timeout_ms * 1000 - now);
|
|
800
|
+
}
|
|
801
|
+
if (mik_rt->awake_armed) {
|
|
802
|
+
wake = mik__nearer_wake(wake, mik_rt->awake_pause_offset_us +
|
|
803
|
+
(int64_t)mik_rt->config.awake_timeout_ms * 1000 - now);
|
|
804
|
+
}
|
|
805
|
+
if (mik__udp_has_open_sockets(mik_rt->ctx)) {
|
|
806
|
+
wake = mik__nearer_wake(wake, MIK__UDP_POLL_US);
|
|
807
|
+
}
|
|
808
|
+
return wake;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
void MIK_Wake(void) {
|
|
812
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
813
|
+
if (platform->wake) platform->wake();
|
|
814
|
+
}
|
|
815
|
+
|
|
771
816
|
void MIK_SetConfig(MIKRuntime* mik_rt, const MIKConfig* config) {
|
|
772
817
|
CHECK_NOT_NULL(mik_rt);
|
|
773
818
|
CHECK_NOT_NULL(config);
|
package/src/modules.cpp
CHANGED
|
@@ -252,6 +252,7 @@ static JSModuleDef* mik_module_loader_inner(JSContext* ctx, const char* module_n
|
|
|
252
252
|
{"mikro/http/helpers", mik__http_helpers_load},
|
|
253
253
|
{"mikro/http/request", mik__http_request_load},
|
|
254
254
|
{"mikro/observable/operators", mik__observable_operators_load},
|
|
255
|
+
{"mikro/test", mik__test_load},
|
|
255
256
|
{"mikro/wifi", mik__wifi_client_load},
|
|
256
257
|
};
|
|
257
258
|
for (const auto& cm : c_modules) {
|
package/src/platform_posix.cpp
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#include "mikrojs/platform.h"
|
|
2
2
|
|
|
3
|
+
#include <errno.h>
|
|
4
|
+
#include <pthread.h>
|
|
3
5
|
#include <stdarg.h>
|
|
4
6
|
#include <stdio.h>
|
|
5
7
|
#include <stdlib.h>
|
|
@@ -29,6 +31,38 @@ static void posix_yield(void) {
|
|
|
29
31
|
usleep(1000);
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
/* One waiter (the serve loop's thread); wakes are latched so one that lands
|
|
35
|
+
* before the wait is not lost. */
|
|
36
|
+
static pthread_mutex_t posix_wake_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
37
|
+
static pthread_cond_t posix_wake_cond = PTHREAD_COND_INITIALIZER;
|
|
38
|
+
static bool posix_wake_pending = false;
|
|
39
|
+
|
|
40
|
+
static void posix_wait(int64_t timeout_us) {
|
|
41
|
+
struct timespec deadline;
|
|
42
|
+
clock_gettime(CLOCK_REALTIME, &deadline);
|
|
43
|
+
deadline.tv_sec += timeout_us / 1000000;
|
|
44
|
+
deadline.tv_nsec += (timeout_us % 1000000) * 1000;
|
|
45
|
+
if (deadline.tv_nsec >= 1000000000L) {
|
|
46
|
+
deadline.tv_sec += 1;
|
|
47
|
+
deadline.tv_nsec -= 1000000000L;
|
|
48
|
+
}
|
|
49
|
+
pthread_mutex_lock(&posix_wake_mutex);
|
|
50
|
+
while (!posix_wake_pending) {
|
|
51
|
+
if (pthread_cond_timedwait(&posix_wake_cond, &posix_wake_mutex, &deadline) == ETIMEDOUT) {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
posix_wake_pending = false;
|
|
56
|
+
pthread_mutex_unlock(&posix_wake_mutex);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static void posix_wake(void) {
|
|
60
|
+
pthread_mutex_lock(&posix_wake_mutex);
|
|
61
|
+
posix_wake_pending = true;
|
|
62
|
+
pthread_cond_signal(&posix_wake_cond);
|
|
63
|
+
pthread_mutex_unlock(&posix_wake_mutex);
|
|
64
|
+
}
|
|
65
|
+
|
|
32
66
|
static size_t posix_get_free_system_mem(void) {
|
|
33
67
|
return 0; /* Not available on desktop */
|
|
34
68
|
}
|
|
@@ -152,6 +186,8 @@ static const MIKPlatform posix_platform = {
|
|
|
152
186
|
.random = posix_random,
|
|
153
187
|
.restart = posix_restart,
|
|
154
188
|
.yield = posix_yield,
|
|
189
|
+
.wait = posix_wait,
|
|
190
|
+
.wake = posix_wake,
|
|
155
191
|
.get_free_system_mem = posix_get_free_system_mem,
|
|
156
192
|
.get_min_free_system_mem = posix_get_min_free_system_mem,
|
|
157
193
|
.get_total_system_mem = posix_get_total_system_mem,
|
package/src/timers.cpp
CHANGED
|
@@ -152,9 +152,9 @@ void mik__timers_consume(JSContext* ctx) {
|
|
|
152
152
|
|
|
153
153
|
for (auto& entry : timers->entries) {
|
|
154
154
|
if (entry.next_deadline <= now) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
// Past the cap, a timer stays due and runs on the next pass.
|
|
156
|
+
if (due_count == MIK_MAX_DUE_TIMERS) break;
|
|
157
|
+
due_ids[due_count++] = entry.id;
|
|
158
158
|
// Drift-free: advance from the previous deadline, not from now, so the rate
|
|
159
159
|
// stays exact. After a stall of a whole period, resume from now; never burst.
|
|
160
160
|
if (entry.is_interval) {
|
package/src/utils.cpp
CHANGED
|
@@ -24,8 +24,7 @@ JSValue mik_throw_errno(JSContext* ctx, int err) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
void mik_assert(const struct AssertionInfo info) {
|
|
27
|
-
fprintf(stderr, "%s
|
|
28
|
-
*info.function ? ":" : "", info.message);
|
|
27
|
+
fprintf(stderr, "%s Assertion '%s' failed\n", info.file_line, info.message);
|
|
29
28
|
fflush(stderr);
|
|
30
29
|
abort();
|
|
31
30
|
}
|
|
@@ -234,12 +233,6 @@ void MIK_ClearPromise(JSContext* ctx, MIKPromise* p) {
|
|
|
234
233
|
p->rfuncs[1] = JS_UNDEFINED;
|
|
235
234
|
}
|
|
236
235
|
|
|
237
|
-
void MIK_MarkPromise(JSRuntime* rt, MIKPromise* p, JS_MarkFunc* mark_func) {
|
|
238
|
-
JS_MarkValue(rt, p->p, mark_func);
|
|
239
|
-
JS_MarkValue(rt, p->rfuncs[0], mark_func);
|
|
240
|
-
JS_MarkValue(rt, p->rfuncs[1], mark_func);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
236
|
void MIK_SettlePromise(JSContext* ctx, MIKPromise* p, bool is_reject, int argc, JSValue* argv) {
|
|
244
237
|
JSValue ret = JS_Call(ctx, p->rfuncs[is_reject], JS_UNDEFINED, argc, argv);
|
|
245
238
|
for (int i = 0; i < argc; i++) {
|
|
@@ -301,7 +294,19 @@ static void* mik__buf_realloc(JSRuntime* rt, void* opaque, void* ptr, size_t siz
|
|
|
301
294
|
}
|
|
302
295
|
|
|
303
296
|
JSValue MIK_NewUint8Array(JSContext* ctx, uint8_t* data, size_t size) {
|
|
304
|
-
|
|
297
|
+
/* JS_NewUint8Array leaks `data` when the ArrayBuffer object cannot be
|
|
298
|
+
* allocated. Once JS_NewArrayBuffer succeeds, the buffer owns `data`. */
|
|
299
|
+
JSValue ab = JS_NewArrayBuffer(ctx, data, size, 0, mik__buf_realloc, NULL, false);
|
|
300
|
+
if (JS_IsException(ab)) {
|
|
301
|
+
js_free(ctx, data);
|
|
302
|
+
return ab;
|
|
303
|
+
}
|
|
304
|
+
/* Before quickjs-ng 0.17.0, JS_NewTypedArray reads argv[1] and argv[2]
|
|
305
|
+
* whatever argc says, so pass all three. */
|
|
306
|
+
JSValueConst args[3] = {ab, JS_UNDEFINED, JS_UNDEFINED};
|
|
307
|
+
JSValue u8 = JS_NewTypedArray(ctx, 3, args, JS_TYPED_ARRAY_UINT8);
|
|
308
|
+
JS_FreeValue(ctx, ab);
|
|
309
|
+
return u8;
|
|
305
310
|
}
|
|
306
311
|
|
|
307
312
|
void mik_dbuf_init(JSContext* ctx, DynBuf* s) {
|