git ssb

0+

wanderer🌟 / js-primea-annotations



Tree: ecd6663811fd2bc53431d0fa2fdab2cd5c9d054b

Files: ecd6663811fd2bc53431d0fa2fdab2cd5c9d054b / index.js

8682 bytesRaw
1const Stream = require('buffer-pipe')
2const Buffer = require('safe-buffer').Buffer
3const leb = require('leb128')
4const {findSections} = require('wasm-json-toolkit')
5
6const FUNC_TYPE = 0x60
7const 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
21const 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
35const EXTERNAL_KIND_BIN = {
36 0x0: 'func',
37 0x1: 'table',
38 0x2: 'memory',
39 0x3: 'global'
40}
41
42const 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 */
54function 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
63function 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 */
86function 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 */
102function 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 */
136function 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 */
150function 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 */
172function 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 */
200function 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 */
248function 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 */
263function encodeAndInject (annotation, wasm) {
264 const buf = encode(annotation)
265 return injectCustomSection(buf, wasm)
266}
267
268function 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
334module.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