Files: ecd6663811fd2bc53431d0fa2fdab2cd5c9d054b / index.js
8682 bytesRaw
1 | const Stream = require('buffer-pipe') |
2 | const Buffer = require('safe-buffer').Buffer |
3 | const leb = require('leb128') |
4 | const {findSections} = require('wasm-json-toolkit') |
5 | |
6 | const FUNC_TYPE = 0x60 |
7 | const LANGUAGE_TYPES_STRG = { |
8 | 'i32': 0x7f, |
9 | 'i64': 0x7e, |
10 | 'f32': 0x7d, |
11 | 'f64': 0x7c, |
12 | 'anyref': 0x70, |
13 | 'module': 0x6f, |
14 | 'func': 0x6e, |
15 | 'data': 0x6d, |
16 | 'elem': 0x6c, |
17 | 'link': 0x6b, |
18 | 'id': 0x6a |
19 | } |
20 | |
21 | const LANGUAGE_TYPES_BIN = { |
22 | 0x7f: 'i32', |
23 | 0x7e: 'i64', |
24 | 0x7d: 'f32', |
25 | 0x7c: 'f64', |
26 | 0x70: 'anyref', |
27 | 0x6f: 'module', |
28 | 0x6e: 'func', |
29 | 0x6d: 'data', |
30 | 0x6c: 'elem', |
31 | 0x6b: 'link', |
32 | 0x6a: 'id' |
33 | } |
34 | |
35 | const EXTERNAL_KIND_BIN = { |
36 | 0x0: 'func', |
37 | 0x1: 'table', |
38 | 0x2: 'memory', |
39 | 0x3: 'global' |
40 | } |
41 | |
42 | const EXTERNAL_KIND_STRG = { |
43 | 'func': 0x0, |
44 | 'table': 0x1, |
45 | 'memory': 0x2, |
46 | 'global': 0x3 |
47 | } |
48 | |
49 | /** |
50 | * encodes the type annotations |
51 | * @param {Object} annotations |
52 | * @return {Buffer} |
53 | */ |
54 | function encode (annotations) { |
55 | const stream = new Stream() |
56 | encodeCustomSection('types', annotations, stream, encodeType) |
57 | encodeCustomSection('typeMap', annotations, stream, encodeTypeMap) |
58 | encodeCustomSection('persist', annotations, stream, encodePersist) |
59 | |
60 | return stream.buffer |
61 | } |
62 | |
63 | function encodeCustomSection (name, json, stream, encodingFunc) { |
64 | let payload = new Stream() |
65 | json = json[name] |
66 | |
67 | if (json) { |
68 | stream.write([0]) |
69 | // encode type |
70 | leb.unsigned.write(name.length, payload) |
71 | payload.write(name) |
72 | encodingFunc(json, payload) |
73 | // write the size of the payload |
74 | leb.unsigned.write(payload.bytesWrote, stream) |
75 | stream.write(payload.buffer) |
76 | } |
77 | return stream |
78 | } |
79 | |
80 | /** |
81 | * encodes the type annoations for persist |
82 | * @param {Object} annoations |
83 | * @param {buffer-pipe} [stream] |
84 | * @return {Buffer} |
85 | */ |
86 | function encodePersist (annotations, stream = new Stream()) { |
87 | leb.unsigned.write(annotations.length, stream) |
88 | for (const entry of annotations) { |
89 | const form = EXTERNAL_KIND_STRG[entry.form] |
90 | leb.unsigned.write(form, stream) |
91 | leb.unsigned.write(entry.index, stream) |
92 | leb.unsigned.write(LANGUAGE_TYPES_STRG[entry.type], stream) |
93 | } |
94 | return stream.buffer |
95 | } |
96 | |
97 | /** |
98 | * decodes the persist annotations |
99 | * @param {Buffer} buf |
100 | * @param {Object} |
101 | */ |
102 | function decodePersist (buf) { |
103 | const stream = new Stream(Buffer.from(buf)) |
104 | let numOfEntries = leb.unsigned.read(stream) |
105 | const json = [] |
106 | while (numOfEntries--) { |
107 | const form = EXTERNAL_KIND_BIN[leb.unsigned.readBn(stream).toNumber()] |
108 | if (!form) { |
109 | throw new Error('invalid form') |
110 | } |
111 | const index = leb.unsigned.readBn(stream).toNumber() |
112 | const type = LANGUAGE_TYPES_BIN[leb.unsigned.readBn(stream).toNumber()] |
113 | if (!type) { |
114 | throw new Error('invalid param') |
115 | } |
116 | json.push({ |
117 | form, |
118 | index, |
119 | type |
120 | }) |
121 | } |
122 | |
123 | if (stream.buffer.length) { |
124 | throw new Error('invalid buffer length') |
125 | } |
126 | |
127 | return json |
128 | } |
129 | |
130 | /** |
131 | * encodes a typeMap definition |
132 | * @param {Object} definition |
133 | * @param {buffer-pipe} [stream] |
134 | * @return {Buffer} |
135 | */ |
136 | function encodeTypeMap (definition, stream = new Stream()) { |
137 | leb.unsigned.write(definition.length, stream) |
138 | for (let entry of definition) { |
139 | leb.unsigned.write(entry.func, stream) |
140 | leb.unsigned.write(entry.type, stream) |
141 | } |
142 | return stream.buffer |
143 | } |
144 | |
145 | /** |
146 | * decodes the TypeMap section |
147 | * @param {Buffer} buf |
148 | * @param {Object} |
149 | */ |
150 | function decodeTypeMap (buf) { |
151 | const stream = new Stream(Buffer.from(buf)) |
152 | let numOfEntries = leb.unsigned.read(stream) |
153 | const json = [] |
154 | while (numOfEntries--) { |
155 | json.push({ |
156 | func: leb.unsigned.readBn(stream).toNumber(), |
157 | type: leb.unsigned.readBn(stream).toNumber() |
158 | }) |
159 | } |
160 | if (stream.buffer.length) { |
161 | throw new Error('invalid buffer length') |
162 | } |
163 | return json |
164 | } |
165 | |
166 | /** |
167 | * encodes the type annotations |
168 | * @param {Object} definition |
169 | * @param {buffer-pipe} [stream] |
170 | * @return {Buffer} |
171 | */ |
172 | function encodeType (annotations, stream = new Stream()) { |
173 | let binEntries = new Stream() |
174 | |
175 | leb.unsigned.write(annotations.length, binEntries) |
176 | for (let entry of annotations) { |
177 | // a single type entry binary encoded |
178 | binEntries.write([FUNC_TYPE]) |
179 | |
180 | const len = entry.params.length // number of parameters |
181 | leb.unsigned.write(len, binEntries) |
182 | binEntries.write(entry.params.map(type => LANGUAGE_TYPES_STRG[type])) // the paramter types |
183 | binEntries.write([0]) |
184 | // binEntries.write([entry.return_type ? 1 : 0]) // number of return types |
185 | // if (entry.return_type) { |
186 | // binEntries.write([LANGUAGE_TYPES[entry.return_type]]) |
187 | // throw new Error('return type are not allowed') |
188 | // } |
189 | } |
190 | |
191 | stream.write(binEntries.buffer) |
192 | return stream.buffer |
193 | } |
194 | |
195 | /** |
196 | * decodes the Type section |
197 | * @param {Buffer} buf |
198 | * @param {Object} |
199 | */ |
200 | function decodeType (buf) { |
201 | const stream = new Stream(Buffer.from(buf)) |
202 | const numberOfEntries = leb.unsigned.readBn(stream).toNumber() |
203 | const json = [] |
204 | for (let i = 0; i < numberOfEntries; i++) { |
205 | let type = stream.read(1)[0] |
206 | if (type !== FUNC_TYPE) { |
207 | throw new Error('invalid form') |
208 | } |
209 | const entry = { |
210 | form: 'func', |
211 | params: [] |
212 | } |
213 | |
214 | let paramCount = leb.unsigned.readBn(stream).toNumber() |
215 | |
216 | // parse the entries |
217 | while (paramCount--) { |
218 | const type = stream.read(1)[0] |
219 | const param = LANGUAGE_TYPES_BIN[type] |
220 | if (!param) { |
221 | throw new Error('invalid param') |
222 | } |
223 | entry.params.push(param) |
224 | } |
225 | // remove the last byte |
226 | leb.unsigned.readBn(stream) |
227 | // const numOfReturns = leb.unsigned.readBn(stream).toNumber() |
228 | // if (numOfReturns) { |
229 | // type = stream.read(1)[0] |
230 | // entry.return_type = LANGUAGE_TYPES[type] |
231 | // } |
232 | |
233 | json.push(entry) |
234 | } |
235 | |
236 | if (stream.buffer.length) { |
237 | throw new Error('invalid buffer length') |
238 | } |
239 | return json |
240 | } |
241 | |
242 | /** |
243 | * injects custom sections into a wasm binary |
244 | * @param {Buffer} custom - the custom section(s) |
245 | * @param {Buffer} wasm - the wasm binary |
246 | * @return {Buffer} |
247 | */ |
248 | function injectCustomSection (custom, wasm) { |
249 | const preramble = wasm.subarray(0, 8) |
250 | const body = wasm.subarray(8) |
251 | return Buffer.concat([ |
252 | Buffer.from(preramble), |
253 | Buffer.from(custom), |
254 | Buffer.from(body) |
255 | ]) |
256 | } |
257 | |
258 | /** |
259 | * encodes a json definition and injects it into a wasm binary |
260 | * @param {Object} annotation - the type definition |
261 | * @param {Buffer} wasm - the wasm binary to inject |
262 | */ |
263 | function encodeAndInject (annotation, wasm) { |
264 | const buf = encode(annotation) |
265 | return injectCustomSection(buf, wasm) |
266 | } |
267 | |
268 | function mergeTypeSections (json) { |
269 | const result = { |
270 | types: [], |
271 | indexes: {}, |
272 | exports: {}, |
273 | persist: [] |
274 | } |
275 | |
276 | const wantedSections = ['types', 'typeMap', 'persist', 'type', 'import', 'function', 'export'] |
277 | const iterator = findSections(json, wantedSections) |
278 | const mappedFuncs = new Map() |
279 | const mappedTypes = new Map() |
280 | const {value: customType} = iterator.next() |
281 | if (customType) { |
282 | const type = decodeType(customType.payload) |
283 | result.types = type |
284 | } |
285 | let {value: typeMap} = iterator.next() |
286 | if (typeMap) { |
287 | decodeTypeMap(typeMap.payload).forEach(map => mappedFuncs.set(map.func, map.type)) |
288 | } |
289 | |
290 | let {value: persist} = iterator.next() |
291 | if (persist) { |
292 | result.persist = decodePersist(persist.payload) |
293 | } |
294 | |
295 | const {value: type} = iterator.next() |
296 | const {value: imports = {entries: []}} = iterator.next() |
297 | const {value: functions = {entries: []}} = iterator.next() |
298 | functions.entries.forEach((typeIndex, funcIndex) => { |
299 | const newType = type.entries[typeIndex] |
300 | // validate that no function signature have no return types |
301 | if (newType.return_type) { |
302 | throw new Error('no return types allowed') |
303 | } |
304 | let customIndex = mappedFuncs.get(funcIndex) |
305 | if (customIndex === undefined) { |
306 | customIndex = mappedTypes.get(typeIndex) |
307 | } else { |
308 | const customType = result.types[customIndex] |
309 | if (customType.params.length !== newType.params.length) { |
310 | throw new Error('invalid param length') |
311 | } |
312 | |
313 | if (!newType.params.every(param => param === 'i32')) { |
314 | throw new Error('invalid base param type') |
315 | } |
316 | } |
317 | |
318 | if (customIndex === undefined) { |
319 | customIndex = result.types.push(newType) - 1 |
320 | mappedTypes.set(typeIndex, customIndex) |
321 | } |
322 | result.indexes[funcIndex + imports.entries.length] = customIndex |
323 | }) |
324 | |
325 | const {value: exports = {entries: []}} = iterator.next() |
326 | exports.entries.forEach(entry => { |
327 | if (entry.kind === 'function') { |
328 | result.exports[entry.field_str] = entry.index |
329 | } |
330 | }) |
331 | return result |
332 | } |
333 | |
334 | module.exports = { |
335 | injectCustomSection, |
336 | encodeAndInject, |
337 | decodeType, |
338 | decodeTypeMap, |
339 | decodePersist, |
340 | encodeType, |
341 | encodeTypeMap, |
342 | encodePersist, |
343 | encode, |
344 | mergeTypeSections, |
345 | LANGUAGE_TYPES_BIN, |
346 | LANGUAGE_TYPES_STRG |
347 | } |
348 |
Built with git-ssb-web