@mikrojs/native 0.17.0 → 0.17.1-pr-300.20260731003316
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 +1 -0
- package/cmake/mikrojs_bytecode.cmake +50 -3
- package/include/mikrojs/cutils_wrap.h +2 -0
- package/include/mikrojs/mikrojs.h +6 -2
- package/include/mikrojs/private.h +3 -0
- package/package.json +3 -3
- 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/scripts/compile-bytecode.sh +22 -3
- package/scripts/extract-atoms.js +144 -0
- package/src/builtins.cpp +17 -1
- package/src/cutils_compat.c +4 -0
- package/src/mikrojs.cpp +17 -0
package/CMakeLists.txt
CHANGED
|
@@ -25,7 +25,11 @@ get_filename_component(_MIK_BC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
|
|
|
25
25
|
set(_MIK_BC_SCRIPTS_DIR "${_MIK_BC_CMAKE_DIR}/../scripts" CACHE INTERNAL "")
|
|
26
26
|
|
|
27
27
|
function(mikrojs_generate_bytecode)
|
|
28
|
-
|
|
28
|
+
# FREEZE_ATOMS: compile blobs against a generated frozen atom table so
|
|
29
|
+
# they load zero-copy (JS_READ_OBJ_INPLACE). Core runtime only: the
|
|
30
|
+
# device preloads exactly one table, so board/driver packages must
|
|
31
|
+
# stay classic (they load via the copy-and-relocate fallback).
|
|
32
|
+
cmake_parse_arguments(ARG "FREEZE_ATOMS" "RUNTIME_DIR;MODULE_PREFIX;SYMBOL_PREFIX;TARGET;WORKING_DIRECTORY" "MODULES" ${ARGN})
|
|
29
33
|
|
|
30
34
|
# Defaults
|
|
31
35
|
if(NOT ARG_TARGET)
|
|
@@ -86,7 +90,46 @@ function(mikrojs_generate_bytecode)
|
|
|
86
90
|
WORKING_DIRECTORY ${ARG_WORKING_DIRECTORY}
|
|
87
91
|
)
|
|
88
92
|
|
|
89
|
-
#
|
|
93
|
+
# Steps 2a/2b (FREEZE_ATOMS only): pass-1 raw bytecode per module, then
|
|
94
|
+
# union the atom sections into the frozen atom table (frozen_atoms.bin
|
|
95
|
+
# for qjsc -A, <prefix>_frozen_atoms.h for the runtime preload).
|
|
96
|
+
# Module order fixes the table order.
|
|
97
|
+
set(_ATOMS_BIN "")
|
|
98
|
+
set(_ATOMS_HEADER "")
|
|
99
|
+
if(ARG_FREEZE_ATOMS)
|
|
100
|
+
set(_EXTRACT_SCRIPT "${_MIK_BC_SCRIPTS_DIR}/extract-atoms.js")
|
|
101
|
+
set(_PASS1_BLOBS "")
|
|
102
|
+
foreach(mod ${ARG_MODULES})
|
|
103
|
+
string(REGEX REPLACE "[^a-zA-Z0-9]" "_" _mod_safe "${mod}")
|
|
104
|
+
set(_blob "${_GEN_DIR}/pass1/${_mod_safe}.bjs")
|
|
105
|
+
add_custom_command(
|
|
106
|
+
OUTPUT ${_blob}
|
|
107
|
+
COMMAND ${CMAKE_COMMAND} -E make_directory ${_GEN_DIR}/pass1
|
|
108
|
+
COMMAND sh ${_COMPILE_SCRIPT}
|
|
109
|
+
${QJSC_EXECUTABLE}
|
|
110
|
+
${_BUNDLE_DIR}/${mod}.js
|
|
111
|
+
${_blob}
|
|
112
|
+
"${ARG_MODULE_PREFIX}/${mod}"
|
|
113
|
+
"unused"
|
|
114
|
+
DEPENDS ${_BUNDLE_DIR}/bundle.stamp ${_COMPILE_SCRIPT} ${QJSC_EXECUTABLE}
|
|
115
|
+
COMMENT "Bytecode pass 1: ${ARG_MODULE_PREFIX}/${mod}"
|
|
116
|
+
)
|
|
117
|
+
list(APPEND _PASS1_BLOBS ${_blob})
|
|
118
|
+
endforeach()
|
|
119
|
+
|
|
120
|
+
set(_ATOMS_BIN "${_GEN_DIR}/frozen_atoms.bin")
|
|
121
|
+
set(_ATOMS_HEADER "${_GEN_DIR}/${ARG_SYMBOL_PREFIX}_frozen_atoms.h")
|
|
122
|
+
add_custom_command(
|
|
123
|
+
OUTPUT ${_ATOMS_BIN} ${_ATOMS_HEADER}
|
|
124
|
+
COMMAND node ${_EXTRACT_SCRIPT} ${_GEN_DIR} ${ARG_SYMBOL_PREFIX} ${_PASS1_BLOBS}
|
|
125
|
+
DEPENDS ${_PASS1_BLOBS} ${_EXTRACT_SCRIPT}
|
|
126
|
+
COMMENT "Extracting frozen atom table"
|
|
127
|
+
)
|
|
128
|
+
endif()
|
|
129
|
+
|
|
130
|
+
# Step 2c: Compile each bundled JS file to a bytecode header with qjsc,
|
|
131
|
+
# against the frozen atom table so atom references are final ids
|
|
132
|
+
# (enables zero-copy loading via JS_READ_OBJ_INPLACE).
|
|
90
133
|
set(_HEADERS "")
|
|
91
134
|
foreach(mod ${ARG_MODULES})
|
|
92
135
|
# Sanitize module name for C identifiers (replace non-alphanumeric with _)
|
|
@@ -101,11 +144,15 @@ function(mikrojs_generate_bytecode)
|
|
|
101
144
|
${_header}
|
|
102
145
|
"${ARG_MODULE_PREFIX}/${mod}"
|
|
103
146
|
"${ARG_SYMBOL_PREFIX}_${_mod_safe}_bytecode"
|
|
104
|
-
|
|
147
|
+
${_ATOMS_BIN}
|
|
148
|
+
DEPENDS ${_BUNDLE_DIR}/bundle.stamp ${_COMPILE_SCRIPT} ${QJSC_EXECUTABLE} ${_ATOMS_BIN}
|
|
105
149
|
COMMENT "Compiling bytecode: ${ARG_MODULE_PREFIX}/${mod}"
|
|
106
150
|
)
|
|
107
151
|
list(APPEND _HEADERS ${_header})
|
|
108
152
|
endforeach()
|
|
153
|
+
if(_ATOMS_HEADER)
|
|
154
|
+
list(APPEND _HEADERS ${_ATOMS_HEADER})
|
|
155
|
+
endif()
|
|
109
156
|
|
|
110
157
|
# Step 3: Generate builtins table header (includes + lookup table)
|
|
111
158
|
set(_TABLE_HEADER "${_GEN_DIR}/${ARG_SYMBOL_PREFIX}_builtins_table.h")
|
|
@@ -36,6 +36,7 @@ typedef struct DynBuf {
|
|
|
36
36
|
|
|
37
37
|
/* Wrapper functions defined in cutils_compat.c */
|
|
38
38
|
int mik__dbuf_put(DynBuf *s, const void *data, size_t len);
|
|
39
|
+
int mik__dbuf_claim(DynBuf *s, size_t len);
|
|
39
40
|
int mik__dbuf_putc(DynBuf *s, uint8_t val);
|
|
40
41
|
int mik__dbuf_putstr(DynBuf *s, const char *str);
|
|
41
42
|
void mik__dbuf_free(DynBuf *s);
|
|
@@ -46,6 +47,7 @@ char *mik__js_pstrcat(char *buf, int buf_size, const char *s);
|
|
|
46
47
|
|
|
47
48
|
/* Map original names to wrapper names */
|
|
48
49
|
#define dbuf_put mik__dbuf_put
|
|
50
|
+
#define dbuf_claim mik__dbuf_claim
|
|
49
51
|
#define dbuf_putc mik__dbuf_putc
|
|
50
52
|
#define dbuf_putstr mik__dbuf_putstr
|
|
51
53
|
#define dbuf_free mik__dbuf_free
|
|
@@ -288,8 +288,12 @@ extern mik_module_desc_t* mik__module_registry_head;
|
|
|
288
288
|
* Driver packages use MIK_REGISTER_BUILTIN() to embed their JS wrappers as
|
|
289
289
|
* firmware builtins, resolved by the real npm package name (e.g. "@mikrojs/your-driver"). */
|
|
290
290
|
typedef struct mik_ext_builtin_t {
|
|
291
|
-
const char* name;
|
|
292
|
-
|
|
291
|
+
const char* name; /* module name, e.g. "@mikrojs/your-driver" */
|
|
292
|
+
/* Compiled bytecode. Must outlive every runtime that imports the
|
|
293
|
+
* module: the loader may keep instruction streams pointing into this
|
|
294
|
+
* buffer instead of copying them to the heap (JS_READ_OBJ_INPLACE).
|
|
295
|
+
* Use a static const array (flash/rodata), never a heap buffer. */
|
|
296
|
+
const uint8_t* data;
|
|
293
297
|
uint32_t data_size;
|
|
294
298
|
struct mik_ext_builtin_t* next;
|
|
295
299
|
} mik_ext_builtin_t;
|
|
@@ -187,6 +187,9 @@ void mik__flush_unhandled_rejections(JSContext* ctx);
|
|
|
187
187
|
/* Drop a promise from the pending-rejection queue without reporting it. */
|
|
188
188
|
void mik__forget_rejection(JSContext* ctx, JSValue promise);
|
|
189
189
|
JSModuleDef* mik__load_builtin(JSContext* ctx, const char* name);
|
|
190
|
+
/* Preload the generated frozen atom table (must run right after
|
|
191
|
+
* JS_NewRuntime2, before any context/intrinsic creates atoms). */
|
|
192
|
+
int mik__preload_frozen_atoms(JSRuntime* rt);
|
|
190
193
|
int mik__load_file(JSContext* ctx, DynBuf* dbuf, const char* filename);
|
|
191
194
|
void mik__resolve_fs_path(JSContext* ctx, const char* module_name, char* out, size_t out_size);
|
|
192
195
|
int mik__resolve_fs_root(JSContext* ctx, const char* path, char* out, size_t out_size);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikrojs/native",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.1-pr-300.20260731003316",
|
|
4
4
|
"description": "Mikro.js C++ runtime library and Node.js native addon",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"esp32",
|
|
@@ -86,14 +86,14 @@
|
|
|
86
86
|
"cmake-js": "^8.0.0",
|
|
87
87
|
"node-addon-api": "^8.7.0",
|
|
88
88
|
"node-gyp-build": "^4.8.4",
|
|
89
|
-
"@mikrojs/quickjs": "0.17.
|
|
89
|
+
"@mikrojs/quickjs": "0.17.1-pr-300.20260731003316+67c29b9"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@swc/core": "^1.15.30",
|
|
93
93
|
"@types/node": "^24.12.2",
|
|
94
94
|
"esbuild": "^0.28.0",
|
|
95
95
|
"terser": "^5.46.2",
|
|
96
|
-
"@mikrojs/registry": "0.17.
|
|
96
|
+
"@mikrojs/registry": "0.17.1-pr-300.20260731003316+67c29b9"
|
|
97
97
|
},
|
|
98
98
|
"engines": {
|
|
99
99
|
"node": ">=24.0.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Compile a bundled JS module to a C bytecode header using qjsc.
|
|
3
3
|
#
|
|
4
|
-
# Usage: compile-bytecode.sh <qjsc> <input.js> <output.h> <module_name> <symbol_name>
|
|
4
|
+
# Usage: compile-bytecode.sh <qjsc> <input.js> <output.h> <module_name> <symbol_name> [atoms.bin]
|
|
5
5
|
#
|
|
6
6
|
# Reads <input>.externals for external module declarations.
|
|
7
|
+
# If <output> ends in .bjs, emits raw bytecode instead of a C header
|
|
8
|
+
# (pass 1 of the frozen-atom pipeline; see extract-atoms.js).
|
|
9
|
+
# If [atoms.bin] is given, compiles against that frozen atom table (-A).
|
|
7
10
|
|
|
8
11
|
set -e
|
|
9
12
|
|
|
@@ -12,6 +15,7 @@ INPUT="$2"
|
|
|
12
15
|
OUTPUT="$3"
|
|
13
16
|
MODULE_NAME="$4"
|
|
14
17
|
SYMBOL_NAME="$5"
|
|
18
|
+
ATOMS_BIN="$6"
|
|
15
19
|
|
|
16
20
|
EXTERNALS_FILE="${INPUT%.js}.externals"
|
|
17
21
|
|
|
@@ -34,5 +38,20 @@ fi
|
|
|
34
38
|
# so any runtime module that reads `import.meta.*` (e.g. mikrojs/env
|
|
35
39
|
# accessing import.meta.env) breaks at runtime. The ~1 KB heap saving
|
|
36
40
|
# from stripping debug isn't worth crippling a spec-standard module API.
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
A_FLAGS=""
|
|
42
|
+
if [ -n "$ATOMS_BIN" ]; then
|
|
43
|
+
A_FLAGS="-A $ATOMS_BIN"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
case "$OUTPUT" in
|
|
47
|
+
*.bjs)
|
|
48
|
+
# Raw bytecode (frozen-atom pass 1). Must use the same flags as the
|
|
49
|
+
# header pass so the atom set matches, minus -N (no C symbol).
|
|
50
|
+
# shellcheck disable=SC2086
|
|
51
|
+
"$QJSC" -b -m -s -n "$MODULE_NAME" $M_FLAGS $A_FLAGS -o "$OUTPUT" "$INPUT"
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
# shellcheck disable=SC2086
|
|
55
|
+
"$QJSC" -m -s -N "$SYMBOL_NAME" -n "$MODULE_NAME" $M_FLAGS $A_FLAGS -o "$OUTPUT" "$INPUT"
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
/**
|
|
4
|
+
* Build the frozen atom table from pass-1 raw bytecode blobs.
|
|
5
|
+
*
|
|
6
|
+
* Reads the atom section of each .bjs blob (compiled without a frozen
|
|
7
|
+
* table), unions the narrow plain-string atoms in first-seen order, and
|
|
8
|
+
* emits:
|
|
9
|
+
* - frozen_atoms.bin — binary table for `qjsc -A` (u32le count, then
|
|
10
|
+
* u32le len + bytes per atom)
|
|
11
|
+
* - <symbol_prefix>_frozen_atoms.h — C arrays for JS_PreloadFrozenAtoms
|
|
12
|
+
*
|
|
13
|
+
* Wide-char and symbol atoms are skipped: they stay dynamic, and any
|
|
14
|
+
* function referencing them falls back to the copy-and-relocate load path.
|
|
15
|
+
*
|
|
16
|
+
* Usage: extract-atoms.js <gen_dir> <symbol_prefix> <blob1.bjs> [blob2.bjs ...]
|
|
17
|
+
*/
|
|
18
|
+
import {readFileSync, writeFileSync} from 'node:fs'
|
|
19
|
+
import {join} from 'node:path'
|
|
20
|
+
|
|
21
|
+
const JS_ATOM_TYPE_STRING = 1
|
|
22
|
+
|
|
23
|
+
function readLeb128(buf, pos) {
|
|
24
|
+
let v = 0
|
|
25
|
+
let shift = 0
|
|
26
|
+
for (;;) {
|
|
27
|
+
const b = buf[pos++]
|
|
28
|
+
v |= (b & 0x7f) << shift
|
|
29
|
+
if ((b & 0x80) === 0) break
|
|
30
|
+
shift += 7
|
|
31
|
+
}
|
|
32
|
+
return [v >>> 0, pos]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const BC_VERSION = 26
|
|
36
|
+
const BC_VERSION_FROZEN = BC_VERSION | 0x40
|
|
37
|
+
|
|
38
|
+
/** Parse the atom section of a classic bytecode blob, returning the
|
|
39
|
+
* narrow plain-string atom names as Buffers. */
|
|
40
|
+
function extractAtoms(blob, path) {
|
|
41
|
+
// Layout: u8 version, u32 checksum, leb128 atom count, entries.
|
|
42
|
+
// A misparse here degrades silently (empty table → classic blobs →
|
|
43
|
+
// green build with zero memory win), so reject anything unexpected.
|
|
44
|
+
if (blob[0] === BC_VERSION_FROZEN) {
|
|
45
|
+
throw new Error(`${path}: pass-1 blob is already frozen-format — pipeline ordering bug`)
|
|
46
|
+
}
|
|
47
|
+
if (blob[0] !== BC_VERSION) {
|
|
48
|
+
throw new Error(`${path}: unknown bytecode version ${blob[0]} (expected ${BC_VERSION})`)
|
|
49
|
+
}
|
|
50
|
+
const [count, afterCount] = readLeb128(blob, 5)
|
|
51
|
+
let pos = afterCount
|
|
52
|
+
const atoms = []
|
|
53
|
+
for (let i = 0; i < count; i++) {
|
|
54
|
+
const type = blob[pos++]
|
|
55
|
+
if (type === 0) {
|
|
56
|
+
pos += 4 // predefined atom id
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
let lenWide
|
|
60
|
+
;[lenWide, pos] = readLeb128(blob, pos)
|
|
61
|
+
const isWide = lenWide & 1
|
|
62
|
+
const len = lenWide >>> 1
|
|
63
|
+
const size = isWide ? len * 2 : len
|
|
64
|
+
if (type === JS_ATOM_TYPE_STRING && !isWide) {
|
|
65
|
+
atoms.push(blob.subarray(pos, pos + len))
|
|
66
|
+
}
|
|
67
|
+
pos += size
|
|
68
|
+
}
|
|
69
|
+
return atoms
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function cEscape(buf) {
|
|
73
|
+
let out = ''
|
|
74
|
+
for (const b of buf) {
|
|
75
|
+
if (b >= 0x20 && b < 0x7f && b !== 0x22 && b !== 0x5c && b !== 0x3f) {
|
|
76
|
+
out += String.fromCharCode(b)
|
|
77
|
+
} else {
|
|
78
|
+
// Octal escapes are always 3 digits here, so a following digit
|
|
79
|
+
// cannot extend the escape (unlike \x).
|
|
80
|
+
out += '\\' + b.toString(8).padStart(3, '0')
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const [genDir, symbolPrefix, ...blobPaths] = process.argv.slice(2)
|
|
87
|
+
if (!genDir || !symbolPrefix || blobPaths.length === 0) {
|
|
88
|
+
console.error('usage: extract-atoms.js <gen_dir> <symbol_prefix> <blob.bjs>...')
|
|
89
|
+
process.exit(1)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const seen = new Set()
|
|
93
|
+
const names = []
|
|
94
|
+
let totalBlobBytes = 0
|
|
95
|
+
for (const path of blobPaths) {
|
|
96
|
+
const blob = readFileSync(path)
|
|
97
|
+
totalBlobBytes += blob.length
|
|
98
|
+
for (const atom of extractAtoms(blob, path)) {
|
|
99
|
+
const key = atom.toString('latin1')
|
|
100
|
+
if (!seen.has(key)) {
|
|
101
|
+
seen.add(key)
|
|
102
|
+
names.push(atom)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Real builtin bundles always introduce atoms; an empty table from
|
|
108
|
+
// non-trivial input means the parse silently missed the atom section.
|
|
109
|
+
if (names.length === 0 && totalBlobBytes > 1024) {
|
|
110
|
+
console.error(
|
|
111
|
+
`extract-atoms: parsed ${blobPaths.length} blobs (${totalBlobBytes} bytes) but found zero atoms — refusing to emit an empty frozen table`,
|
|
112
|
+
)
|
|
113
|
+
process.exit(1)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Binary table for qjsc -A
|
|
117
|
+
const chunks = [Buffer.alloc(4)]
|
|
118
|
+
chunks[0].writeUInt32LE(names.length, 0)
|
|
119
|
+
for (const name of names) {
|
|
120
|
+
const lenBuf = Buffer.alloc(4)
|
|
121
|
+
lenBuf.writeUInt32LE(name.length, 0)
|
|
122
|
+
chunks.push(lenBuf, name)
|
|
123
|
+
}
|
|
124
|
+
writeFileSync(join(genDir, 'frozen_atoms.bin'), Buffer.concat(chunks))
|
|
125
|
+
|
|
126
|
+
// C header for JS_PreloadFrozenAtoms
|
|
127
|
+
let h = '/* Auto-generated by extract-atoms.js — do not edit */\n'
|
|
128
|
+
if (names.length === 0) {
|
|
129
|
+
h += `static const char* const ${symbolPrefix}_frozen_atom_names[] = {NULL};\n`
|
|
130
|
+
h += `static const uint32_t ${symbolPrefix}_frozen_atom_lens[] = {0};\n`
|
|
131
|
+
} else {
|
|
132
|
+
h += `static const char* const ${symbolPrefix}_frozen_atom_names[] = {\n`
|
|
133
|
+
for (const name of names) {
|
|
134
|
+
h += ` "${cEscape(name)}",\n`
|
|
135
|
+
}
|
|
136
|
+
h += '};\n'
|
|
137
|
+
h += `static const uint32_t ${symbolPrefix}_frozen_atom_lens[] = {\n `
|
|
138
|
+
h += names.map((n) => n.length).join(', ')
|
|
139
|
+
h += ',\n};\n'
|
|
140
|
+
}
|
|
141
|
+
h += `enum { ${symbolPrefix}_frozen_atom_count = ${names.length} };\n`
|
|
142
|
+
writeFileSync(join(genDir, `${symbolPrefix}_frozen_atoms.h`), h)
|
|
143
|
+
|
|
144
|
+
console.log(`extract-atoms: ${names.length} frozen atoms from ${blobPaths.length} blobs`)
|
package/src/builtins.cpp
CHANGED
|
@@ -17,6 +17,19 @@ typedef struct {
|
|
|
17
17
|
#include "gen/mikro_builtins_table.h"
|
|
18
18
|
#define builtins mikro_builtins
|
|
19
19
|
|
|
20
|
+
/* Frozen atom table generated alongside the builtin bytecode: preloading it
|
|
21
|
+
* right after runtime creation gives builtin blobs final atom ids, letting
|
|
22
|
+
* their instruction streams load zero-copy from flash/rodata. */
|
|
23
|
+
#include "gen/mikro_frozen_atoms.h"
|
|
24
|
+
|
|
25
|
+
int mik__preload_frozen_atoms(JSRuntime* rt) {
|
|
26
|
+
if (mikro_frozen_atom_count == 0) {
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
return JS_PreloadFrozenAtoms(rt, mikro_frozen_atom_names, mikro_frozen_atom_lens,
|
|
30
|
+
mikro_frozen_atom_count);
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
/* External builtins registered by board/driver packages via MIK_REGISTER_BUILTIN() */
|
|
21
34
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
22
35
|
mik_ext_builtin_t* mik__ext_builtin_head = nullptr;
|
|
@@ -29,7 +42,10 @@ static JSModuleDef* mik__deserialize_builtin(JSContext* ctx, const char* name,
|
|
|
29
42
|
platform->log(MIK_LOG_DEBUG, "mikrojs", "Loading builtin '%s' (%u bytes, bc_version=%u, data_ptr=%p)", name,
|
|
30
43
|
data_size, data_size > 0 ? data[0] : 0, (void*)data);
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
/* Builtin bytecode lives in flash/rodata for the runtime's lifetime, so
|
|
46
|
+
* qualifying instruction streams stay in place instead of being copied
|
|
47
|
+
* to the JS heap (falls back per function for non-final atom refs). */
|
|
48
|
+
JSValue obj = JS_ReadObject(ctx, data, data_size, JS_READ_OBJ_BYTECODE | JS_READ_OBJ_INPLACE);
|
|
33
49
|
|
|
34
50
|
if (JS_IsException(obj)) {
|
|
35
51
|
/* Peek the exception for the log, then re-throw it so the caller
|
package/src/cutils_compat.c
CHANGED
|
@@ -9,6 +9,10 @@ int mik__dbuf_put(DynBuf *s, const void *data, size_t len) {
|
|
|
9
9
|
return dbuf_put(s, data, len);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
int mik__dbuf_claim(DynBuf *s, size_t len) {
|
|
13
|
+
return dbuf_claim(s, len);
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
int mik__dbuf_putc(DynBuf *s, uint8_t val) {
|
|
13
17
|
return dbuf_putc(s, val);
|
|
14
18
|
}
|
package/src/mikrojs.cpp
CHANGED
|
@@ -286,6 +286,12 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
|
|
|
286
286
|
CHECK_NOT_NULL(rt);
|
|
287
287
|
mik_rt->rt = rt;
|
|
288
288
|
|
|
289
|
+
/* Builtin blobs are compiled against this table; a mismatch would make
|
|
290
|
+
* every builtin import fail, and failure here can only mean atoms were
|
|
291
|
+
* created before this point (a programming error). Must run before any
|
|
292
|
+
* context/intrinsic setup creates atoms. */
|
|
293
|
+
CHECK(mik__preload_frozen_atoms(rt) == 0);
|
|
294
|
+
|
|
289
295
|
/* Seed Math.random() with real entropy.
|
|
290
296
|
* On ESP32 the system clock starts near epoch 0 on every boot, producing
|
|
291
297
|
* the same seed. Use platform RNG to inject entropy. */
|
|
@@ -850,6 +856,17 @@ int mik__load_file(JSContext* ctx, DynBuf* dbuf, const char* filename) {
|
|
|
850
856
|
return -1;
|
|
851
857
|
}
|
|
852
858
|
|
|
859
|
+
/* Reserve the exact file size up front: DynBuf grows 1.5x from zero, so
|
|
860
|
+
* letting it grow incrementally peaks at ~2.5x the file size mid-copy.
|
|
861
|
+
* +4 covers the NUL/wrapper suffix callers append after loading. */
|
|
862
|
+
struct stat st;
|
|
863
|
+
if (fstat(fd, &st) == 0 && st.st_size > 0) {
|
|
864
|
+
if (dbuf_claim(dbuf, (size_t)st.st_size + 4)) {
|
|
865
|
+
close(fd);
|
|
866
|
+
return -1;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
853
870
|
uint8_t buf[512];
|
|
854
871
|
ssize_t n;
|
|
855
872
|
while ((n = read(fd, buf, sizeof(buf))) > 0) {
|