Skip to content

Commit 33f369e

Browse files
authored
compiler: make OHM_DEBUG a compile-time-only option (#595)
1 parent 3d08f5b commit 33f369e

6 files changed

Lines changed: 303 additions & 282 deletions

File tree

packages/compiler/scripts/bootstrap.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ try {
3838
const g = ohm.grammar(source);
3939

4040
// Compile to WASM.
41-
const bytes = new Compiler(g).compile();
41+
// Never include debug imports in the metagrammar — it's loaded via the
42+
// sync Grammar constructor which doesn't support debug imports.
43+
const bytes = new Compiler(g, {debug: false}).compile();
4244

4345
// Write the raw .wasm file.
4446
writeFileSync(wasmPath, bytes);

packages/compiler/scripts/bundlewasm.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import * as w from '@wasmgroundup/emit';
66

77
import {extractSections} from './modparse.ts';
88

9-
const importAdjust = process.env.OHM_DEBUG === '1' ? 10000 : 0;
10-
119
/*
1210
Extracts the code section from the AssemblyScript release build
1311
and writes it to a .ts module in the same directory.
@@ -17,18 +15,10 @@ const inputPath = process.argv[2];
1715
const outputPath = inputPath + '_sections.ts';
1816

1917
const buf = fs.readFileSync(inputPath);
20-
const sections = extractSections(buf, {
21-
destImportCountAdjustment: importAdjust,
22-
});
23-
24-
// We've rewritten every funcidx in the function bodies to account for a
25-
// specific number of imports in the dest module. We record that number
26-
// to enable a run-time check that the final module is compatible.
27-
const destImportCount = importAdjust + sections.importsec.entryCount;
18+
const sections = extractSections(buf);
2819

2920
let output = `const decodeBase64 = (str: string) => Array.from(atob(str), c => c.charCodeAt(0));
3021
31-
export const destImportCount = ${destImportCount};
3222
export const startFuncidx = ${sections.startFuncidx};
3323
`;
3424

Lines changed: 6 additions & 254 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
import {decodeULEB128, decodeSLEB128} from '@thi.ng/leb128';
1+
import {decodeULEB128} from '@thi.ng/leb128';
22
import * as w from '@wasmgroundup/emit';
33

44
import assert from 'node:assert';
55

66
const textDecoder = new TextDecoder();
77

8-
// For sanity checking, assume that the number of locals is never
9-
// above a certain number. (We can raise this if necessary.)
10-
const MAX_LOCALS = 64;
11-
12-
const WASM_NUMTYPES = [0x7c, 0x7d, 0x7e, 0x7f];
13-
const WASM_VECTYPE = 0x7b;
14-
const WASM_REFTYPES = [0x6f, 0x70];
15-
168
function checkNotNull<T>(x: T): NonNullable<T> {
179
assert(x !== null, 'unexpected null value');
1810
return x as NonNullable<T>;
@@ -35,25 +27,11 @@ function skipPreamble(bytes: Uint8Array): void {
3527
}
3628
}
3729

38-
function isValtype(t: number): boolean {
39-
return WASM_NUMTYPES.includes(t) || WASM_VECTYPE === t || WASM_REFTYPES.includes(t);
40-
}
41-
42-
function checkValtype(t: number): number {
43-
assert(isValtype(t), `unrecognized valtype: 0x${t.toString(16).padStart(2, '0')}`);
44-
return t;
45-
}
46-
4730
function checkU32(b: bigint): number {
4831
assert(b >= 0n && b < 2n ** 32n, `not a valid U32 value: ${b}`);
4932
return Number(b);
5033
}
5134

52-
function checkI32(b: bigint): number {
53-
assert(b >= -(2n ** 31n) && b < 2n ** 31n, `not a valid I32 value: ${b}`);
54-
return Number(b);
55-
}
56-
5735
export type VecContents = {
5836
entryCount: number;
5937
contents: Uint8Array;
@@ -67,13 +45,8 @@ export type RawContents = {
6745
contents: Uint8Array;
6846
};
6947

70-
interface ExtractOptions {
71-
// The number of extra imports int the dest module.
72-
destImportCountAdjustment?: number;
73-
}
74-
7548
// Extracts the type, import, function, global, and code sections from a Wasm module.
76-
export function extractSections(bytes: Uint8Array, opts: ExtractOptions = {}) {
49+
export function extractSections(bytes: Uint8Array) {
7750
skipPreamble(bytes);
7851

7952
const parseU32 = () => {
@@ -96,7 +69,7 @@ export function extractSections(bytes: Uint8Array, opts: ExtractOptions = {}) {
9669
// Parse the export section. Returns {funcs, globals} maps of {name: idx}.
9770
// `funcidxAdjustment` adjusts function indices to account for the dest
9871
// module having more imports than the source module.
99-
function parseExportSection(expectedId: number, funcidxAdjustment: number) {
72+
function parseExportSection(expectedId: number) {
10073
const id = bytes[pos++];
10174
assert(id === expectedId, `expected section with id ${expectedId}, got ${id}`);
10275
const size = parseU32();
@@ -119,10 +92,8 @@ export function extractSections(bytes: Uint8Array, opts: ExtractOptions = {}) {
11992
const index = parseU32();
12093

12194
if (kind === 0x00) {
122-
// Function export: adjust index for dest import count.
123-
funcs[name] = index + funcidxAdjustment;
95+
funcs[name] = index;
12496
} else if (kind === 0x03) {
125-
// Global export: index is unchanged (globals aren't rewritten).
12697
globals[name] = index;
12798
}
12899
}
@@ -202,11 +173,6 @@ export function extractSections(bytes: Uint8Array, opts: ExtractOptions = {}) {
202173
let codesec: VecContents | undefined;
203174
let startFuncidx: number | undefined;
204175

205-
let srcImportCount = 0;
206-
let destImportCount = 0;
207-
208-
const importAdjust = opts.destImportCountAdjustment ?? 0;
209-
210176
let pos = 8;
211177
let lastId = -1;
212178
while (pos < bytes.length) {
@@ -223,26 +189,16 @@ export function extractSections(bytes: Uint8Array, opts: ExtractOptions = {}) {
223189
typesec = parseVecSectionOpaque(id);
224190
} else if (id === 2) {
225191
importsec = parseVecSectionOpaque(id);
226-
srcImportCount = importsec.entryCount;
227-
destImportCount = srcImportCount + importAdjust;
228192
} else if (id === 3) {
229193
funcsec = parseVecSectionOpaque(id);
230194
} else if (id === 6) {
231195
globalsec = parseVecSectionOpaque(id);
232196
} else if (id === 7) {
233-
exports = parseExportSection(id, destImportCount - srcImportCount);
197+
exports = parseExportSection(id);
234198
} else if (id === 8) {
235-
startFuncidx = parseStartSection(id) + destImportCount - srcImportCount;
199+
startFuncidx = parseStartSection(id);
236200
} else if (id === 10) {
237201
codesec = parseVecSectionOpaque(id);
238-
// Rewrite the code section to account for the number of imports that
239-
// will exist in the final module. If `destImportCount` is not provided,
240-
// assume that it's the same as srcImportCount.
241-
codesec.contents = rewriteCodesecContents(
242-
codesec.contents,
243-
srcImportCount,
244-
destImportCount
245-
);
246202
} else if (id === 0) {
247203
// Custom section — check if it's the 'name' section.
248204
pos++; // consume section id
@@ -274,207 +230,3 @@ export function extractSections(bytes: Uint8Array, opts: ExtractOptions = {}) {
274230
startFuncidx,
275231
};
276232
}
277-
278-
function rewriteCodeEntry(
279-
bytes: Uint8Array,
280-
srcImportCount: number,
281-
destImportCount: number
282-
): number[] {
283-
const {instr} = w;
284-
let pos = 0;
285-
286-
const parseU32 = () => {
287-
const [val, count] = decodeULEB128(bytes, pos);
288-
pos += count;
289-
return checkU32(val);
290-
};
291-
292-
const parseI32 = () => {
293-
const [val, count] = decodeSLEB128(bytes, pos);
294-
pos += count;
295-
return checkI32(val);
296-
};
297-
298-
function skipLocals() {
299-
const len = parseU32();
300-
for (let i = 0; i < len; i++) {
301-
const count = parseU32();
302-
assert(count < MAX_LOCALS, `too many locals: ${count} @${pos}`);
303-
checkValtype(bytes[pos++]);
304-
}
305-
}
306-
307-
function skipU32Vec() {
308-
const len = parseU32();
309-
for (let i = 0; i < len; i++) {
310-
parseU32();
311-
}
312-
}
313-
314-
// See https://webassembly.github.io/spec/core/bikeshed/#binary-blocktype
315-
function skipBlocktype() {
316-
const b = bytes[pos];
317-
if (b === 0x40 || isValtype(b)) {
318-
pos += 1;
319-
return;
320-
}
321-
// From the spec:
322-
// > Unlike any other occurrence, the type index in a block type is encoded
323-
// > as a positive signed integer, so that its signed LEB128 bit pattern
324-
// > cannot collide with the encoding of value types or the special code
325-
// > 0x40, which correspond to the LEB128 encoding of negative integers.
326-
const [idx, count] = decodeSLEB128(bytes.slice(pos));
327-
pos += count;
328-
assert(idx >= 0, `unexpected typeidx in blocktype: ${idx}`);
329-
}
330-
331-
skipLocals();
332-
333-
const result: number[] = [];
334-
let sliceStart = 0;
335-
let nesting = 1;
336-
337-
// Walk through the function's bytecode.
338-
while (pos < bytes.length) {
339-
const bc = bytes[pos++];
340-
341-
// The cases here are ordered by ascending opcode.
342-
// See https://pengowray.github.io/wasm-ops/ for an overview.
343-
switch (bc) {
344-
case instr.unreachable:
345-
case instr.nop:
346-
break;
347-
case instr.block:
348-
case instr.loop:
349-
case instr.if:
350-
skipBlocktype();
351-
++nesting;
352-
break;
353-
case instr.else:
354-
break;
355-
case instr.end:
356-
assert(--nesting >= 0, `bad nesting @${pos - 1}`);
357-
break;
358-
case instr.br:
359-
case instr.br_if:
360-
parseU32();
361-
break;
362-
case instr.br_table:
363-
skipU32Vec(); // labels
364-
parseU32(); // default label
365-
break;
366-
case instr.return:
367-
break;
368-
case instr.call:
369-
// Rewrite `call` instructions so that the index is valid for the
370-
// target module.
371-
result.push(...bytes.slice(sliceStart, pos));
372-
let idx = parseU32();
373-
374-
// Function indices in a Wasm bundle are automatically assigned.
375-
// First come the imports, then the user-defined functions.
376-
// Since the dest module has additional imports, we need to rewrite
377-
// the funcidx if and only if it referred to a user function.
378-
if (idx >= srcImportCount) {
379-
idx += destImportCount - srcImportCount;
380-
}
381-
result.push(...w.u32(idx));
382-
sliceStart = pos;
383-
break;
384-
case instr.call_indirect:
385-
parseU32();
386-
parseU32();
387-
break;
388-
case instr.drop:
389-
case instr.select:
390-
break;
391-
case instr.local.get:
392-
case instr.local.set:
393-
case instr.local.tee:
394-
case instr.global.get:
395-
case instr.global.set:
396-
parseU32();
397-
break;
398-
case instr.i32.const:
399-
parseI32();
400-
break;
401-
case instr.i64.const:
402-
const origPos = pos;
403-
const [_, count] = decodeULEB128(bytes.slice(pos));
404-
assert(count <= 10, `too many bytes (${count}) for i64 @${origPos}`);
405-
pos += count;
406-
break;
407-
case instr.f32.const:
408-
pos += 4;
409-
break;
410-
case instr.f64.const:
411-
pos += 8;
412-
break;
413-
// @ts-ignore Fallthrough case in switch
414-
case 0xfc:
415-
const bc2 = parseU32();
416-
if (0 <= bc2 && bc2 <= 7) {
417-
// i32.trunc_sat_XXX
418-
break;
419-
}
420-
switch (bc2) {
421-
case 0x0a: // memory.copy
422-
parseU32();
423-
parseU32();
424-
break;
425-
case 0x0b: // memory.fill
426-
parseU32();
427-
break;
428-
default:
429-
throw new Error(`unhandled multibyte ${bc2.toString(16)} @${pos - 1}`);
430-
}
431-
break;
432-
default:
433-
if (instr.i32.load <= bc && bc <= instr.i64.store32) {
434-
parseU32();
435-
parseU32();
436-
} else if (instr.memory.size <= bc && bc <= instr.memory.grow) {
437-
parseU32();
438-
} else if (instr.i32.eqz <= bc && bc <= instr.f64.reinterpret_i64) {
439-
// do nothing
440-
} else {
441-
throw new Error(`unhandled bytecode 0x${bc.toString(16)} @${pos - 1}`);
442-
}
443-
break;
444-
}
445-
}
446-
result.push(...bytes.slice(sliceStart));
447-
return result;
448-
}
449-
450-
// Rewrite the contents of the prebuilt code section, changing the funcidx of
451-
// `call` instructions to account for the correct number of imports in the
452-
// final module.
453-
function rewriteCodesecContents(
454-
bytes: Uint8Array,
455-
srcImportCount: number,
456-
destImportCount: number
457-
): Uint8Array {
458-
let pos = 0;
459-
460-
const parseU32 = () => {
461-
const [val, count] = decodeULEB128(bytes, pos);
462-
pos += count;
463-
return checkU32(val);
464-
};
465-
466-
const newBytes: number[] = [];
467-
468-
while (pos < bytes.length) {
469-
const size = parseU32();
470-
const newEntry = rewriteCodeEntry(
471-
bytes.slice(pos, pos + size),
472-
srcImportCount,
473-
destImportCount
474-
);
475-
newBytes.push(...w.u32(newEntry.length), ...newEntry);
476-
pos += size;
477-
}
478-
479-
return new Uint8Array(newBytes);
480-
}

0 commit comments

Comments
 (0)