@pulse-compute/wasm-guest-link 0.0.0 → 1.0.0-beta.2
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/README.md +94 -2
- package/package.json +29 -7
- package/src/constants.js +159 -0
- package/src/contracts.js +1143 -0
- package/src/errors.js +76 -0
- package/src/files.js +149 -0
- package/src/index.js +33 -0
- package/src/inspect.js +510 -0
- package/src/materialize.js +424 -0
- package/src/memory-owner.js +29 -0
- package/src/pipeline.js +729 -0
- package/src/stage.js +578 -0
- package/src/toolchain.js +125 -0
package/src/inspect.js
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { TextDecoder } = require('node:util');
|
|
6
|
+
const { diagnosticCodes } = require('./constants.js');
|
|
7
|
+
const { fail, wrap } = require('./errors.js');
|
|
8
|
+
const { sha256 } = require('./files.js');
|
|
9
|
+
const { runTool } = require('./toolchain.js');
|
|
10
|
+
|
|
11
|
+
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
12
|
+
const typeNames = new Map([
|
|
13
|
+
[0x7f, 'i32'],
|
|
14
|
+
[0x7e, 'i64'],
|
|
15
|
+
[0x7d, 'f32'],
|
|
16
|
+
[0x7c, 'f64'],
|
|
17
|
+
[0x7b, 'v128'],
|
|
18
|
+
[0x70, 'funcref'],
|
|
19
|
+
[0x6f, 'externref']
|
|
20
|
+
]);
|
|
21
|
+
const exportKinds = ['function', 'table', 'memory', 'global'];
|
|
22
|
+
const sectionRanks = new Map([
|
|
23
|
+
[1, 1],
|
|
24
|
+
[2, 2],
|
|
25
|
+
[3, 3],
|
|
26
|
+
[4, 4],
|
|
27
|
+
[5, 5],
|
|
28
|
+
[6, 6],
|
|
29
|
+
[7, 7],
|
|
30
|
+
[8, 8],
|
|
31
|
+
[9, 9],
|
|
32
|
+
[12, 10],
|
|
33
|
+
[10, 11],
|
|
34
|
+
[11, 12]
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
class Reader {
|
|
38
|
+
constructor(bytes, label, start = 0, end = bytes.length) {
|
|
39
|
+
this.bytes = bytes;
|
|
40
|
+
this.label = label;
|
|
41
|
+
this.offset = start;
|
|
42
|
+
this.end = end;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ensure(length, context) {
|
|
46
|
+
if (this.offset + length > this.end) fail(diagnosticCodes.invalid, `${this.label} has truncated ${context}.`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
byte(context) {
|
|
50
|
+
this.ensure(1, context);
|
|
51
|
+
return this.bytes[this.offset++];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
take(length, context) {
|
|
55
|
+
this.ensure(length, context);
|
|
56
|
+
const value = this.bytes.subarray(this.offset, this.offset + length);
|
|
57
|
+
this.offset += length;
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
uleb(context, maximumBits = 32) {
|
|
62
|
+
let value = 0;
|
|
63
|
+
let shift = 0;
|
|
64
|
+
while (this.offset < this.end) {
|
|
65
|
+
const byte = this.byte(context);
|
|
66
|
+
value += (byte & 0x7f) * (2 ** shift);
|
|
67
|
+
if ((byte & 0x80) === 0) {
|
|
68
|
+
if (!Number.isSafeInteger(value)) fail(diagnosticCodes.invalid, `${this.label} ${context} exceeds safe integer range.`);
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
shift += 7;
|
|
72
|
+
if (shift > maximumBits + 7) fail(diagnosticCodes.invalid, `${this.label} ${context} ULEB is too large.`);
|
|
73
|
+
}
|
|
74
|
+
fail(diagnosticCodes.invalid, `${this.label} has truncated ${context}.`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
sleb(context, maximumBits = 32) {
|
|
78
|
+
let value = 0;
|
|
79
|
+
let shift = 0;
|
|
80
|
+
let byte;
|
|
81
|
+
do {
|
|
82
|
+
byte = this.byte(context);
|
|
83
|
+
value += (byte & 0x7f) * (2 ** shift);
|
|
84
|
+
shift += 7;
|
|
85
|
+
if (shift > maximumBits + 7) fail(diagnosticCodes.invalid, `${this.label} ${context} SLEB is too large.`);
|
|
86
|
+
} while ((byte & 0x80) !== 0);
|
|
87
|
+
if (shift < maximumBits && (byte & 0x40)) value -= 2 ** shift;
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
name(context) {
|
|
92
|
+
const length = this.uleb(`${context} length`);
|
|
93
|
+
try {
|
|
94
|
+
return utf8.decode(this.take(length, context));
|
|
95
|
+
} catch (error) {
|
|
96
|
+
fail(diagnosticCodes.invalid, `${this.label} ${context} is not valid UTF-8.`, { cause: error.message });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
vector(context, readEntry) {
|
|
101
|
+
const count = this.uleb(`${context} count`);
|
|
102
|
+
const result = [];
|
|
103
|
+
for (let index = 0; index < count; index += 1) result.push(readEntry(this, index));
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
done(context) {
|
|
108
|
+
if (this.offset !== this.end) fail(diagnosticCodes.invalid, `${this.label} ${context} was not fully decoded.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function valueType(reader, context) {
|
|
113
|
+
const byte = reader.byte(context);
|
|
114
|
+
const name = typeNames.get(byte);
|
|
115
|
+
if (!name) fail(diagnosticCodes.featureMismatch, `${reader.label} uses unsupported value type 0x${byte.toString(16)}.`);
|
|
116
|
+
return name;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function limits(reader, context) {
|
|
120
|
+
const flags = reader.uleb(`${context} flags`);
|
|
121
|
+
if (flags !== 0 && flags !== 1 && flags !== 3) {
|
|
122
|
+
fail(diagnosticCodes.featureMismatch, `${reader.label} uses unsupported memory/table limit flags ${flags}.`);
|
|
123
|
+
}
|
|
124
|
+
const minimumPages = reader.uleb(`${context} minimum`);
|
|
125
|
+
const maximumPages = flags & 1 ? reader.uleb(`${context} maximum`) : null;
|
|
126
|
+
return Object.freeze({
|
|
127
|
+
minimumPages,
|
|
128
|
+
maximumPages,
|
|
129
|
+
shared: Boolean(flags & 2)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function tableType(reader, context) {
|
|
134
|
+
const element = valueType(reader, `${context} element`);
|
|
135
|
+
return Object.freeze({ element, limits: limits(reader, context) });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function globalType(reader, context) {
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
type: valueType(reader, `${context} type`),
|
|
141
|
+
mutable: reader.byte(`${context} mutability`) === 1
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function initExpression(reader, context) {
|
|
146
|
+
const opcode = reader.byte(`${context} opcode`);
|
|
147
|
+
let instruction;
|
|
148
|
+
let value;
|
|
149
|
+
if (opcode === 0x41) {
|
|
150
|
+
instruction = 'i32.const';
|
|
151
|
+
value = reader.sleb(`${context} i32`, 32);
|
|
152
|
+
} else if (opcode === 0x42) {
|
|
153
|
+
instruction = 'i64.const';
|
|
154
|
+
value = reader.sleb(`${context} i64`, 64);
|
|
155
|
+
} else if (opcode === 0x43) {
|
|
156
|
+
instruction = 'f32.const';
|
|
157
|
+
value = reader.take(4, `${context} f32`).toString('hex');
|
|
158
|
+
} else if (opcode === 0x44) {
|
|
159
|
+
instruction = 'f64.const';
|
|
160
|
+
value = reader.take(8, `${context} f64`).toString('hex');
|
|
161
|
+
} else if (opcode === 0x23) {
|
|
162
|
+
instruction = 'global.get';
|
|
163
|
+
value = reader.uleb(`${context} global index`);
|
|
164
|
+
} else {
|
|
165
|
+
fail(diagnosticCodes.featureMismatch, `${reader.label} uses unsupported initializer opcode 0x${opcode.toString(16)}.`);
|
|
166
|
+
}
|
|
167
|
+
if (reader.byte(`${context} end`) !== 0x0b) fail(diagnosticCodes.invalid, `${reader.label} initializer must end immediately.`);
|
|
168
|
+
return Object.freeze({ instruction, value });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseModule(bytes, label) {
|
|
172
|
+
if (
|
|
173
|
+
bytes.length < 8
|
|
174
|
+
|| bytes.subarray(0, 4).toString('hex') !== '0061736d'
|
|
175
|
+
|| bytes.subarray(4, 8).toString('hex') !== '01000000'
|
|
176
|
+
) {
|
|
177
|
+
fail(diagnosticCodes.invalid, `${label} is not a core WebAssembly v1 module.`);
|
|
178
|
+
}
|
|
179
|
+
const root = new Reader(bytes, label, 8);
|
|
180
|
+
const sections = new Map();
|
|
181
|
+
let lastSectionRank = 0;
|
|
182
|
+
while (root.offset < root.end) {
|
|
183
|
+
const id = root.byte('section id');
|
|
184
|
+
const size = root.uleb('section size');
|
|
185
|
+
root.ensure(size, 'section payload');
|
|
186
|
+
const start = root.offset;
|
|
187
|
+
root.offset += size;
|
|
188
|
+
if (id === 0) continue;
|
|
189
|
+
const rank = sectionRanks.get(id);
|
|
190
|
+
if (!rank) fail(diagnosticCodes.featureMismatch, `${label} uses unsupported section ${id}.`);
|
|
191
|
+
if (rank < lastSectionRank || sections.has(id)) fail(diagnosticCodes.invalid, `${label} has duplicate or out-of-order section ${id}.`);
|
|
192
|
+
lastSectionRank = rank;
|
|
193
|
+
sections.set(id, new Reader(bytes, label, start, start + size));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const types = [];
|
|
197
|
+
if (sections.has(1)) {
|
|
198
|
+
const reader = sections.get(1);
|
|
199
|
+
types.push(...reader.vector('types', (entry, index) => {
|
|
200
|
+
if (entry.byte(`type ${index} form`) !== 0x60) fail(diagnosticCodes.featureMismatch, `${label} type ${index} is not a function type.`);
|
|
201
|
+
return Object.freeze({
|
|
202
|
+
parameters: Object.freeze(entry.vector(`type ${index} parameters`, (item, itemIndex) => valueType(item, `type ${index} parameter ${itemIndex}`))),
|
|
203
|
+
results: Object.freeze(entry.vector(`type ${index} results`, (item, itemIndex) => valueType(item, `type ${index} result ${itemIndex}`)))
|
|
204
|
+
});
|
|
205
|
+
}));
|
|
206
|
+
reader.done('type section');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const imports = [];
|
|
210
|
+
const importedFunctionTypes = [];
|
|
211
|
+
const importedMemories = [];
|
|
212
|
+
let importedTables = 0;
|
|
213
|
+
let importedGlobals = 0;
|
|
214
|
+
if (sections.has(2)) {
|
|
215
|
+
const reader = sections.get(2);
|
|
216
|
+
imports.push(...reader.vector('imports', (entry, index) => {
|
|
217
|
+
const module = entry.name(`import ${index} module`);
|
|
218
|
+
const name = entry.name(`import ${index} name`);
|
|
219
|
+
const kind = entry.byte(`import ${index} kind`);
|
|
220
|
+
if (kind === 0) {
|
|
221
|
+
const typeIndex = entry.uleb(`import ${index} function type`);
|
|
222
|
+
const type = types[typeIndex];
|
|
223
|
+
if (!type) fail(diagnosticCodes.invalid, `${label} import ${module}.${name} references missing type ${typeIndex}.`);
|
|
224
|
+
importedFunctionTypes.push(typeIndex);
|
|
225
|
+
return Object.freeze({ module, name, kind: 'function', type });
|
|
226
|
+
}
|
|
227
|
+
if (kind === 1) {
|
|
228
|
+
importedTables += 1;
|
|
229
|
+
return Object.freeze({ module, name, kind: 'table', type: tableType(entry, `import ${index} table`) });
|
|
230
|
+
}
|
|
231
|
+
if (kind === 2) {
|
|
232
|
+
const type = limits(entry, `import ${index} memory`);
|
|
233
|
+
importedMemories.push(type);
|
|
234
|
+
return Object.freeze({ module, name, kind: 'memory', type });
|
|
235
|
+
}
|
|
236
|
+
if (kind === 3) {
|
|
237
|
+
importedGlobals += 1;
|
|
238
|
+
return Object.freeze({ module, name, kind: 'global', type: globalType(entry, `import ${index} global`) });
|
|
239
|
+
}
|
|
240
|
+
fail(diagnosticCodes.featureMismatch, `${label} import ${module}.${name} uses unsupported kind ${kind}.`);
|
|
241
|
+
}));
|
|
242
|
+
reader.done('import section');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const functionTypeIndexes = [];
|
|
246
|
+
if (sections.has(3)) {
|
|
247
|
+
const reader = sections.get(3);
|
|
248
|
+
functionTypeIndexes.push(...reader.vector('functions', (entry, index) => {
|
|
249
|
+
const typeIndex = entry.uleb(`function ${index} type`);
|
|
250
|
+
if (!types[typeIndex]) fail(diagnosticCodes.invalid, `${label} function ${index} references missing type ${typeIndex}.`);
|
|
251
|
+
return typeIndex;
|
|
252
|
+
}));
|
|
253
|
+
reader.done('function section');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let definedTables = 0;
|
|
257
|
+
if (sections.has(4)) {
|
|
258
|
+
const reader = sections.get(4);
|
|
259
|
+
definedTables = reader.vector('tables', (entry, index) => tableType(entry, `table ${index}`)).length;
|
|
260
|
+
reader.done('table section');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const definedMemories = [];
|
|
264
|
+
if (sections.has(5)) {
|
|
265
|
+
const reader = sections.get(5);
|
|
266
|
+
definedMemories.push(...reader.vector('memories', (entry, index) => limits(entry, `memory ${index}`)));
|
|
267
|
+
reader.done('memory section');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const globals = [];
|
|
271
|
+
if (sections.has(6)) {
|
|
272
|
+
const reader = sections.get(6);
|
|
273
|
+
globals.push(...reader.vector('globals', (entry, index) => Object.freeze({
|
|
274
|
+
...globalType(entry, `global ${index}`),
|
|
275
|
+
initializer: initExpression(entry, `global ${index} initializer`)
|
|
276
|
+
})));
|
|
277
|
+
reader.done('global section');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const allFunctionTypes = [...importedFunctionTypes, ...functionTypeIndexes].map((typeIndex) => types[typeIndex]);
|
|
281
|
+
const allMemories = [...importedMemories, ...definedMemories];
|
|
282
|
+
const exports = [];
|
|
283
|
+
if (sections.has(7)) {
|
|
284
|
+
const reader = sections.get(7);
|
|
285
|
+
exports.push(...reader.vector('exports', (entry, index) => {
|
|
286
|
+
const name = entry.name(`export ${index} name`);
|
|
287
|
+
const kindIndex = entry.byte(`export ${index} kind`);
|
|
288
|
+
const itemIndex = entry.uleb(`export ${index} index`);
|
|
289
|
+
const kind = exportKinds[kindIndex];
|
|
290
|
+
if (!kind) fail(diagnosticCodes.featureMismatch, `${label} export ${name} uses unsupported kind ${kindIndex}.`);
|
|
291
|
+
if (kind === 'function') {
|
|
292
|
+
const type = allFunctionTypes[itemIndex];
|
|
293
|
+
if (!type) fail(diagnosticCodes.invalid, `${label} export ${name} references missing function ${itemIndex}.`);
|
|
294
|
+
return Object.freeze({ name, kind, parameters: type.parameters, results: type.results });
|
|
295
|
+
}
|
|
296
|
+
if (kind === 'memory') {
|
|
297
|
+
const type = allMemories[itemIndex];
|
|
298
|
+
if (!type) fail(diagnosticCodes.invalid, `${label} export ${name} references missing memory ${itemIndex}.`);
|
|
299
|
+
return Object.freeze({ name, kind, type });
|
|
300
|
+
}
|
|
301
|
+
return Object.freeze({ name, kind });
|
|
302
|
+
}));
|
|
303
|
+
reader.done('export section');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let hasStart = false;
|
|
307
|
+
if (sections.has(8)) {
|
|
308
|
+
const reader = sections.get(8);
|
|
309
|
+
reader.uleb('start function');
|
|
310
|
+
reader.done('start section');
|
|
311
|
+
hasStart = true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const dataSegments = [];
|
|
315
|
+
if (sections.has(11)) {
|
|
316
|
+
const reader = sections.get(11);
|
|
317
|
+
dataSegments.push(...reader.vector('data segments', (entry, index) => {
|
|
318
|
+
const flags = entry.uleb(`data segment ${index} flags`);
|
|
319
|
+
if (flags !== 0 && flags !== 2) {
|
|
320
|
+
fail(diagnosticCodes.featureMismatch, `${label} data segment ${index} must be active with a literal offset.`);
|
|
321
|
+
}
|
|
322
|
+
const memoryIndex = flags === 2 ? entry.uleb(`data segment ${index} memory`) : 0;
|
|
323
|
+
const offset = initExpression(entry, `data segment ${index} offset`);
|
|
324
|
+
if (offset.instruction !== 'i32.const' || offset.value < 0) {
|
|
325
|
+
fail(diagnosticCodes.memoryMismatch, `${label} data segment ${index} must use a non-negative literal offset.`);
|
|
326
|
+
}
|
|
327
|
+
const length = entry.uleb(`data segment ${index} length`);
|
|
328
|
+
const payload = entry.take(length, `data segment ${index} payload`);
|
|
329
|
+
return Object.freeze({
|
|
330
|
+
index,
|
|
331
|
+
mode: 'active',
|
|
332
|
+
memoryIndex,
|
|
333
|
+
offset: offset.value,
|
|
334
|
+
endExclusive: offset.value + payload.length,
|
|
335
|
+
bytes: payload.length,
|
|
336
|
+
sha256: sha256(payload)
|
|
337
|
+
});
|
|
338
|
+
}));
|
|
339
|
+
reader.done('data section');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return Object.freeze({
|
|
343
|
+
imports: Object.freeze(imports),
|
|
344
|
+
exports: Object.freeze(exports),
|
|
345
|
+
functions: functionTypeIndexes.length,
|
|
346
|
+
globals: globals.length + importedGlobals,
|
|
347
|
+
mutableGlobals: Object.freeze(globals
|
|
348
|
+
.map((entry, index) => ({ ...entry, name: `global$${index}` }))
|
|
349
|
+
.filter((entry) => entry.mutable)
|
|
350
|
+
.map((entry) => Object.freeze({
|
|
351
|
+
name: entry.name,
|
|
352
|
+
type: entry.type,
|
|
353
|
+
initializer: entry.initializer
|
|
354
|
+
}))),
|
|
355
|
+
definedMemories: definedMemories.length,
|
|
356
|
+
importedMemories: importedMemories.length,
|
|
357
|
+
memories: allMemories.length,
|
|
358
|
+
memoryTypes: Object.freeze(allMemories),
|
|
359
|
+
definedTables,
|
|
360
|
+
importedTables,
|
|
361
|
+
tables: definedTables + importedTables,
|
|
362
|
+
hasStart,
|
|
363
|
+
dataSegments: Object.freeze(dataSegments)
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function count(source, expression) {
|
|
368
|
+
return [...source.matchAll(expression)].length;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function features(output) {
|
|
372
|
+
return Object.freeze(output
|
|
373
|
+
.split(/\r?\n/)
|
|
374
|
+
.map((line) => line.trim())
|
|
375
|
+
.filter((line) => line.startsWith('--enable-'))
|
|
376
|
+
.map((line) => line.slice('--enable-'.length))
|
|
377
|
+
.sort());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function safeLabel(value) {
|
|
381
|
+
return String(value || 'module').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-|-$/g, '').slice(0, 64) || 'module';
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function inspectWasmFile(file, options = {}) {
|
|
385
|
+
const label = safeLabel(options.label);
|
|
386
|
+
const workDirectory = options.workDirectory;
|
|
387
|
+
if (!workDirectory || !fs.existsSync(workDirectory) || !fs.statSync(workDirectory).isDirectory()) {
|
|
388
|
+
fail(diagnosticCodes.invalid, 'Wasm inspection requires an existing work directory.');
|
|
389
|
+
}
|
|
390
|
+
let bytes;
|
|
391
|
+
try {
|
|
392
|
+
bytes = fs.readFileSync(file);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
wrap(error, options.failureCode || diagnosticCodes.invalid, `${label} Wasm could not be read.`);
|
|
395
|
+
}
|
|
396
|
+
if (!WebAssembly.validate(bytes)) {
|
|
397
|
+
fail(options.failureCode || diagnosticCodes.invalid, `${label} is not valid WebAssembly.`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const featureOutput = path.join(workDirectory, `${label}-feature-check.wasm`);
|
|
401
|
+
const watFile = path.join(workDirectory, `${label}.wat`);
|
|
402
|
+
const featureResult = runTool('wasm-opt', [
|
|
403
|
+
file,
|
|
404
|
+
'--mvp-features',
|
|
405
|
+
'--print-features',
|
|
406
|
+
'-o',
|
|
407
|
+
featureOutput
|
|
408
|
+
], {
|
|
409
|
+
cwd: workDirectory,
|
|
410
|
+
failureCode: options.featureFailureCode || diagnosticCodes.featureMismatch,
|
|
411
|
+
failureMessage: `${label} uses a feature outside the MVP baseline.`
|
|
412
|
+
});
|
|
413
|
+
runTool('wasm-dis', [
|
|
414
|
+
file,
|
|
415
|
+
'--mvp-features',
|
|
416
|
+
'-o',
|
|
417
|
+
watFile
|
|
418
|
+
], {
|
|
419
|
+
cwd: workDirectory,
|
|
420
|
+
failureCode: options.failureCode || diagnosticCodes.invalid,
|
|
421
|
+
failureMessage: `${label} could not be independently disassembled.`
|
|
422
|
+
});
|
|
423
|
+
const wat = fs.readFileSync(watFile, 'utf8');
|
|
424
|
+
const parsed = parseModule(bytes, label);
|
|
425
|
+
return Object.freeze({
|
|
426
|
+
bytes: bytes.length,
|
|
427
|
+
sha256: sha256(bytes),
|
|
428
|
+
valid: true,
|
|
429
|
+
imports: parsed.imports,
|
|
430
|
+
exports: parsed.exports,
|
|
431
|
+
functions: parsed.functions,
|
|
432
|
+
globals: parsed.globals,
|
|
433
|
+
mutableGlobals: parsed.mutableGlobals,
|
|
434
|
+
definedMemories: parsed.definedMemories,
|
|
435
|
+
importedMemories: parsed.importedMemories,
|
|
436
|
+
memories: parsed.memories,
|
|
437
|
+
memoryTypes: parsed.memoryTypes,
|
|
438
|
+
dataSegmentCount: parsed.dataSegments.length,
|
|
439
|
+
dataBytes: parsed.dataSegments.reduce((sum, segment) => sum + segment.bytes, 0),
|
|
440
|
+
dataSegments: parsed.dataSegments,
|
|
441
|
+
definedTables: parsed.definedTables,
|
|
442
|
+
importedTables: parsed.importedTables,
|
|
443
|
+
tables: parsed.tables,
|
|
444
|
+
features: features(featureResult.stdout),
|
|
445
|
+
hasStart: parsed.hasStart,
|
|
446
|
+
instructions: Object.freeze({
|
|
447
|
+
memoryGrow: count(wat, /\(memory\.grow\b/g),
|
|
448
|
+
memoryCopy: count(wat, /\(memory\.copy\b/g),
|
|
449
|
+
memoryFill: count(wat, /\(memory\.fill\b/g),
|
|
450
|
+
memoryInit: count(wat, /\(memory\.init\b/g),
|
|
451
|
+
globalSet: count(wat, /\(global\.set\b/g),
|
|
452
|
+
store: count(wat, /\((?:i32|i64|f32|f64|v128)\.store(?:8|16|32)?\b/g),
|
|
453
|
+
callIndirect: count(wat, /\(call_indirect\b/g)
|
|
454
|
+
})
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function surfaceImport(entry) {
|
|
459
|
+
if (entry.kind === 'memory') {
|
|
460
|
+
return Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind, type: entry.type });
|
|
461
|
+
}
|
|
462
|
+
if (entry.kind === 'function') {
|
|
463
|
+
return Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind, type: entry.type });
|
|
464
|
+
}
|
|
465
|
+
return Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind });
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function surfaceExport(entry) {
|
|
469
|
+
if (entry.kind === 'function') {
|
|
470
|
+
return Object.freeze({
|
|
471
|
+
name: entry.name,
|
|
472
|
+
kind: entry.kind,
|
|
473
|
+
parameters: entry.parameters,
|
|
474
|
+
results: entry.results
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return Object.freeze({ name: entry.name, kind: entry.kind });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function reportInspection(inspection) {
|
|
481
|
+
return Object.freeze({
|
|
482
|
+
bytes: inspection.bytes,
|
|
483
|
+
sha256: inspection.sha256,
|
|
484
|
+
imports: Object.freeze(inspection.imports.map(surfaceImport)),
|
|
485
|
+
exports: Object.freeze(inspection.exports.map(surfaceExport)),
|
|
486
|
+
definedMemories: inspection.definedMemories,
|
|
487
|
+
importedMemories: inspection.importedMemories,
|
|
488
|
+
memories: inspection.memories,
|
|
489
|
+
memoryTypes: inspection.memoryTypes,
|
|
490
|
+
features: inspection.features,
|
|
491
|
+
hasStart: inspection.hasStart,
|
|
492
|
+
dataSegments: Object.freeze(inspection.dataSegments.map((entry) => Object.freeze({
|
|
493
|
+
memoryIndex: entry.memoryIndex,
|
|
494
|
+
offset: entry.offset,
|
|
495
|
+
endExclusive: entry.endExclusive,
|
|
496
|
+
bytes: entry.bytes,
|
|
497
|
+
sha256: entry.sha256
|
|
498
|
+
}))),
|
|
499
|
+
mutableGlobals: inspection.mutableGlobals,
|
|
500
|
+
tables: inspection.tables,
|
|
501
|
+
instructions: inspection.instructions
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
module.exports = Object.freeze({
|
|
506
|
+
inspectWasmFile,
|
|
507
|
+
reportInspection,
|
|
508
|
+
surfaceImport,
|
|
509
|
+
surfaceExport
|
|
510
|
+
});
|