Skip to content

Commit 44c9442

Browse files
feat: allow setting rust min stack size
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
1 parent c4d72cd commit 44c9442

2 files changed

Lines changed: 71 additions & 23 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ The component iself can be executed in any component runtime, see the [example](
111111

112112
To enable AOT compilation, set the `enableAot: true` option to run [Weval][weval] ahead-of-time compilation.
113113

114+
AOT compilation can also be configured with the following options:
115+
116+
| Option | Type | Example | Description |
117+
|------------------------|-------------------------------------|-----------------|--------------------------------------------------------------------------|
118+
| `aotMinStackSizeBytes` | `nubmer | Number | bigint | BigInt` | `2_007_846_092` | The minimum stack size (via `RUST_MIN_STACK` to set when running `weval` |
119+
114120
[weval]: https://github.com/bytecodealliance/weval
115121

116122
### Custom `weval` binary

src/componentize.js

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { freemem } from "node:os";
12
import wizer from '@bytecodealliance/wizer';
23
import getWeval from '@bytecodealliance/weval';
34
import {
@@ -19,7 +20,7 @@ import { fileURLToPath } from 'node:url';
1920
import { stdout, stderr, exit, platform } from 'node:process';
2021
import { init as lexerInit, parse } from 'es-module-lexer';
2122
export const { version } = JSON.parse(
22-
await readFile(new URL('../package.json', import.meta.url), 'utf8')
23+
await readFile(new URL('../package.json', import.meta.url), 'utf8'),
2324
);
2425
const isWindows = platform === 'win32';
2526
const DEBUG_BINDINGS = false;
@@ -32,6 +33,24 @@ function maybeWindowsPath(path) {
3233
return '//?/' + resolve(path).replace(/\\/g, '/');
3334
}
3435

36+
/**
37+
* Check whether a value is numeric (including BigInt)
38+
*
39+
* @param {any} n
40+
* @returns {boolean} whether the value is numeric
41+
*/
42+
function isNumeric(n) {
43+
switch (typeof n) {
44+
case 'bigint':
45+
case 'number':
46+
return true;
47+
case 'object':
48+
return n.constructor == BigInt || n.constructor == Number;
49+
default:
50+
return false;
51+
}
52+
}
53+
3554
export async function componentize(jsSource, witWorld, opts) {
3655
if (typeof witWorld === 'object') {
3756
opts = witWorld;
@@ -48,7 +67,7 @@ export async function componentize(jsSource, witWorld, opts) {
4867
debugBuild = false,
4968
runtimeArgs,
5069
aotCache = fileURLToPath(
51-
new URL(`../lib/starlingmonkey_ics.wevalcache`, import.meta.url)
70+
new URL(`../lib/starlingmonkey_ics.wevalcache`, import.meta.url),
5271
),
5372
} = opts;
5473

@@ -58,11 +77,9 @@ export async function componentize(jsSource, witWorld, opts) {
5877
new URL(
5978
opts.enableAot
6079
? `../lib/starlingmonkey_embedding_weval.wasm`
61-
: `../lib/starlingmonkey_embedding${
62-
debugBuild ? '.debug' : ''
63-
}.wasm`,
64-
import.meta.url
65-
)
80+
: `../lib/starlingmonkey_embedding${debugBuild ? '.debug' : ''}.wasm`,
81+
import.meta.url,
82+
),
6683
);
6784

6885
await lexerInit;
@@ -108,7 +125,7 @@ export async function componentize(jsSource, witWorld, opts) {
108125
guestImports,
109126
guestExports,
110127
features,
111-
false
128+
false,
112129
);
113130

114131
if (DEBUG_BINDINGS) {
@@ -119,7 +136,7 @@ export async function componentize(jsSource, witWorld, opts) {
119136
jsBindings
120137
.split('\n')
121138
.map((ln, idx) => `${(idx + 1).toString().padStart(4, ' ')} | ${ln}`)
122-
.join('\n')
139+
.join('\n'),
123140
);
124141
console.log('--- JS Imports ---');
125142
console.log(imports);
@@ -133,7 +150,7 @@ export async function componentize(jsSource, witWorld, opts) {
133150
createHash('sha256')
134151
.update(Math.random().toString())
135152
.digest('hex')
136-
.slice(0, 12)
153+
.slice(0, 12),
137154
);
138155
await mkdir(tmpDir);
139156

@@ -158,8 +175,8 @@ export async function componentize(jsSource, witWorld, opts) {
158175
]
159176
.map((impt) => `'${impt}'`)
160177
.join(
161-
', '
162-
)}.\nMake sure to use a bundler for JS dependencies such as esbuild or RollupJS.`
178+
', ',
179+
)}.\nMake sure to use a bundler for JS dependencies such as esbuild or RollupJS.`,
163180
);
164181
}
165182
const specifier = jsSource.slice(jsImpt.s, jsImpt.e);
@@ -186,8 +203,8 @@ export async function componentize(jsSource, witWorld, opts) {
186203
source,
187204
]),
188205
].map(async ([sourceName, source]) =>
189-
writeFile(join(sourceDir, sourceName), source)
190-
)
206+
writeFile(join(sourceDir, sourceName), source),
207+
),
191208
);
192209

193210
let hostenv = {};
@@ -224,7 +241,9 @@ export async function componentize(jsSource, witWorld, opts) {
224241
console.log(env);
225242
}
226243

227-
let sourcePath = maybeWindowsPath(join(sourceDir, sourceName.slice(0, -3) + '.bindings.js'));
244+
let sourcePath = maybeWindowsPath(
245+
join(sourceDir, sourceName.slice(0, -3) + '.bindings.js'),
246+
);
228247
runtimeArgs = runtimeArgs ? `${runtimeArgs} ${sourcePath}` : sourcePath;
229248

230249
if (opts.enableAot) {
@@ -236,6 +255,18 @@ export async function componentize(jsSource, witWorld, opts) {
236255
wevalBin = await getWeval();
237256
}
238257

258+
// Set the min stack size, if one was provided
259+
if (opts.aotMinStackSizeBytes) {
260+
if (!isNumeric(opts.aotMinStackSizeBytes)) {
261+
throw new TypeError(
262+
`aotMinStackSizeBytes must be a numeric value, received [${opts.aotMinStackSizeBytes}] (type ${typeof opts.aotMinStackSizeBytes})`,
263+
);
264+
}
265+
env.RUST_MIN_STACK = opts.aotMinStackSizeBytes;
266+
} else {
267+
env.RUST_MIN_STACK = defaultMinStackSize();
268+
}
269+
239270
try {
240271
let wevalProcess = spawnSync(
241272
wevalBin,
@@ -255,7 +286,7 @@ export async function componentize(jsSource, witWorld, opts) {
255286
input: runtimeArgs,
256287
shell: true,
257288
encoding: 'utf-8',
258-
}
289+
},
259290
);
260291
if (wevalProcess.status !== 0)
261292
throw new Error('Wevaling failed to complete');
@@ -290,7 +321,7 @@ export async function componentize(jsSource, witWorld, opts) {
290321
input: runtimeArgs,
291322
shell: true,
292323
encoding: 'utf-8',
293-
}
324+
},
294325
);
295326
if (wizerProcess.status !== 0)
296327
throw new Error('Wizering failed to complete');
@@ -326,7 +357,7 @@ export async function componentize(jsSource, witWorld, opts) {
326357
async function initWasm(bin) {
327358
const eep = (name) => () => {
328359
throw new Error(
329-
`Internal error: unexpected call to "${name}" during Wasm verification`
360+
`Internal error: unexpected call to "${name}" during Wasm verification`,
330361
);
331362
};
332363

@@ -346,7 +377,7 @@ export async function componentize(jsSource, witWorld, opts) {
346377
const bufPtr = mem.getUint32(iovs + i * 8, true);
347378
const bufLen = mem.getUint32(iovs + 4 + i * 8, true);
348379
stderr += new TextDecoder().decode(
349-
new Uint8Array(exports.memory.buffer, bufPtr, bufLen)
380+
new Uint8Array(exports.memory.buffer, bufPtr, bufLen),
350381
);
351382
written += bufLen;
352383
}
@@ -372,7 +403,7 @@ export async function componentize(jsSource, witWorld, opts) {
372403

373404
// convert CABI import conventiosn to ESM import conventions
374405
imports = imports.map(([specifier, impt]) =>
375-
specifier === '$root' ? [impt, 'default'] : [specifier, impt]
406+
specifier === '$root' ? [impt, 'default'] : [specifier, impt],
376407
);
377408

378409
const INIT_OK = 0;
@@ -409,7 +440,7 @@ export async function componentize(jsSource, witWorld, opts) {
409440
features,
410441
witWorld,
411442
maybeWindowsPath(witPath),
412-
worldName
443+
worldName,
413444
);
414445

415446
if (DEBUG_BINARY) {
@@ -422,16 +453,27 @@ export async function componentize(jsSource, witWorld, opts) {
422453
Object.entries({
423454
wasi_snapshot_preview1: await readFile(preview2Adapter),
424455
}),
425-
false
456+
false,
426457
),
427458
Object.entries({
428459
language: [['JavaScript', '']],
429460
'processed-by': [['ComponentizeJS', version]],
430-
})
461+
}),
431462
);
432463

433464
return {
434465
component,
435466
imports,
436467
};
437468
}
469+
470+
/**
471+
* Calculate the min stack size depending on free memory
472+
*
473+
* @param {number} freeMemoryBytes - Amount of free memory in the system, in bytes (if not provided, os.freemem() is used)
474+
* @returns {number} The minimum stack size that should be used as a default.
475+
*/
476+
function defaultMinStackSize(freeMemoryBytes) {
477+
freeMemoryBytes = freeMemoryBytes ?? freemem();
478+
return Math.max(8 * 1024 * 1024, Math.floor(freeMemoryBytes * 0.1));
479+
}

0 commit comments

Comments
 (0)