@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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compare minifier output sizes for firmware runtime modules.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node scripts/compare-minifiers.js [--open]
|
|
7
|
+
*
|
|
8
|
+
* Builds runtime modules with each minifier/level combination, collects
|
|
9
|
+
* bundle sizes, and writes an HTML report to compare-minifiers.html.
|
|
10
|
+
* Pass --open to open the report in the default browser.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {execSync} from 'node:child_process'
|
|
14
|
+
import {existsSync, mkdirSync, readFileSync, rmSync, writeFileSync} from 'node:fs'
|
|
15
|
+
import {join} from 'node:path'
|
|
16
|
+
import {fileURLToPath} from 'node:url'
|
|
17
|
+
|
|
18
|
+
const ROOT = join(fileURLToPath(import.meta.url), '../..')
|
|
19
|
+
const CONFIGS = [
|
|
20
|
+
{name: 'esbuild', args: ''},
|
|
21
|
+
{name: 'terser', args: '-DMIK_MINIFIER=terser'},
|
|
22
|
+
{name: 'terser max', args: '-DMIK_MINIFIER=terser -DMIK_MINIFY_LEVEL=max'},
|
|
23
|
+
{name: 'swc', args: '-DMIK_MINIFIER=swc'},
|
|
24
|
+
{name: 'swc max', args: '-DMIK_MINIFIER=swc -DMIK_MINIFY_LEVEL=max'},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
function run(cmd) {
|
|
28
|
+
return execSync(cmd, {cwd: ROOT, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseBundleOutput(output) {
|
|
32
|
+
const modules = new Map()
|
|
33
|
+
for (const match of output.matchAll(/Bundled (\S+) \((\d+) bytes/g)) {
|
|
34
|
+
modules.set(match[1], Number(match[2]))
|
|
35
|
+
}
|
|
36
|
+
return modules
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildConfig(config) {
|
|
40
|
+
const buildDir = join(ROOT, `.compare-${config.name.replace(/\s+/g, '-')}`)
|
|
41
|
+
if (existsSync(buildDir)) rmSync(buildDir, {recursive: true, force: true})
|
|
42
|
+
mkdirSync(buildDir, {recursive: true})
|
|
43
|
+
|
|
44
|
+
const configureCmd = `cmake -B ${buildDir} ${config.args} .`
|
|
45
|
+
run(configureCmd)
|
|
46
|
+
|
|
47
|
+
const buildCmd = `cmake --build ${buildDir} --target gen_bytecode`
|
|
48
|
+
const output = run(buildCmd)
|
|
49
|
+
|
|
50
|
+
const modules = parseBundleOutput(output)
|
|
51
|
+
rmSync(buildDir, {recursive: true, force: true})
|
|
52
|
+
return modules
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Run all builds
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.log('Comparing minifiers for firmware runtime modules...\n')
|
|
58
|
+
const results = []
|
|
59
|
+
for (const config of CONFIGS) {
|
|
60
|
+
process.stdout.write(` Building with ${config.name}...`)
|
|
61
|
+
const start = Date.now()
|
|
62
|
+
const modules = buildConfig(config)
|
|
63
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1)
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.log(` ${modules.size} modules (${elapsed}s)`)
|
|
66
|
+
results.push({name: config.name, modules})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Collect all module names
|
|
70
|
+
const allModules = [...new Set(results.flatMap((r) => [...r.modules.keys()]))].sort()
|
|
71
|
+
|
|
72
|
+
// Find the baseline (esbuild)
|
|
73
|
+
const baseline = results[0].modules
|
|
74
|
+
|
|
75
|
+
// Generate HTML
|
|
76
|
+
function generateHtml() {
|
|
77
|
+
const rows = allModules.map((mod) => {
|
|
78
|
+
const cells = results.map((r) => {
|
|
79
|
+
const size = r.modules.get(mod) ?? 0
|
|
80
|
+
const base = baseline.get(mod) ?? 0
|
|
81
|
+
const diff = size - base
|
|
82
|
+
const pct = base > 0 ? ((diff / base) * 100).toFixed(1) : '—'
|
|
83
|
+
return {size, diff, pct}
|
|
84
|
+
})
|
|
85
|
+
return {mod, cells}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const totals = results.map((r) => {
|
|
89
|
+
const total = allModules.reduce((sum, mod) => sum + (r.modules.get(mod) ?? 0), 0)
|
|
90
|
+
const baseTotal = allModules.reduce((sum, mod) => sum + (baseline.get(mod) ?? 0), 0)
|
|
91
|
+
const diff = total - baseTotal
|
|
92
|
+
const pct = baseTotal > 0 ? ((diff / baseTotal) * 100).toFixed(1) : '—'
|
|
93
|
+
return {total, diff, pct}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const configHeaders = results.map((r) => `<th>${r.name}</th>`).join('\n ')
|
|
97
|
+
|
|
98
|
+
const tableRows = rows
|
|
99
|
+
.map(({mod, cells}) => {
|
|
100
|
+
const tds = cells
|
|
101
|
+
.map(({size, diff, pct}, i) => {
|
|
102
|
+
if (i === 0) return `<td class="num">${size}</td>`
|
|
103
|
+
const cls = diff < 0 ? 'better' : diff > 0 ? 'worse' : 'same'
|
|
104
|
+
const sign = diff > 0 ? '+' : ''
|
|
105
|
+
return `<td class="num ${cls}">${size} <span class="diff">(${sign}${diff}, ${sign}${pct}%)</span></td>`
|
|
106
|
+
})
|
|
107
|
+
.join('\n ')
|
|
108
|
+
return ` <tr>
|
|
109
|
+
<td class="mod">${mod}</td>
|
|
110
|
+
${tds}
|
|
111
|
+
</tr>`
|
|
112
|
+
})
|
|
113
|
+
.join('\n')
|
|
114
|
+
|
|
115
|
+
const totalRow = totals
|
|
116
|
+
.map(({total, diff, pct}, i) => {
|
|
117
|
+
if (i === 0) return `<td class="num"><strong>${total}</strong></td>`
|
|
118
|
+
const cls = diff < 0 ? 'better' : diff > 0 ? 'worse' : 'same'
|
|
119
|
+
const sign = diff > 0 ? '+' : ''
|
|
120
|
+
return `<td class="num ${cls}"><strong>${total}</strong> <span class="diff">(${sign}${diff}, ${sign}${pct}%)</span></td>`
|
|
121
|
+
})
|
|
122
|
+
.join('\n ')
|
|
123
|
+
|
|
124
|
+
return `<!DOCTYPE html>
|
|
125
|
+
<html lang="en">
|
|
126
|
+
<head>
|
|
127
|
+
<meta charset="utf-8">
|
|
128
|
+
<title>Minifier Comparison — mikrojs firmware runtime modules</title>
|
|
129
|
+
<style>
|
|
130
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
131
|
+
body { font-family: system-ui, -apple-system, sans-serif; padding: 2rem; background: #f8f9fa; color: #1a1a1a; }
|
|
132
|
+
h1 { font-size: 1.4rem; margin-bottom: 0.25rem; }
|
|
133
|
+
.subtitle { color: #666; margin-bottom: 1.5rem; font-size: 0.9rem; }
|
|
134
|
+
table { border-collapse: collapse; width: 100%; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
135
|
+
th { background: #1a1a1a; color: white; padding: 0.6rem 1rem; text-align: left; font-weight: 500; font-size: 0.85rem; }
|
|
136
|
+
td { padding: 0.45rem 1rem; border-bottom: 1px solid #eee; font-size: 0.85rem; }
|
|
137
|
+
tr:last-child td { border-bottom: none; }
|
|
138
|
+
tr:hover td { background: #f5f5f5; }
|
|
139
|
+
.mod { font-family: ui-monospace, monospace; font-size: 0.8rem; }
|
|
140
|
+
.num { text-align: right; font-family: ui-monospace, monospace; font-size: 0.8rem; white-space: nowrap; }
|
|
141
|
+
.diff { font-size: 0.75rem; opacity: 0.7; }
|
|
142
|
+
.better { color: #16a34a; }
|
|
143
|
+
.worse { color: #dc2626; }
|
|
144
|
+
.same { color: #666; }
|
|
145
|
+
.total-row td { border-top: 2px solid #1a1a1a; background: #f9fafb; }
|
|
146
|
+
.legend { margin-top: 1rem; font-size: 0.8rem; color: #666; }
|
|
147
|
+
.legend span { margin-right: 1.5rem; }
|
|
148
|
+
.timestamp { margin-top: 0.5rem; font-size: 0.75rem; color: #999; }
|
|
149
|
+
</style>
|
|
150
|
+
</head>
|
|
151
|
+
<body>
|
|
152
|
+
<h1>Minifier Comparison</h1>
|
|
153
|
+
<p class="subtitle">Bundle sizes (bytes) for mikrojs firmware runtime modules. Diff relative to esbuild baseline.</p>
|
|
154
|
+
<table>
|
|
155
|
+
<thead>
|
|
156
|
+
<tr>
|
|
157
|
+
<th>Module</th>
|
|
158
|
+
${configHeaders}
|
|
159
|
+
</tr>
|
|
160
|
+
</thead>
|
|
161
|
+
<tbody>
|
|
162
|
+
${tableRows}
|
|
163
|
+
<tr class="total-row">
|
|
164
|
+
<td class="mod"><strong>Total</strong></td>
|
|
165
|
+
${totalRow}
|
|
166
|
+
</tr>
|
|
167
|
+
</tbody>
|
|
168
|
+
</table>
|
|
169
|
+
<p class="legend">
|
|
170
|
+
<span class="better">Green = smaller than esbuild</span>
|
|
171
|
+
<span class="worse">Red = larger than esbuild</span>
|
|
172
|
+
<span class="same">Gray = same size</span>
|
|
173
|
+
</p>
|
|
174
|
+
<p class="timestamp">Generated ${new Date().toISOString()}</p>
|
|
175
|
+
</body>
|
|
176
|
+
</html>`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const html = generateHtml()
|
|
180
|
+
const outPath = join(ROOT, 'compare-minifiers.html')
|
|
181
|
+
writeFileSync(outPath, html)
|
|
182
|
+
// eslint-disable-next-line no-console
|
|
183
|
+
console.log(`\nReport written to ${outPath}`)
|
|
184
|
+
|
|
185
|
+
if (process.argv.includes('--open')) {
|
|
186
|
+
const {platform} = await import('node:os')
|
|
187
|
+
const cmd = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open'
|
|
188
|
+
execSync(`${cmd} ${outPath}`)
|
|
189
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Compile a bundled JS module to a C bytecode header using qjsc.
|
|
3
|
+
#
|
|
4
|
+
# Usage: compile-bytecode.sh <qjsc> <input.js> <output.h> <module_name> <symbol_name>
|
|
5
|
+
#
|
|
6
|
+
# Reads <input>.externals for external module declarations.
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
QJSC="$1"
|
|
11
|
+
INPUT="$2"
|
|
12
|
+
OUTPUT="$3"
|
|
13
|
+
MODULE_NAME="$4"
|
|
14
|
+
SYMBOL_NAME="$5"
|
|
15
|
+
|
|
16
|
+
EXTERNALS_FILE="${INPUT%.js}.externals"
|
|
17
|
+
|
|
18
|
+
# Build -M flags for external modules
|
|
19
|
+
M_FLAGS=""
|
|
20
|
+
if [ -f "$EXTERNALS_FILE" ]; then
|
|
21
|
+
while IFS= read -r ext || [ -n "$ext" ]; do
|
|
22
|
+
[ -n "$ext" ] && M_FLAGS="$M_FLAGS -M $ext"
|
|
23
|
+
done < "$EXTERNALS_FILE"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Compile with qjsc: ES module, strip source, custom name and script name.
|
|
27
|
+
#
|
|
28
|
+
# We use -s (strip source), NOT -ss (strip source + debug info). Stripping
|
|
29
|
+
# debug info via -ss drops b->filename from every function bytecode
|
|
30
|
+
# (quickjs.c writes filename inside `if (s->allow_debug)` — see
|
|
31
|
+
# JS_WriteFunctionBytecode). Once filename is gone, JS_GetScriptOrModuleName
|
|
32
|
+
# returns JS_ATOM_NULL for any function in the deserialized module, and
|
|
33
|
+
# js_import_meta fails with "import.meta not supported in this context" —
|
|
34
|
+
# so any runtime module that reads `import.meta.*` (e.g. mikrojs/env
|
|
35
|
+
# accessing import.meta.env) breaks at runtime. The ~1 KB heap saving
|
|
36
|
+
# from stripping debug isn't worth crippling a spec-standard module API.
|
|
37
|
+
# shellcheck disable=SC2086
|
|
38
|
+
"$QJSC" -m -s -N "$SYMBOL_NAME" -n "$MODULE_NAME" $M_FLAGS -o "$OUTPUT" "$INPUT"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {copyFileSync, existsSync, mkdirSync} from 'node:fs'
|
|
3
|
+
import {dirname, resolve} from 'node:path'
|
|
4
|
+
import {fileURLToPath} from 'node:url'
|
|
5
|
+
|
|
6
|
+
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
7
|
+
const source = resolve(pkgRoot, 'addon', 'build', 'Release', 'mikrojs_node.node')
|
|
8
|
+
|
|
9
|
+
if (!existsSync(source)) {
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
console.error(`No native binary at ${source}. Run 'cmake-js compile --directory addon' first.`)
|
|
12
|
+
process.exit(1)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const targetDir = resolve(pkgRoot, 'prebuilds', `${process.platform}-${process.arch}`)
|
|
16
|
+
mkdirSync(targetDir, {recursive: true})
|
|
17
|
+
const target = resolve(targetDir, 'mikrojs.napi.node')
|
|
18
|
+
copyFileSync(source, target)
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.log(`Copied → ${target}`)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import {execFileSync, spawnSync} from 'node:child_process'
|
|
3
|
+
import {createHash} from 'node:crypto'
|
|
4
|
+
import {readFileSync, writeFileSync} from 'node:fs'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a compact symbol map from an ELF for offline panic-trace decoding.
|
|
8
|
+
*
|
|
9
|
+
* Toolchain-agnostic: takes a binutils prefix (e.g. "riscv32-esp-elf-",
|
|
10
|
+
* "xtensa-esp32-elf-", "arm-none-eabi-") so the same script works for any
|
|
11
|
+
* target a future port (RP2350, etc.) might add.
|
|
12
|
+
*
|
|
13
|
+
* Usage: node generate-symbol-map.js <elf> <out.json> <toolchain-prefix> [chip]
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const [, , elfPath, outPath, toolchainPrefix, chip = ''] = process.argv
|
|
17
|
+
|
|
18
|
+
if (!elfPath || !outPath || !toolchainPrefix) {
|
|
19
|
+
console.error('Usage: node generate-symbol-map.js <elf> <out.json> <toolchain-prefix> [chip]')
|
|
20
|
+
process.exit(1)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const nm = `${toolchainPrefix}nm`
|
|
24
|
+
const addr2line = `${toolchainPrefix}addr2line`
|
|
25
|
+
|
|
26
|
+
const elfBuf = readFileSync(elfPath)
|
|
27
|
+
const elfHash = createHash('sha256').update(elfBuf).digest('hex')
|
|
28
|
+
|
|
29
|
+
// nm -S --defined-only --print-size: "ADDR SIZE T name"
|
|
30
|
+
// Symbols without a size (size = 0) are skipped — they're typically labels,
|
|
31
|
+
// not real functions, and would only pollute the lookup table.
|
|
32
|
+
const nmOut = execFileSync(nm, ['-S', '--defined-only', '--print-size', elfPath], {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const rawFuncs = []
|
|
38
|
+
const addrs = []
|
|
39
|
+
let codeMin = Infinity
|
|
40
|
+
let codeMax = 0
|
|
41
|
+
|
|
42
|
+
for (const line of nmOut.split('\n')) {
|
|
43
|
+
const m = line.match(/^([0-9a-f]+)\s+([0-9a-f]+)\s+([tTwW])\s+(.+)$/)
|
|
44
|
+
if (!m) continue
|
|
45
|
+
const start = parseInt(m[1], 16)
|
|
46
|
+
const size = parseInt(m[2], 16)
|
|
47
|
+
if (size === 0) continue
|
|
48
|
+
rawFuncs.push({start, size, mangled: m[4]})
|
|
49
|
+
addrs.push('0x' + start.toString(16))
|
|
50
|
+
if (start < codeMin) codeMin = start
|
|
51
|
+
if (start + size > codeMax) codeMax = start + size
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (rawFuncs.length === 0) {
|
|
55
|
+
console.error('No function symbols found in ELF — nothing to do.')
|
|
56
|
+
process.exit(1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Resolve every function entry's file:line in a single addr2line invocation.
|
|
60
|
+
// `-f` = function name, `-C` = demangle, `-s` = base names only (we keep
|
|
61
|
+
// full paths from the file column instead — `-s` would shorten file paths).
|
|
62
|
+
// Actually we want full paths: drop -s.
|
|
63
|
+
const a2l = spawnSync(addr2line, ['-f', '-C', '-e', elfPath], {
|
|
64
|
+
input: addrs.join('\n') + '\n',
|
|
65
|
+
encoding: 'utf8',
|
|
66
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (a2l.status !== 0) {
|
|
70
|
+
console.error(`addr2line failed: ${a2l.stderr}`)
|
|
71
|
+
process.exit(1)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// addr2line output: two lines per input address — function name, then file:line
|
|
75
|
+
const a2lLines = a2l.stdout.split('\n')
|
|
76
|
+
|
|
77
|
+
const fileIdx = new Map()
|
|
78
|
+
const files = []
|
|
79
|
+
const nameIdx = new Map()
|
|
80
|
+
const names = []
|
|
81
|
+
|
|
82
|
+
function internFile(f) {
|
|
83
|
+
let i = fileIdx.get(f)
|
|
84
|
+
if (i === undefined) {
|
|
85
|
+
i = files.length
|
|
86
|
+
files.push(f)
|
|
87
|
+
fileIdx.set(f, i)
|
|
88
|
+
}
|
|
89
|
+
return i
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function internName(n) {
|
|
93
|
+
let i = nameIdx.get(n)
|
|
94
|
+
if (i === undefined) {
|
|
95
|
+
i = names.length
|
|
96
|
+
names.push(n)
|
|
97
|
+
nameIdx.set(n, i)
|
|
98
|
+
}
|
|
99
|
+
return i
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const funcs = [] // [start, size, nameIdx, fileIdx, line]
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < rawFuncs.length; i++) {
|
|
105
|
+
const fn = rawFuncs[i]
|
|
106
|
+
const fname = (a2lLines[i * 2] || '').trim()
|
|
107
|
+
const floc = (a2lLines[i * 2 + 1] || '').trim()
|
|
108
|
+
|
|
109
|
+
// addr2line emits "??" when DWARF info is missing.
|
|
110
|
+
const displayName = fname && fname !== '??' ? fname : fn.mangled
|
|
111
|
+
let fIdx = -1
|
|
112
|
+
let lineNo = 0
|
|
113
|
+
if (floc && floc !== '??:?' && floc !== '??:0') {
|
|
114
|
+
// "path/to/file.cpp:123" (or "...:123 (discriminator N)")
|
|
115
|
+
const colon = floc.lastIndexOf(':')
|
|
116
|
+
if (colon > 0) {
|
|
117
|
+
const file = floc.slice(0, colon)
|
|
118
|
+
const lineStr = floc.slice(colon + 1).split(/\s/)[0]
|
|
119
|
+
const parsed = parseInt(lineStr, 10)
|
|
120
|
+
if (file && file !== '??' && Number.isFinite(parsed)) {
|
|
121
|
+
fIdx = internFile(file)
|
|
122
|
+
lineNo = parsed
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
funcs.push([fn.start, fn.size, internName(displayName), fIdx, lineNo])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
funcs.sort((a, b) => a[0] - b[0])
|
|
130
|
+
|
|
131
|
+
const out = {
|
|
132
|
+
v: 1,
|
|
133
|
+
chip,
|
|
134
|
+
elfHash,
|
|
135
|
+
codeStart: codeMin,
|
|
136
|
+
codeEnd: codeMax,
|
|
137
|
+
files,
|
|
138
|
+
names,
|
|
139
|
+
funcs,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
writeFileSync(outPath, JSON.stringify(out))
|
|
143
|
+
console.log(
|
|
144
|
+
`Wrote ${outPath}: ${funcs.length} funcs, ${files.length} files, ${names.length} names, ` +
|
|
145
|
+
`code range 0x${codeMin.toString(16)}-0x${codeMax.toString(16)}`,
|
|
146
|
+
)
|
package/src/builtins.cpp
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
|
|
2
|
+
#include <cinttypes>
|
|
3
|
+
#include <cstdio>
|
|
4
|
+
|
|
5
|
+
#include "mikrojs/platform.h"
|
|
6
|
+
#include "mikrojs/private.h"
|
|
7
|
+
#include "mikrojs/utils.h"
|
|
8
|
+
|
|
9
|
+
typedef struct {
|
|
10
|
+
const char* name;
|
|
11
|
+
const uint8_t* data;
|
|
12
|
+
uint32_t data_size;
|
|
13
|
+
} mik_builtin_t;
|
|
14
|
+
|
|
15
|
+
/* Generated by mikrojs_generate_bytecode() in CMake — includes + lookup table.
|
|
16
|
+
* Adding a new module only requires updating the MODULES list in CMakeLists.txt. */
|
|
17
|
+
#include "gen/mikrojs_builtins_table.h"
|
|
18
|
+
#define builtins mikrojs_builtins
|
|
19
|
+
|
|
20
|
+
/* External builtins registered by board/driver packages via MIK_REGISTER_BUILTIN() */
|
|
21
|
+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
22
|
+
mik_ext_builtin_t* mik__ext_builtin_head = nullptr;
|
|
23
|
+
|
|
24
|
+
/* Deserialize bytecode into a module. Shared by core and external builtins. */
|
|
25
|
+
static JSModuleDef* mik__deserialize_builtin(JSContext* ctx, const char* name,
|
|
26
|
+
const uint8_t* data, uint32_t data_size) {
|
|
27
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
28
|
+
|
|
29
|
+
platform->log(MIK_LOG_DEBUG, "mikrojs", "Loading builtin '%s' (%u bytes, bc_version=%u, data_ptr=%p)", name,
|
|
30
|
+
data_size, data_size > 0 ? data[0] : 0, (void*)data);
|
|
31
|
+
|
|
32
|
+
JSValue obj = JS_ReadObject(ctx, data, data_size, JS_READ_OBJ_BYTECODE);
|
|
33
|
+
|
|
34
|
+
if (JS_IsException(obj)) {
|
|
35
|
+
platform->log(MIK_LOG_ERROR, "mikrojs", "Failed to deserialize bytecode for builtin '%s' (%u bytes)", name,
|
|
36
|
+
data_size);
|
|
37
|
+
/* Leave the JS exception on ctx so the caller can propagate the
|
|
38
|
+
* real error (e.g. "Maximum call stack size exceeded") instead of
|
|
39
|
+
* replacing it with a generic "not available" message. */
|
|
40
|
+
return NULL;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (JS_VALUE_GET_TAG(obj) != JS_TAG_MODULE) {
|
|
44
|
+
platform->log(MIK_LOG_ERROR, "mikrojs", "Builtin '%s' bytecode is not a module (tag=%d)", name,
|
|
45
|
+
JS_VALUE_GET_TAG(obj));
|
|
46
|
+
JS_FreeValue(ctx, obj);
|
|
47
|
+
return NULL;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Initialize import.meta (url, env, etc.) for the builtin module */
|
|
51
|
+
js_module_set_import_meta(ctx, obj, false, false);
|
|
52
|
+
|
|
53
|
+
/* Resolve imports so the module loader is called for dependencies */
|
|
54
|
+
if (JS_ResolveModule(ctx, obj) < 0) {
|
|
55
|
+
platform->log(MIK_LOG_ERROR, "mikrojs", "Failed to resolve imports for builtin '%s'", name);
|
|
56
|
+
JS_FreeValue(ctx, obj);
|
|
57
|
+
return NULL;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
JSModuleDef* m = static_cast<JSModuleDef*>(JS_VALUE_GET_PTR(obj));
|
|
61
|
+
JS_FreeValue(ctx, obj);
|
|
62
|
+
|
|
63
|
+
return m;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
JSModuleDef* mik__load_builtin(JSContext* ctx, const char* name) {
|
|
67
|
+
/* Check core builtins table */
|
|
68
|
+
for (const mik_builtin_t* p = builtins; p->name != NULL; ++p) {
|
|
69
|
+
if (strcmp(p->name, name) == 0) {
|
|
70
|
+
return mik__deserialize_builtin(ctx, name, p->data, p->data_size);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Check external builtins (registered by board/driver packages) */
|
|
75
|
+
for (mik_ext_builtin_t* p = mik__ext_builtin_head; p != nullptr; p = p->next) {
|
|
76
|
+
if (strcmp(p->name, name) == 0) {
|
|
77
|
+
return mik__deserialize_builtin(ctx, name, p->data, p->data_size);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return NULL;
|
|
82
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Provide non-inline definitions of cutils.h functions for C++ linkage.
|
|
2
|
+
*
|
|
3
|
+
* cutils.h declares these as static inline, so they have internal linkage
|
|
4
|
+
* and aren't visible to C++ translation units that use cutils_wrap.h
|
|
5
|
+
* (declarations only). This file provides real external definitions. */
|
|
6
|
+
#include <cutils.h>
|
|
7
|
+
|
|
8
|
+
int mik__dbuf_put(DynBuf *s, const void *data, size_t len) {
|
|
9
|
+
return dbuf_put(s, data, len);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
int mik__dbuf_putc(DynBuf *s, uint8_t val) {
|
|
13
|
+
return dbuf_putc(s, val);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
int mik__dbuf_putstr(DynBuf *s, const char *str) {
|
|
17
|
+
return dbuf_putstr(s, str);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
void mik__dbuf_free(DynBuf *s) {
|
|
21
|
+
dbuf_free(s);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void mik__dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) {
|
|
25
|
+
dbuf_init2(s, opaque, realloc_func);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
int mik__js_has_suffix(const char *str, const char *suffix) {
|
|
29
|
+
return js__has_suffix(str, suffix);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void mik__js_pstrcpy(char *buf, int buf_size, const char *str) {
|
|
33
|
+
js__pstrcpy(buf, buf_size, str);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
char *mik__js_pstrcat(char *buf, int buf_size, const char *s) {
|
|
37
|
+
return js__pstrcat(buf, buf_size, s);
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#include <quickjs.h>
|
|
2
|
+
|
|
3
|
+
#include "mikrojs/private.h"
|
|
4
|
+
#include "mikrojs/utils.h"
|
|
5
|
+
|
|
6
|
+
int mik__eval_bytecode(JSContext* ctx, const uint8_t* buf, size_t buf_len, bool check_promise) {
|
|
7
|
+
JSValue obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE);
|
|
8
|
+
|
|
9
|
+
if (JS_IsException(obj)) {
|
|
10
|
+
mik_dump_error(ctx);
|
|
11
|
+
return -1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) {
|
|
15
|
+
if (JS_ResolveModule(ctx, obj) < 0) {
|
|
16
|
+
JS_FreeValue(ctx, obj);
|
|
17
|
+
mik_dump_error(ctx);
|
|
18
|
+
return -1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
JSValue val = JS_EvalFunction(ctx, obj);
|
|
23
|
+
if (JS_IsException(val)) {
|
|
24
|
+
mik_dump_error(ctx);
|
|
25
|
+
return -1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (check_promise) {
|
|
29
|
+
JSPromiseStateEnum promise_state = JS_PromiseState(ctx, val);
|
|
30
|
+
if (promise_state != (JSPromiseStateEnum)-1) {
|
|
31
|
+
if (promise_state == JS_PROMISE_REJECTED) {
|
|
32
|
+
JSValue res = JS_PromiseResult(ctx, val);
|
|
33
|
+
JS_FreeValue(ctx, res);
|
|
34
|
+
JS_FreeValue(ctx, val);
|
|
35
|
+
return -1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
JS_FreeValue(ctx, val);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|