1- import { decodeULEB128 , decodeSLEB128 } from '@thi.ng/leb128' ;
1+ import { decodeULEB128 } from '@thi.ng/leb128' ;
22import * as w from '@wasmgroundup/emit' ;
33
44import assert from 'node:assert' ;
55
66const 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-
168function 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-
4730function 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-
5735export 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