porffor 0.0.0-650350
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/LICENSE +21 -0
- package/README.md +275 -0
- package/compiler/builtins/base64.js +92 -0
- package/compiler/builtins.js +770 -0
- package/compiler/codeGen.js +2027 -0
- package/compiler/decompile.js +102 -0
- package/compiler/embedding.js +19 -0
- package/compiler/encoding.js +217 -0
- package/compiler/expression.js +70 -0
- package/compiler/index.js +67 -0
- package/compiler/opt.js +436 -0
- package/compiler/parse.js +8 -0
- package/compiler/prototype.js +272 -0
- package/compiler/sections.js +154 -0
- package/compiler/wasmSpec.js +200 -0
- package/compiler/wrap.js +119 -0
- package/package.json +23 -0
- package/porf.cmd +2 -0
- package/publish.js +13 -0
- package/runner/compare.js +35 -0
- package/runner/index.js +34 -0
- package/runner/info.js +54 -0
- package/runner/profile.js +47 -0
- package/runner/repl.js +104 -0
- package/runner/sizes.js +38 -0
- package/runner/transform.js +36 -0
- package/sw.js +26 -0
- package/util/enum.js +20 -0
@@ -0,0 +1,2027 @@
|
|
1
|
+
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
+
import { signedLEB128, unsignedLEB128 } from "./encoding.js";
|
3
|
+
import { operatorOpcode } from "./expression.js";
|
4
|
+
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
|
+
import { PrototypeFuncs } from "./prototype.js";
|
6
|
+
import { number, i32x4 } from "./embedding.js";
|
7
|
+
import parse from "./parse.js";
|
8
|
+
|
9
|
+
let globals = {};
|
10
|
+
let globalInd = 0;
|
11
|
+
let tags = [];
|
12
|
+
let funcs = [];
|
13
|
+
let exceptions = [];
|
14
|
+
let funcIndex = {};
|
15
|
+
let currentFuncIndex = importedFuncs.length;
|
16
|
+
let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
|
17
|
+
|
18
|
+
const debug = str => {
|
19
|
+
const code = [];
|
20
|
+
|
21
|
+
const logChar = n => {
|
22
|
+
code.push(...number(n));
|
23
|
+
|
24
|
+
code.push(Opcodes.call);
|
25
|
+
code.push(...unsignedLEB128(0));
|
26
|
+
};
|
27
|
+
|
28
|
+
for (let i = 0; i < str.length; i++) {
|
29
|
+
logChar(str.charCodeAt(i));
|
30
|
+
}
|
31
|
+
|
32
|
+
logChar('\n'.charCodeAt(0));
|
33
|
+
|
34
|
+
return code;
|
35
|
+
};
|
36
|
+
|
37
|
+
const todo = msg => {
|
38
|
+
throw new Error(`todo: ${msg}`);
|
39
|
+
|
40
|
+
const code = [];
|
41
|
+
|
42
|
+
code.push(...debug(`todo! ` + msg));
|
43
|
+
code.push(Opcodes.unreachable);
|
44
|
+
|
45
|
+
return code;
|
46
|
+
};
|
47
|
+
|
48
|
+
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
49
|
+
const generate = (scope, decl, global = false, name = undefined) => {
|
50
|
+
switch (decl.type) {
|
51
|
+
case 'BinaryExpression':
|
52
|
+
return generateBinaryExp(scope, decl, global, name);
|
53
|
+
|
54
|
+
case 'LogicalExpression':
|
55
|
+
return generateLogicExp(scope, decl);
|
56
|
+
|
57
|
+
case 'Identifier':
|
58
|
+
return generateIdent(scope, decl);
|
59
|
+
|
60
|
+
case 'ArrowFunctionExpression':
|
61
|
+
case 'FunctionDeclaration':
|
62
|
+
generateFunc(scope, decl);
|
63
|
+
return [];
|
64
|
+
|
65
|
+
case 'BlockStatement':
|
66
|
+
return generateCode(scope, decl);
|
67
|
+
|
68
|
+
case 'ReturnStatement':
|
69
|
+
return generateReturn(scope, decl);
|
70
|
+
|
71
|
+
case 'ExpressionStatement':
|
72
|
+
return generateExp(scope, decl);
|
73
|
+
|
74
|
+
case 'CallExpression':
|
75
|
+
return generateCall(scope, decl, global, name);
|
76
|
+
|
77
|
+
case 'NewExpression':
|
78
|
+
return generateNew(scope, decl, global, name);
|
79
|
+
|
80
|
+
case 'Literal':
|
81
|
+
return generateLiteral(scope, decl, global, name);
|
82
|
+
|
83
|
+
case 'VariableDeclaration':
|
84
|
+
return generateVar(scope, decl);
|
85
|
+
|
86
|
+
case 'AssignmentExpression':
|
87
|
+
return generateAssign(scope, decl);
|
88
|
+
|
89
|
+
case 'UnaryExpression':
|
90
|
+
return generateUnary(scope, decl);
|
91
|
+
|
92
|
+
case 'UpdateExpression':
|
93
|
+
return generateUpdate(scope, decl);
|
94
|
+
|
95
|
+
case 'IfStatement':
|
96
|
+
return generateIf(scope, decl);
|
97
|
+
|
98
|
+
case 'ForStatement':
|
99
|
+
return generateFor(scope, decl);
|
100
|
+
|
101
|
+
case 'WhileStatement':
|
102
|
+
return generateWhile(scope, decl);
|
103
|
+
|
104
|
+
case 'BreakStatement':
|
105
|
+
return generateBreak(scope, decl);
|
106
|
+
|
107
|
+
case 'ContinueStatement':
|
108
|
+
return generateContinue(scope, decl);
|
109
|
+
|
110
|
+
case 'EmptyStatement':
|
111
|
+
return generateEmpty(scope, decl);
|
112
|
+
|
113
|
+
case 'ConditionalExpression':
|
114
|
+
return generateConditional(scope, decl);
|
115
|
+
|
116
|
+
case 'ThrowStatement':
|
117
|
+
return generateThrow(scope, decl);
|
118
|
+
|
119
|
+
case 'TryStatement':
|
120
|
+
return generateTry(scope, decl);
|
121
|
+
|
122
|
+
case 'DebuggerStatement':
|
123
|
+
// todo: add fancy terminal debugger?
|
124
|
+
return [];
|
125
|
+
|
126
|
+
case 'ArrayExpression':
|
127
|
+
return generateArray(scope, decl, global, name);
|
128
|
+
|
129
|
+
case 'MemberExpression':
|
130
|
+
return generateMember(scope, decl, global, name);
|
131
|
+
|
132
|
+
case 'ExportNamedDeclaration':
|
133
|
+
// hack to flag new func for export
|
134
|
+
const funcsBefore = funcs.length;
|
135
|
+
generate(scope, decl.declaration);
|
136
|
+
|
137
|
+
if (funcsBefore === funcs.length) throw new Error('no new func added in export');
|
138
|
+
|
139
|
+
const newFunc = funcs[funcs.length - 1];
|
140
|
+
newFunc.export = true;
|
141
|
+
|
142
|
+
return [];
|
143
|
+
|
144
|
+
case 'TaggedTemplateExpression':
|
145
|
+
// hack for inline asm
|
146
|
+
if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
|
147
|
+
|
148
|
+
const str = decl.quasi.quasis[0].value.raw;
|
149
|
+
let out = [];
|
150
|
+
|
151
|
+
for (const line of str.split('\n')) {
|
152
|
+
const asm = line.trim().split(';;')[0].split(' ');
|
153
|
+
if (asm[0] === '') continue; // blank
|
154
|
+
|
155
|
+
if (asm[0] === 'local') {
|
156
|
+
const [ name, idx, type ] = asm.slice(1);
|
157
|
+
scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
|
158
|
+
continue;
|
159
|
+
}
|
160
|
+
|
161
|
+
if (asm[0] === 'returns') {
|
162
|
+
scope.returns = asm.slice(1).map(x => Valtype[x]);
|
163
|
+
continue;
|
164
|
+
}
|
165
|
+
|
166
|
+
if (asm[0] === 'memory') {
|
167
|
+
scope.memory = true;
|
168
|
+
allocPage('asm instrinsic');
|
169
|
+
// todo: add to store/load offset insts
|
170
|
+
continue;
|
171
|
+
}
|
172
|
+
|
173
|
+
let inst = Opcodes[asm[0].replace('.', '_')];
|
174
|
+
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
175
|
+
|
176
|
+
if (!Array.isArray(inst)) inst = [ inst ];
|
177
|
+
const immediates = asm.slice(1).map(x => parseInt(x));
|
178
|
+
|
179
|
+
out.push([ ...inst, ...immediates ]);
|
180
|
+
}
|
181
|
+
|
182
|
+
return out;
|
183
|
+
|
184
|
+
default:
|
185
|
+
return todo(`no generation for ${decl.type}!`);
|
186
|
+
}
|
187
|
+
};
|
188
|
+
|
189
|
+
const mapName = x => {
|
190
|
+
if (!x) return x;
|
191
|
+
|
192
|
+
if (x.startsWith('__globalThis_')) {
|
193
|
+
const key = x.slice('__globalThis_'.length);
|
194
|
+
// hack: this will not work properly
|
195
|
+
return key.includes('_') ? ('__' + key) : key;
|
196
|
+
}
|
197
|
+
|
198
|
+
return x;
|
199
|
+
};
|
200
|
+
|
201
|
+
const lookupName = (scope, _name) => {
|
202
|
+
const name = mapName(_name);
|
203
|
+
|
204
|
+
let local = scope.locals[name];
|
205
|
+
if (local) return [ local, false ];
|
206
|
+
|
207
|
+
let global = globals[name];
|
208
|
+
if (global) return [ global, true ];
|
209
|
+
|
210
|
+
return [ undefined, undefined ];
|
211
|
+
};
|
212
|
+
|
213
|
+
const internalThrow = (scope, constructor, message, expectsValue = false) => [
|
214
|
+
...generateThrow(scope, {
|
215
|
+
argument: {
|
216
|
+
type: 'NewExpression',
|
217
|
+
callee: {
|
218
|
+
name: constructor
|
219
|
+
},
|
220
|
+
arguments: [
|
221
|
+
{
|
222
|
+
value: message
|
223
|
+
}
|
224
|
+
]
|
225
|
+
}
|
226
|
+
}),
|
227
|
+
...(expectsValue ? number(UNDEFINED) : [])
|
228
|
+
];
|
229
|
+
|
230
|
+
const generateIdent = (scope, decl) => {
|
231
|
+
const lookup = rawName => {
|
232
|
+
const name = mapName(rawName);
|
233
|
+
let local = scope.locals[rawName];
|
234
|
+
|
235
|
+
if (builtinVars[name]) {
|
236
|
+
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
237
|
+
return builtinVars[name];
|
238
|
+
}
|
239
|
+
|
240
|
+
if (builtinFuncs[name] || internalConstrs[name]) {
|
241
|
+
// todo: return an actual something
|
242
|
+
return number(1);
|
243
|
+
}
|
244
|
+
|
245
|
+
if (local === undefined) {
|
246
|
+
// no local var with name
|
247
|
+
if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
|
248
|
+
if (funcIndex[name] !== undefined) return number(funcIndex[name]);
|
249
|
+
|
250
|
+
if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
251
|
+
}
|
252
|
+
|
253
|
+
if (local === undefined && rawName.startsWith('__')) {
|
254
|
+
// return undefined if unknown key in already known var
|
255
|
+
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
256
|
+
if (parent.includes('_')) parent = '__' + parent;
|
257
|
+
|
258
|
+
const parentLookup = lookup(parent);
|
259
|
+
if (!parentLookup[1]) return number(UNDEFINED);
|
260
|
+
}
|
261
|
+
|
262
|
+
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
263
|
+
|
264
|
+
return [ [ Opcodes.local_get, local.idx ] ];
|
265
|
+
};
|
266
|
+
|
267
|
+
return lookup(decl.name);
|
268
|
+
};
|
269
|
+
|
270
|
+
const generateReturn = (scope, decl) => {
|
271
|
+
if (decl.argument === null) {
|
272
|
+
if (!scope.returnType) scope.returnType = TYPES.undefined;
|
273
|
+
|
274
|
+
// just bare "return"
|
275
|
+
return [
|
276
|
+
...number(UNDEFINED), // "undefined" if func returns
|
277
|
+
[ Opcodes.return ]
|
278
|
+
];
|
279
|
+
}
|
280
|
+
|
281
|
+
if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
|
282
|
+
|
283
|
+
return [
|
284
|
+
...generate(scope, decl.argument),
|
285
|
+
[ Opcodes.return ]
|
286
|
+
];
|
287
|
+
};
|
288
|
+
|
289
|
+
const localTmp = (scope, name, type = valtypeBinary) => {
|
290
|
+
if (scope.locals[name]) return scope.locals[name].idx;
|
291
|
+
|
292
|
+
let idx = scope.localInd++;
|
293
|
+
scope.locals[name] = { idx, type };
|
294
|
+
|
295
|
+
return idx;
|
296
|
+
};
|
297
|
+
|
298
|
+
const performLogicOp = (scope, op, left, right) => {
|
299
|
+
const checks = {
|
300
|
+
'||': Opcodes.eqz,
|
301
|
+
'&&': [ Opcodes.i32_to ]
|
302
|
+
// todo: ??
|
303
|
+
};
|
304
|
+
|
305
|
+
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
306
|
+
|
307
|
+
// generic structure for {a} OP {b}
|
308
|
+
// -->
|
309
|
+
// _ = {a}; if (OP_CHECK) {b} else _
|
310
|
+
return [
|
311
|
+
...left,
|
312
|
+
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
313
|
+
...checks[op],
|
314
|
+
[ Opcodes.if, valtypeBinary ],
|
315
|
+
...right,
|
316
|
+
[ Opcodes.else ],
|
317
|
+
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
318
|
+
[ Opcodes.end ]
|
319
|
+
];
|
320
|
+
};
|
321
|
+
|
322
|
+
const concatStrings = (scope, left, right, global, name, assign) => {
|
323
|
+
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
324
|
+
// todo: convert left and right to strings if not
|
325
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
326
|
+
// todo: optimize this if using literals/known lengths?
|
327
|
+
|
328
|
+
scope.memory = true;
|
329
|
+
|
330
|
+
const pointer = arrays.get(name ?? '$undeclared');
|
331
|
+
|
332
|
+
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
|
+
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
|
+
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
335
|
+
|
336
|
+
if (assign) {
|
337
|
+
return [
|
338
|
+
// setup right
|
339
|
+
...right,
|
340
|
+
Opcodes.i32_to_u,
|
341
|
+
[ Opcodes.local_set, rightPointer ],
|
342
|
+
|
343
|
+
// calculate length
|
344
|
+
...number(0, Valtype.i32), // base 0 for store later
|
345
|
+
|
346
|
+
...number(pointer, Valtype.i32),
|
347
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
348
|
+
[ Opcodes.local_tee, leftLength ],
|
349
|
+
|
350
|
+
[ Opcodes.local_get, rightPointer ],
|
351
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
352
|
+
[ Opcodes.local_tee, rightLength ],
|
353
|
+
|
354
|
+
[ Opcodes.i32_add ],
|
355
|
+
|
356
|
+
// store length
|
357
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
358
|
+
|
359
|
+
// copy right
|
360
|
+
// dst = out pointer + length size + current length * i16 size
|
361
|
+
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
362
|
+
|
363
|
+
[ Opcodes.local_get, leftLength ],
|
364
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
365
|
+
[ Opcodes.i32_mul ],
|
366
|
+
[ Opcodes.i32_add ],
|
367
|
+
|
368
|
+
// src = right pointer + length size
|
369
|
+
[ Opcodes.local_get, rightPointer ],
|
370
|
+
...number(ValtypeSize.i32, Valtype.i32),
|
371
|
+
[ Opcodes.i32_add ],
|
372
|
+
|
373
|
+
// size = right length * i16 size
|
374
|
+
[ Opcodes.local_get, rightLength ],
|
375
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
376
|
+
[ Opcodes.i32_mul ],
|
377
|
+
|
378
|
+
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
379
|
+
|
380
|
+
// return new string (page)
|
381
|
+
...number(pointer)
|
382
|
+
];
|
383
|
+
}
|
384
|
+
|
385
|
+
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
386
|
+
|
387
|
+
const newOut = makeArray(scope, {
|
388
|
+
rawElements: new Array(0)
|
389
|
+
}, global, name, true, 'i16');
|
390
|
+
|
391
|
+
return [
|
392
|
+
// setup new/out array
|
393
|
+
...newOut,
|
394
|
+
[ Opcodes.drop ],
|
395
|
+
|
396
|
+
// setup left
|
397
|
+
...left,
|
398
|
+
Opcodes.i32_to_u,
|
399
|
+
[ Opcodes.local_set, leftPointer ],
|
400
|
+
|
401
|
+
// setup right
|
402
|
+
...right,
|
403
|
+
Opcodes.i32_to_u,
|
404
|
+
[ Opcodes.local_set, rightPointer ],
|
405
|
+
|
406
|
+
// calculate length
|
407
|
+
...number(0, Valtype.i32), // base 0 for store later
|
408
|
+
|
409
|
+
[ Opcodes.local_get, leftPointer ],
|
410
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
411
|
+
[ Opcodes.local_tee, leftLength ],
|
412
|
+
|
413
|
+
[ Opcodes.local_get, rightPointer ],
|
414
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
415
|
+
[ Opcodes.local_tee, rightLength ],
|
416
|
+
|
417
|
+
[ Opcodes.i32_add ],
|
418
|
+
|
419
|
+
// store length
|
420
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
421
|
+
|
422
|
+
// copy left
|
423
|
+
// dst = out pointer + length size
|
424
|
+
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
425
|
+
|
426
|
+
// src = left pointer + length size
|
427
|
+
[ Opcodes.local_get, leftPointer ],
|
428
|
+
...number(ValtypeSize.i32, Valtype.i32),
|
429
|
+
[ Opcodes.i32_add ],
|
430
|
+
|
431
|
+
// size = PageSize - length size. we do not need to calculate length as init value
|
432
|
+
...number(pageSize - ValtypeSize.i32, Valtype.i32),
|
433
|
+
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
434
|
+
|
435
|
+
// copy right
|
436
|
+
// dst = out pointer + length size + left length * i16 size
|
437
|
+
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
438
|
+
|
439
|
+
[ Opcodes.local_get, leftLength ],
|
440
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
441
|
+
[ Opcodes.i32_mul ],
|
442
|
+
[ Opcodes.i32_add ],
|
443
|
+
|
444
|
+
// src = right pointer + length size
|
445
|
+
[ Opcodes.local_get, rightPointer ],
|
446
|
+
...number(ValtypeSize.i32, Valtype.i32),
|
447
|
+
[ Opcodes.i32_add ],
|
448
|
+
|
449
|
+
// size = right length * i16 size
|
450
|
+
[ Opcodes.local_get, rightLength ],
|
451
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
452
|
+
[ Opcodes.i32_mul ],
|
453
|
+
|
454
|
+
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
455
|
+
|
456
|
+
// return new string (page)
|
457
|
+
...number(pointer)
|
458
|
+
];
|
459
|
+
};
|
460
|
+
|
461
|
+
const falsy = (scope, wasm, type) => {
|
462
|
+
// arrays are always truthy
|
463
|
+
if (type === TYPES._array) return [
|
464
|
+
...wasm,
|
465
|
+
[ Opcodes.drop ],
|
466
|
+
number(0)
|
467
|
+
];
|
468
|
+
|
469
|
+
if (type === TYPES.string) {
|
470
|
+
// if "" (length = 0)
|
471
|
+
return [
|
472
|
+
// pointer
|
473
|
+
...wasm,
|
474
|
+
|
475
|
+
// get length
|
476
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
477
|
+
|
478
|
+
// if length == 0
|
479
|
+
[ Opcodes.i32_eqz ],
|
480
|
+
Opcodes.i32_from_u
|
481
|
+
]
|
482
|
+
}
|
483
|
+
|
484
|
+
// if = 0
|
485
|
+
return [
|
486
|
+
...wasm,
|
487
|
+
|
488
|
+
...Opcodes.eqz,
|
489
|
+
Opcodes.i32_from_u
|
490
|
+
];
|
491
|
+
};
|
492
|
+
|
493
|
+
const truthy = (scope, wasm, type) => {
|
494
|
+
// arrays are always truthy
|
495
|
+
if (type === TYPES._array) return [
|
496
|
+
...wasm,
|
497
|
+
[ Opcodes.drop ],
|
498
|
+
number(1)
|
499
|
+
];
|
500
|
+
|
501
|
+
if (type === TYPES.string) {
|
502
|
+
// if not "" (length = 0)
|
503
|
+
return [
|
504
|
+
// pointer
|
505
|
+
...wasm,
|
506
|
+
|
507
|
+
// get length
|
508
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
509
|
+
|
510
|
+
// if length != 0
|
511
|
+
/* [ Opcodes.i32_eqz ],
|
512
|
+
[ Opcodes.i32_eqz ], */
|
513
|
+
Opcodes.i32_from_u
|
514
|
+
]
|
515
|
+
}
|
516
|
+
|
517
|
+
// if != 0
|
518
|
+
return [
|
519
|
+
...wasm,
|
520
|
+
|
521
|
+
/* Opcodes.eqz,
|
522
|
+
[ Opcodes.i32_eqz ],
|
523
|
+
Opcodes.i32_from */
|
524
|
+
];
|
525
|
+
};
|
526
|
+
|
527
|
+
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
|
528
|
+
if (op === '||' || op === '&&' || op === '??') {
|
529
|
+
return performLogicOp(scope, op, left, right);
|
530
|
+
}
|
531
|
+
|
532
|
+
if (leftType === TYPES.string || rightType === TYPES.string) {
|
533
|
+
if (op === '+') {
|
534
|
+
// string concat (a + b)
|
535
|
+
return concatStrings(scope, left, right, _global, _name, assign);
|
536
|
+
}
|
537
|
+
|
538
|
+
// any other math op, NaN
|
539
|
+
if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
|
540
|
+
|
541
|
+
// else leave bool ops
|
542
|
+
// todo: convert string to number if string and number or le/ge op
|
543
|
+
// todo: string equality
|
544
|
+
}
|
545
|
+
|
546
|
+
let ops = operatorOpcode[valtype][op];
|
547
|
+
|
548
|
+
// some complex ops are implemented as builtin funcs
|
549
|
+
const builtinName = `${valtype}_${op}`;
|
550
|
+
if (!ops && builtinFuncs[builtinName]) {
|
551
|
+
includeBuiltin(scope, builtinName);
|
552
|
+
const idx = funcIndex[builtinName];
|
553
|
+
|
554
|
+
return [
|
555
|
+
...left,
|
556
|
+
...right,
|
557
|
+
[ Opcodes.call, idx ]
|
558
|
+
];
|
559
|
+
}
|
560
|
+
|
561
|
+
if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
|
562
|
+
|
563
|
+
if (!Array.isArray(ops)) ops = [ ops ];
|
564
|
+
|
565
|
+
return [
|
566
|
+
...left,
|
567
|
+
...right,
|
568
|
+
ops
|
569
|
+
];
|
570
|
+
};
|
571
|
+
|
572
|
+
const generateBinaryExp = (scope, decl, _global, _name) => {
|
573
|
+
const out = [
|
574
|
+
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
575
|
+
];
|
576
|
+
|
577
|
+
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
578
|
+
|
579
|
+
return out;
|
580
|
+
};
|
581
|
+
|
582
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
|
583
|
+
const existing = funcs.find(x => x.name === name);
|
584
|
+
if (existing) return existing;
|
585
|
+
|
586
|
+
const nameParam = i => localNames[i] ?? (i >= params.length ? ['a', 'b', 'c'][i - params.length] : ['x', 'y', 'z'][i]);
|
587
|
+
|
588
|
+
const allLocals = params.concat(localTypes);
|
589
|
+
const locals = {};
|
590
|
+
for (let i = 0; i < allLocals.length; i++) {
|
591
|
+
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
592
|
+
}
|
593
|
+
|
594
|
+
let baseGlobalIdx, i = 0;
|
595
|
+
for (const type of globalTypes) {
|
596
|
+
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
597
|
+
|
598
|
+
globals[globalNames[i] ?? `${name}_global_${i}`] = { idx: globalInd++, type, init: globalInits[i] ?? 0 };
|
599
|
+
i++;
|
600
|
+
}
|
601
|
+
|
602
|
+
if (globalTypes.length !== 0) {
|
603
|
+
// offset global ops for base global idx
|
604
|
+
for (const inst of wasm) {
|
605
|
+
if (inst[0] === Opcodes.global_get || inst[0] === Opcodes.global_set) {
|
606
|
+
inst[1] += baseGlobalIdx;
|
607
|
+
}
|
608
|
+
}
|
609
|
+
}
|
610
|
+
|
611
|
+
const func = {
|
612
|
+
name,
|
613
|
+
params,
|
614
|
+
locals,
|
615
|
+
returns,
|
616
|
+
returnType: TYPES[returnType ?? 'number'],
|
617
|
+
wasm,
|
618
|
+
memory,
|
619
|
+
internal: true,
|
620
|
+
index: currentFuncIndex++
|
621
|
+
};
|
622
|
+
|
623
|
+
funcs.push(func);
|
624
|
+
funcIndex[name] = func.index;
|
625
|
+
|
626
|
+
return func;
|
627
|
+
};
|
628
|
+
|
629
|
+
const includeBuiltin = (scope, builtin) => {
|
630
|
+
const code = builtinFuncs[builtin];
|
631
|
+
if (code.wasm) return asmFunc(builtin, code);
|
632
|
+
|
633
|
+
return code.body.map(x => generate(scope, x));
|
634
|
+
};
|
635
|
+
|
636
|
+
const generateLogicExp = (scope, decl) => {
|
637
|
+
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
|
638
|
+
};
|
639
|
+
|
640
|
+
const TYPES = {
|
641
|
+
number: 0xffffffffffff0,
|
642
|
+
boolean: 0xffffffffffff1,
|
643
|
+
string: 0xffffffffffff2,
|
644
|
+
undefined: 0xffffffffffff3,
|
645
|
+
object: 0xffffffffffff4,
|
646
|
+
function: 0xffffffffffff5,
|
647
|
+
symbol: 0xffffffffffff6,
|
648
|
+
bigint: 0xffffffffffff7,
|
649
|
+
|
650
|
+
// these are not "typeof" types but tracked internally
|
651
|
+
_array: 0xffffffffffff8
|
652
|
+
};
|
653
|
+
|
654
|
+
const TYPE_NAMES = {
|
655
|
+
[TYPES.number]: 'Number',
|
656
|
+
[TYPES.boolean]: 'Boolean',
|
657
|
+
[TYPES.string]: 'String',
|
658
|
+
[TYPES.undefined]: 'undefined',
|
659
|
+
[TYPES.object]: 'Object',
|
660
|
+
[TYPES.function]: 'Function',
|
661
|
+
[TYPES.symbol]: 'Symbol',
|
662
|
+
[TYPES.bigint]: 'BigInt',
|
663
|
+
|
664
|
+
[TYPES._array]: 'Array'
|
665
|
+
};
|
666
|
+
|
667
|
+
let typeStates = {};
|
668
|
+
|
669
|
+
const getType = (scope, _name) => {
|
670
|
+
const name = mapName(_name);
|
671
|
+
if (scope.locals[name]) return typeStates[name];
|
672
|
+
|
673
|
+
if (builtinVars[name]) return TYPES[builtinVars[name].type ?? 'number'];
|
674
|
+
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) return TYPES.function;
|
675
|
+
if (globals[name]) return typeStates[name];
|
676
|
+
|
677
|
+
if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)]) return TYPES.function;
|
678
|
+
if (name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) return TYPES.function;
|
679
|
+
|
680
|
+
return TYPES.undefined;
|
681
|
+
};
|
682
|
+
|
683
|
+
const getNodeType = (scope, node) => {
|
684
|
+
if (node.type === 'Literal') {
|
685
|
+
return TYPES[typeof node.value];
|
686
|
+
}
|
687
|
+
|
688
|
+
if (isFuncType(node.type)) {
|
689
|
+
return TYPES.function;
|
690
|
+
}
|
691
|
+
|
692
|
+
if (node.type === 'Identifier') {
|
693
|
+
return getType(scope, node.name);
|
694
|
+
}
|
695
|
+
|
696
|
+
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
697
|
+
const name = node.callee.name;
|
698
|
+
const func = funcs.find(x => x.name === name);
|
699
|
+
if (func) return func.returnType ?? TYPES.number;
|
700
|
+
|
701
|
+
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
702
|
+
if (internalConstrs[name]) return internalConstrs[name].type;
|
703
|
+
|
704
|
+
let protoFunc;
|
705
|
+
// ident.func()
|
706
|
+
if (name && name.startsWith('__')) {
|
707
|
+
const spl = name.slice(2).split('_');
|
708
|
+
|
709
|
+
const baseName = spl.slice(0, -1).join('_');
|
710
|
+
const baseType = getType(scope, baseName);
|
711
|
+
|
712
|
+
const func = spl[spl.length - 1];
|
713
|
+
protoFunc = prototypeFuncs[baseType]?.[func];
|
714
|
+
}
|
715
|
+
|
716
|
+
// literal.func()
|
717
|
+
if (!name && node.callee.type === 'MemberExpression') {
|
718
|
+
const baseType = getNodeType(scope, node.callee.object);
|
719
|
+
|
720
|
+
const func = node.callee.property.name;
|
721
|
+
protoFunc = prototypeFuncs[baseType]?.[func];
|
722
|
+
}
|
723
|
+
|
724
|
+
if (protoFunc) return protoFunc.returnType ?? TYPES.number;
|
725
|
+
|
726
|
+
return TYPES.number;
|
727
|
+
}
|
728
|
+
|
729
|
+
if (node.type === 'ExpressionStatement') {
|
730
|
+
return getNodeType(scope, node.expression);
|
731
|
+
}
|
732
|
+
|
733
|
+
if (node.type === 'AssignmentExpression') {
|
734
|
+
return getNodeType(scope, node.right);
|
735
|
+
}
|
736
|
+
|
737
|
+
if (node.type === 'ArrayExpression') {
|
738
|
+
return TYPES._array;
|
739
|
+
}
|
740
|
+
|
741
|
+
if (node.type === 'BinaryExpression') {
|
742
|
+
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
743
|
+
|
744
|
+
if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
|
745
|
+
}
|
746
|
+
|
747
|
+
if (node.type === 'UnaryExpression') {
|
748
|
+
if (node.operator === '!') return TYPES.boolean;
|
749
|
+
if (node.operator === 'void') return TYPES.undefined;
|
750
|
+
if (node.operator === 'delete') return TYPES.boolean;
|
751
|
+
}
|
752
|
+
|
753
|
+
if (node.type === 'MemberExpression') {
|
754
|
+
const objectType = getNodeType(scope, node.object);
|
755
|
+
|
756
|
+
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
757
|
+
}
|
758
|
+
|
759
|
+
// default to number
|
760
|
+
return TYPES.number;
|
761
|
+
};
|
762
|
+
|
763
|
+
const generateLiteral = (scope, decl, global, name) => {
|
764
|
+
if (decl.value === null) return number(NULL);
|
765
|
+
|
766
|
+
switch (typeof decl.value) {
|
767
|
+
case 'number':
|
768
|
+
return number(decl.value);
|
769
|
+
|
770
|
+
case 'boolean':
|
771
|
+
// hack: bool as int (1/0)
|
772
|
+
return number(decl.value ? 1 : 0);
|
773
|
+
|
774
|
+
case 'string':
|
775
|
+
// this is a terrible hack which changes type strings ("number" etc) to known const number values
|
776
|
+
switch (decl.value) {
|
777
|
+
case 'number': return number(TYPES.number);
|
778
|
+
case 'boolean': return number(TYPES.boolean);
|
779
|
+
case 'string': return number(TYPES.string);
|
780
|
+
case 'undefined': return number(TYPES.undefined);
|
781
|
+
case 'object': return number(TYPES.object);
|
782
|
+
case 'function': return number(TYPES.function);
|
783
|
+
case 'symbol': return number(TYPES.symbol);
|
784
|
+
case 'bigint': return number(TYPES.bigint);
|
785
|
+
}
|
786
|
+
|
787
|
+
const str = decl.value;
|
788
|
+
const rawElements = new Array(str.length);
|
789
|
+
for (let i = 0; i < str.length; i++) {
|
790
|
+
rawElements[i] = str.charCodeAt(i);
|
791
|
+
}
|
792
|
+
|
793
|
+
return makeArray(scope, {
|
794
|
+
rawElements
|
795
|
+
}, global, name, false, 'i16');
|
796
|
+
|
797
|
+
default:
|
798
|
+
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
799
|
+
}
|
800
|
+
};
|
801
|
+
|
802
|
+
const countLeftover = wasm => {
|
803
|
+
let count = 0, depth = 0;
|
804
|
+
|
805
|
+
for (const inst of wasm) {
|
806
|
+
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
807
|
+
if (inst[0] === Opcodes.if) count--;
|
808
|
+
if (inst[1] !== Blocktype.void) count++;
|
809
|
+
}
|
810
|
+
if ([Opcodes.if, Opcodes.try, Opcodes.loop, Opcodes.block].includes(inst[0])) depth++;
|
811
|
+
if (inst[0] === Opcodes.end) depth--;
|
812
|
+
|
813
|
+
if (depth === 0)
|
814
|
+
if ([Opcodes.throw, Opcodes.return, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
815
|
+
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
816
|
+
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
817
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
818
|
+
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
819
|
+
else if (inst[0] === Opcodes.call) {
|
820
|
+
let func = funcs.find(x => x.index === inst[1]);
|
821
|
+
if (func) {
|
822
|
+
count -= func.params.length;
|
823
|
+
} else count--;
|
824
|
+
if (func) count += func.returns.length;
|
825
|
+
} else count--;
|
826
|
+
}
|
827
|
+
|
828
|
+
return count;
|
829
|
+
};
|
830
|
+
|
831
|
+
const disposeLeftover = wasm => {
|
832
|
+
let leftover = countLeftover(wasm);
|
833
|
+
|
834
|
+
for (let i = 0; i < leftover; i++) wasm.push([ Opcodes.drop ]);
|
835
|
+
};
|
836
|
+
|
837
|
+
const generateExp = (scope, decl) => {
|
838
|
+
const expression = decl.expression;
|
839
|
+
|
840
|
+
const out = generate(scope, expression);
|
841
|
+
disposeLeftover(out);
|
842
|
+
|
843
|
+
return out;
|
844
|
+
};
|
845
|
+
|
846
|
+
const arrayUtil = {
|
847
|
+
getLengthI32: pointer => [
|
848
|
+
...number(0, Valtype.i32),
|
849
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
850
|
+
],
|
851
|
+
|
852
|
+
getLength: pointer => [
|
853
|
+
...number(0, Valtype.i32),
|
854
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
855
|
+
Opcodes.i32_from_u
|
856
|
+
],
|
857
|
+
|
858
|
+
setLengthI32: (pointer, value) => [
|
859
|
+
...number(0, Valtype.i32),
|
860
|
+
...value,
|
861
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
862
|
+
],
|
863
|
+
|
864
|
+
setLength: (pointer, value) => [
|
865
|
+
...number(0, Valtype.i32),
|
866
|
+
...value,
|
867
|
+
Opcodes.i32_to_u,
|
868
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
869
|
+
]
|
870
|
+
};
|
871
|
+
|
872
|
+
const generateCall = (scope, decl, _global, _name) => {
|
873
|
+
/* const callee = decl.callee;
|
874
|
+
const args = decl.arguments;
|
875
|
+
|
876
|
+
return [
|
877
|
+
...generate(args),
|
878
|
+
...generate(callee),
|
879
|
+
Opcodes.call_indirect,
|
880
|
+
]; */
|
881
|
+
|
882
|
+
let name = mapName(decl.callee.name);
|
883
|
+
if (isFuncType(decl.callee.type)) { // iife
|
884
|
+
const func = generateFunc(scope, decl.callee);
|
885
|
+
name = func.name;
|
886
|
+
}
|
887
|
+
|
888
|
+
if (name === 'eval' && decl.arguments[0].type === 'Literal') {
|
889
|
+
// literal eval hack
|
890
|
+
const code = decl.arguments[0].value;
|
891
|
+
const parsed = parse(code, []);
|
892
|
+
|
893
|
+
const out = generate(scope, {
|
894
|
+
type: 'BlockStatement',
|
895
|
+
body: parsed.body
|
896
|
+
});
|
897
|
+
|
898
|
+
const lastInst = out[out.length - 1];
|
899
|
+
if (lastInst && lastInst[0] === Opcodes.drop) {
|
900
|
+
out.splice(out.length - 1, 1);
|
901
|
+
} else if (countLeftover(out) === 0) {
|
902
|
+
out.push(...number(UNDEFINED));
|
903
|
+
}
|
904
|
+
|
905
|
+
return out;
|
906
|
+
}
|
907
|
+
|
908
|
+
let out = [];
|
909
|
+
let protoFunc, protoName, baseType, baseName = '$undeclared';
|
910
|
+
// ident.func()
|
911
|
+
if (name && name.startsWith('__')) {
|
912
|
+
const spl = name.slice(2).split('_');
|
913
|
+
|
914
|
+
baseName = spl.slice(0, -1).join('_');
|
915
|
+
baseType = getType(scope, baseName);
|
916
|
+
|
917
|
+
const func = spl[spl.length - 1];
|
918
|
+
protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
|
919
|
+
protoName = func;
|
920
|
+
}
|
921
|
+
|
922
|
+
// literal.func()
|
923
|
+
if (!name && decl.callee.type === 'MemberExpression') {
|
924
|
+
baseType = getNodeType(scope, decl.callee.object);
|
925
|
+
|
926
|
+
const func = decl.callee.property.name;
|
927
|
+
protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
|
928
|
+
protoName = func;
|
929
|
+
|
930
|
+
out = generate(scope, decl.callee.object);
|
931
|
+
out.push([ Opcodes.drop ]);
|
932
|
+
}
|
933
|
+
|
934
|
+
if (protoFunc) {
|
935
|
+
scope.memory = true;
|
936
|
+
|
937
|
+
let pointer = arrays.get(baseName);
|
938
|
+
|
939
|
+
if (pointer == null) {
|
940
|
+
// unknown dynamic pointer, so clone to new pointer which we know aot. now that's what I call inefficient™
|
941
|
+
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
942
|
+
|
943
|
+
// register array
|
944
|
+
makeArray(scope, {
|
945
|
+
rawElements: new Array(0)
|
946
|
+
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
947
|
+
pointer = arrays.get(baseName);
|
948
|
+
|
949
|
+
const [ local, isGlobal ] = lookupName(scope, baseName);
|
950
|
+
|
951
|
+
out = [
|
952
|
+
// clone to new pointer
|
953
|
+
...number(pointer, Valtype.i32), // dst = new pointer
|
954
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ], // src = unknown pointer
|
955
|
+
Opcodes.i32_to_u,
|
956
|
+
...number(pageSize, Valtype.i32), // size = pagesize
|
957
|
+
|
958
|
+
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
959
|
+
];
|
960
|
+
}
|
961
|
+
|
962
|
+
if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
|
963
|
+
|
964
|
+
let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
|
965
|
+
|
966
|
+
// use local for cached i32 length as commonly used
|
967
|
+
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
968
|
+
|
969
|
+
return [
|
970
|
+
...out,
|
971
|
+
|
972
|
+
...arrayUtil.getLengthI32(pointer),
|
973
|
+
[ Opcodes.local_set, lengthLocal ],
|
974
|
+
|
975
|
+
[ Opcodes.block, valtypeBinary ],
|
976
|
+
...protoFunc(pointer, {
|
977
|
+
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
978
|
+
get: arrayUtil.getLength(pointer),
|
979
|
+
getI32: arrayUtil.getLengthI32(pointer),
|
980
|
+
set: value => arrayUtil.setLength(pointer, value),
|
981
|
+
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
982
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
983
|
+
const out = makeArray(scope, {
|
984
|
+
rawElements: new Array(length)
|
985
|
+
}, _global, _name, true, itemType);
|
986
|
+
return [ out, arrays.get(_name ?? '$undeclared') ];
|
987
|
+
}),
|
988
|
+
[ Opcodes.end ]
|
989
|
+
];
|
990
|
+
}
|
991
|
+
|
992
|
+
// TODO: only allows callee as literal
|
993
|
+
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
994
|
+
|
995
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
996
|
+
if (idx === undefined && builtinFuncs[name]) {
|
997
|
+
if (builtinFuncs[name].floatOnly && valtype !== 'f64') throw new Error(`Cannot use built-in ${unhackName(name)} with integer valtype`);
|
998
|
+
|
999
|
+
includeBuiltin(scope, name);
|
1000
|
+
idx = funcIndex[name];
|
1001
|
+
|
1002
|
+
// infer arguments types from builtins params
|
1003
|
+
const func = funcs.find(x => x.name === name);
|
1004
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
1005
|
+
const arg = decl.arguments[i];
|
1006
|
+
if (!arg.name) continue;
|
1007
|
+
|
1008
|
+
const local = scope.locals[arg.name];
|
1009
|
+
if (!local) continue;
|
1010
|
+
|
1011
|
+
local.type = func.params[i];
|
1012
|
+
if (local.type === Valtype.v128) {
|
1013
|
+
// specify vec subtype inferred from last vec type in function name
|
1014
|
+
local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1015
|
+
}
|
1016
|
+
}
|
1017
|
+
}
|
1018
|
+
|
1019
|
+
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1020
|
+
|
1021
|
+
if (idx === undefined && name === scope.name) {
|
1022
|
+
// hack: calling self, func generator will fix later
|
1023
|
+
idx = -1;
|
1024
|
+
}
|
1025
|
+
|
1026
|
+
if (idx === undefined) {
|
1027
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
|
1028
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1029
|
+
}
|
1030
|
+
|
1031
|
+
const func = funcs.find(x => x.index === idx);
|
1032
|
+
|
1033
|
+
let args = decl.arguments;
|
1034
|
+
if (func && args.length < func.params.length) {
|
1035
|
+
// too little args, push undefineds
|
1036
|
+
args = args.concat(new Array(func.params.length - args.length).fill(DEFAULT_VALUE));
|
1037
|
+
}
|
1038
|
+
|
1039
|
+
if (func && args.length > func.params.length) {
|
1040
|
+
// too many args, slice extras off
|
1041
|
+
args = args.slice(0, func.params.length);
|
1042
|
+
}
|
1043
|
+
|
1044
|
+
if (func && func.memory) scope.memory = true;
|
1045
|
+
if (func && func.throws) scope.throws = true;
|
1046
|
+
|
1047
|
+
for (const arg of args) {
|
1048
|
+
out.push(...generate(scope, arg));
|
1049
|
+
}
|
1050
|
+
|
1051
|
+
out.push([ Opcodes.call, idx ]);
|
1052
|
+
|
1053
|
+
return out;
|
1054
|
+
};
|
1055
|
+
|
1056
|
+
const generateNew = (scope, decl, _global, _name) => {
|
1057
|
+
// hack: basically treat this as a normal call for builtins for now
|
1058
|
+
const name = mapName(decl.callee.name);
|
1059
|
+
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1060
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1061
|
+
|
1062
|
+
return generateCall(scope, decl, _global, _name);
|
1063
|
+
};
|
1064
|
+
|
1065
|
+
// bad hack for undefined and null working without additional logic
|
1066
|
+
const DEFAULT_VALUE = {
|
1067
|
+
type: 'Identifier',
|
1068
|
+
name: 'undefined'
|
1069
|
+
};
|
1070
|
+
|
1071
|
+
const unhackName = name => {
|
1072
|
+
if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
|
1073
|
+
return name;
|
1074
|
+
};
|
1075
|
+
|
1076
|
+
const generateVar = (scope, decl) => {
|
1077
|
+
const out = [];
|
1078
|
+
|
1079
|
+
const topLevel = scope.name === 'main';
|
1080
|
+
|
1081
|
+
// global variable if in top scope (main) and var ..., or if wanted
|
1082
|
+
const global = decl.kind === 'var';
|
1083
|
+
const target = global ? globals : scope.locals;
|
1084
|
+
|
1085
|
+
for (const x of decl.declarations) {
|
1086
|
+
const name = mapName(x.id.name);
|
1087
|
+
|
1088
|
+
if (x.init && isFuncType(x.init.type)) {
|
1089
|
+
// hack for let a = function () { ... }
|
1090
|
+
x.init.id = { name };
|
1091
|
+
generateFunc(scope, x.init);
|
1092
|
+
continue;
|
1093
|
+
}
|
1094
|
+
|
1095
|
+
// console.log(name);
|
1096
|
+
if (topLevel && builtinVars[name]) {
|
1097
|
+
// cannot redeclare
|
1098
|
+
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1099
|
+
|
1100
|
+
continue; // always ignore
|
1101
|
+
}
|
1102
|
+
|
1103
|
+
let idx;
|
1104
|
+
// already declared
|
1105
|
+
if (target[name]) {
|
1106
|
+
// parser should catch this but sanity check anyway
|
1107
|
+
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1108
|
+
|
1109
|
+
idx = target[name].idx;
|
1110
|
+
} else {
|
1111
|
+
idx = global ? globalInd++ : scope.localInd++;
|
1112
|
+
target[name] = { idx, type: valtypeBinary };
|
1113
|
+
}
|
1114
|
+
|
1115
|
+
typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
|
1116
|
+
|
1117
|
+
// x.init ??= DEFAULT_VALUE;
|
1118
|
+
if (x.init) {
|
1119
|
+
out.push(...generate(scope, x.init, global, name));
|
1120
|
+
|
1121
|
+
// if our value is the result of a function, infer the type from that func's return value
|
1122
|
+
if (out[out.length - 1][0] === Opcodes.call) {
|
1123
|
+
const ind = out[out.length - 1][1];
|
1124
|
+
if (ind >= importedFuncs.length) { // not an imported func
|
1125
|
+
const func = funcs.find(x => x.index === ind);
|
1126
|
+
if (!func) throw new Error('could not find func being called as var value to infer type'); // sanity check
|
1127
|
+
|
1128
|
+
const returns = func.returns;
|
1129
|
+
if (returns.length > 1) throw new Error('func returning >1 value being set as 1 local'); // sanity check
|
1130
|
+
|
1131
|
+
target[name].type = func.returns[0];
|
1132
|
+
if (target[name].type === Valtype.v128) {
|
1133
|
+
// specify vec subtype inferred from first vec type in function name
|
1134
|
+
target[name].vecType = func.name.split('_').find(x => x.includes('x'));
|
1135
|
+
}
|
1136
|
+
} else {
|
1137
|
+
// we do not have imports that return yet, ignore for now
|
1138
|
+
}
|
1139
|
+
}
|
1140
|
+
|
1141
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1142
|
+
}
|
1143
|
+
}
|
1144
|
+
|
1145
|
+
return out;
|
1146
|
+
};
|
1147
|
+
|
1148
|
+
const generateAssign = (scope, decl) => {
|
1149
|
+
const { type, name } = decl.left;
|
1150
|
+
|
1151
|
+
if (type === 'ObjectPattern') {
|
1152
|
+
// hack: ignore object parts of `var a = {} = 2`
|
1153
|
+
return generate(scope, decl.right);
|
1154
|
+
}
|
1155
|
+
|
1156
|
+
if (isFuncType(decl.right.type)) {
|
1157
|
+
// hack for a = function () { ... }
|
1158
|
+
decl.right.id = { name };
|
1159
|
+
generateFunc(scope, decl.right);
|
1160
|
+
return [];
|
1161
|
+
}
|
1162
|
+
|
1163
|
+
// hack: .length setter
|
1164
|
+
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1165
|
+
const name = decl.left.object.name;
|
1166
|
+
const pointer = arrays.get(name);
|
1167
|
+
|
1168
|
+
scope.memory = true;
|
1169
|
+
|
1170
|
+
const aotPointer = pointer != null;
|
1171
|
+
|
1172
|
+
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
1173
|
+
|
1174
|
+
return [
|
1175
|
+
...(aotPointer ? number(0, Valtype.i32) : [
|
1176
|
+
...generate(scope, decl.left.object),
|
1177
|
+
Opcodes.i32_to_u
|
1178
|
+
]),
|
1179
|
+
|
1180
|
+
...generate(scope, decl.right, false, name),
|
1181
|
+
[ Opcodes.local_tee, newValueTmp ],
|
1182
|
+
|
1183
|
+
Opcodes.i32_to_u,
|
1184
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
|
1185
|
+
|
1186
|
+
[ Opcodes.local_get, newValueTmp ]
|
1187
|
+
];
|
1188
|
+
}
|
1189
|
+
|
1190
|
+
const [ local, isGlobal ] = lookupName(scope, name);
|
1191
|
+
|
1192
|
+
if (local === undefined) {
|
1193
|
+
// todo: this should be a devtools/repl/??? only thing
|
1194
|
+
|
1195
|
+
// only allow = for this
|
1196
|
+
if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1197
|
+
|
1198
|
+
if (builtinVars[name]) {
|
1199
|
+
// just return rhs (eg `NaN = 2`)
|
1200
|
+
return generate(scope, decl.right);
|
1201
|
+
}
|
1202
|
+
|
1203
|
+
// set global and return (eg a = 2)
|
1204
|
+
return [
|
1205
|
+
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1206
|
+
[ Opcodes.global_get, globals[name].idx ]
|
1207
|
+
];
|
1208
|
+
}
|
1209
|
+
|
1210
|
+
if (decl.operator === '=') {
|
1211
|
+
typeStates[name] = getNodeType(scope, decl.right);
|
1212
|
+
|
1213
|
+
return [
|
1214
|
+
...generate(scope, decl.right, isGlobal, name),
|
1215
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1216
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1217
|
+
];
|
1218
|
+
}
|
1219
|
+
|
1220
|
+
return [
|
1221
|
+
...performOp(scope, decl.operator.slice(0, -1), [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1222
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1223
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1224
|
+
];
|
1225
|
+
};
|
1226
|
+
|
1227
|
+
const generateUnary = (scope, decl) => {
|
1228
|
+
switch (decl.operator) {
|
1229
|
+
case '+':
|
1230
|
+
// stub
|
1231
|
+
return generate(scope, decl.argument);
|
1232
|
+
|
1233
|
+
case '-':
|
1234
|
+
// * -1
|
1235
|
+
|
1236
|
+
if (decl.prefix && decl.argument.type === 'Literal' && typeof decl.argument.value === 'number') {
|
1237
|
+
// if -<N>, just return that
|
1238
|
+
return number(-1 * decl.argument.value);
|
1239
|
+
}
|
1240
|
+
|
1241
|
+
return [
|
1242
|
+
...generate(scope, decl.argument),
|
1243
|
+
...(valtype === 'f64' ? [ [ Opcodes.f64_neg ] ] : [ ...number(-1), [ Opcodes.mul ] ])
|
1244
|
+
];
|
1245
|
+
|
1246
|
+
case '!':
|
1247
|
+
// !=
|
1248
|
+
return falsy(scope, generate(scope, decl.argument));
|
1249
|
+
|
1250
|
+
case '~':
|
1251
|
+
return [
|
1252
|
+
...generate(scope, decl.argument),
|
1253
|
+
Opcodes.i32_to,
|
1254
|
+
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1255
|
+
[ Opcodes.i32_xor ],
|
1256
|
+
Opcodes.i32_from
|
1257
|
+
];
|
1258
|
+
|
1259
|
+
case 'void': {
|
1260
|
+
// drop current expression value after running, give undefined
|
1261
|
+
const out = generate(scope, decl.argument);
|
1262
|
+
disposeLeftover(out);
|
1263
|
+
|
1264
|
+
out.push(...number(UNDEFINED));
|
1265
|
+
return out;
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
case 'delete':
|
1269
|
+
let toReturn = true, toGenerate = true;
|
1270
|
+
|
1271
|
+
if (decl.argument.type === 'Identifier') {
|
1272
|
+
const out = generateIdent(scope, decl.argument);
|
1273
|
+
|
1274
|
+
// if ReferenceError (undeclared var), ignore and return true. otherwise false
|
1275
|
+
if (!out[1]) {
|
1276
|
+
// exists
|
1277
|
+
toReturn = false;
|
1278
|
+
} else {
|
1279
|
+
// does not exist (2 ops from throw)
|
1280
|
+
toReturn = true;
|
1281
|
+
toGenerate = false;
|
1282
|
+
}
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
1286
|
+
disposeLeftover(out);
|
1287
|
+
|
1288
|
+
out.push(...number(toReturn ? 1 : 0));
|
1289
|
+
return out;
|
1290
|
+
|
1291
|
+
case 'typeof':
|
1292
|
+
const type = getNodeType(scope, decl.argument);
|
1293
|
+
|
1294
|
+
// for custom types, just return object
|
1295
|
+
if (type > 0xffffffffffff7) return number(TYPES.object);
|
1296
|
+
return number(type);
|
1297
|
+
|
1298
|
+
default:
|
1299
|
+
return todo(`unary operator ${decl.operator} not implemented yet`);
|
1300
|
+
}
|
1301
|
+
};
|
1302
|
+
|
1303
|
+
const generateUpdate = (scope, decl) => {
|
1304
|
+
const { name } = decl.argument;
|
1305
|
+
|
1306
|
+
const [ local, isGlobal ] = lookupName(scope, name);
|
1307
|
+
|
1308
|
+
if (local === undefined) {
|
1309
|
+
return todo(`update expression with undefined variable`);
|
1310
|
+
}
|
1311
|
+
|
1312
|
+
const idx = local.idx;
|
1313
|
+
const out = [];
|
1314
|
+
|
1315
|
+
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1316
|
+
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1317
|
+
|
1318
|
+
switch (decl.operator) {
|
1319
|
+
case '++':
|
1320
|
+
out.push(...number(1), [ Opcodes.add ]);
|
1321
|
+
break;
|
1322
|
+
|
1323
|
+
case '--':
|
1324
|
+
out.push(...number(1), [ Opcodes.sub ]);
|
1325
|
+
break;
|
1326
|
+
}
|
1327
|
+
|
1328
|
+
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1329
|
+
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1330
|
+
|
1331
|
+
return out;
|
1332
|
+
};
|
1333
|
+
|
1334
|
+
const generateIf = (scope, decl) => {
|
1335
|
+
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
1336
|
+
|
1337
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1338
|
+
depth.push('if');
|
1339
|
+
|
1340
|
+
const consOut = generate(scope, decl.consequent);
|
1341
|
+
disposeLeftover(consOut);
|
1342
|
+
out.push(...consOut);
|
1343
|
+
|
1344
|
+
if (decl.alternate) {
|
1345
|
+
out.push([ Opcodes.else ]);
|
1346
|
+
|
1347
|
+
const altOut = generate(scope, decl.alternate);
|
1348
|
+
disposeLeftover(altOut);
|
1349
|
+
out.push(...altOut);
|
1350
|
+
}
|
1351
|
+
|
1352
|
+
out.push([ Opcodes.end ]);
|
1353
|
+
depth.pop();
|
1354
|
+
|
1355
|
+
return out;
|
1356
|
+
};
|
1357
|
+
|
1358
|
+
const generateConditional = (scope, decl) => {
|
1359
|
+
const out = [ ...generate(scope, decl.test) ];
|
1360
|
+
|
1361
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, valtypeBinary ]);
|
1362
|
+
depth.push('if');
|
1363
|
+
|
1364
|
+
out.push(...generate(scope, decl.consequent));
|
1365
|
+
|
1366
|
+
out.push([ Opcodes.else ]);
|
1367
|
+
out.push(...generate(scope, decl.alternate));
|
1368
|
+
|
1369
|
+
out.push([ Opcodes.end ]);
|
1370
|
+
depth.pop();
|
1371
|
+
|
1372
|
+
return out;
|
1373
|
+
};
|
1374
|
+
|
1375
|
+
let depth = [];
|
1376
|
+
const generateFor = (scope, decl) => {
|
1377
|
+
const out = [];
|
1378
|
+
|
1379
|
+
if (decl.init) {
|
1380
|
+
out.push(...generate(scope, decl.init));
|
1381
|
+
disposeLeftover(out);
|
1382
|
+
}
|
1383
|
+
|
1384
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1385
|
+
depth.push('for');
|
1386
|
+
|
1387
|
+
out.push(...generate(scope, decl.test));
|
1388
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1389
|
+
depth.push('if');
|
1390
|
+
|
1391
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
1392
|
+
depth.push('block');
|
1393
|
+
out.push(...generate(scope, decl.body));
|
1394
|
+
out.push([ Opcodes.end ]);
|
1395
|
+
|
1396
|
+
out.push(...generate(scope, decl.update));
|
1397
|
+
depth.pop();
|
1398
|
+
|
1399
|
+
out.push([ Opcodes.br, 1 ]);
|
1400
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
1401
|
+
depth.pop(); depth.pop();
|
1402
|
+
|
1403
|
+
return out;
|
1404
|
+
};
|
1405
|
+
|
1406
|
+
const generateWhile = (scope, decl) => {
|
1407
|
+
const out = [];
|
1408
|
+
|
1409
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1410
|
+
depth.push('while');
|
1411
|
+
|
1412
|
+
out.push(...generate(scope, decl.test));
|
1413
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1414
|
+
depth.push('if');
|
1415
|
+
|
1416
|
+
out.push(...generate(scope, decl.body));
|
1417
|
+
|
1418
|
+
out.push([ Opcodes.br, 1 ]);
|
1419
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
1420
|
+
depth.pop(); depth.pop();
|
1421
|
+
|
1422
|
+
return out;
|
1423
|
+
};
|
1424
|
+
|
1425
|
+
const getNearestLoop = () => {
|
1426
|
+
for (let i = depth.length - 1; i >= 0; i--) {
|
1427
|
+
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1428
|
+
}
|
1429
|
+
|
1430
|
+
return -1;
|
1431
|
+
};
|
1432
|
+
|
1433
|
+
const generateBreak = (scope, decl) => {
|
1434
|
+
const nearestLoop = depth.length - getNearestLoop();
|
1435
|
+
return [
|
1436
|
+
[ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
|
1437
|
+
];
|
1438
|
+
};
|
1439
|
+
|
1440
|
+
const generateContinue = (scope, decl) => {
|
1441
|
+
const nearestLoop = depth.length - getNearestLoop();
|
1442
|
+
return [
|
1443
|
+
[ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
|
1444
|
+
];
|
1445
|
+
};
|
1446
|
+
|
1447
|
+
const generateThrow = (scope, decl) => {
|
1448
|
+
scope.throws = true;
|
1449
|
+
|
1450
|
+
let message = decl.argument.value, constructor = null;
|
1451
|
+
|
1452
|
+
// hack: throw new X("...") -> throw "..."
|
1453
|
+
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
1454
|
+
constructor = decl.argument.callee.name;
|
1455
|
+
message = decl.argument.arguments[0].value;
|
1456
|
+
}
|
1457
|
+
|
1458
|
+
if (tags.length === 0) tags.push({
|
1459
|
+
params: [ Valtype.i32 ],
|
1460
|
+
results: [],
|
1461
|
+
idx: tags.length
|
1462
|
+
});
|
1463
|
+
|
1464
|
+
let exceptId = exceptions.push({ constructor, message }) - 1;
|
1465
|
+
let tagIdx = tags[0].idx;
|
1466
|
+
|
1467
|
+
// todo: write a description of how this works lol
|
1468
|
+
|
1469
|
+
return [
|
1470
|
+
[ Opcodes.i32_const, signedLEB128(exceptId) ],
|
1471
|
+
[ Opcodes.throw, tagIdx ]
|
1472
|
+
];
|
1473
|
+
};
|
1474
|
+
|
1475
|
+
const generateTry = (scope, decl) => {
|
1476
|
+
if (decl.finalizer) return todo('try finally not implemented yet');
|
1477
|
+
|
1478
|
+
const out = [];
|
1479
|
+
|
1480
|
+
out.push([ Opcodes.try, Blocktype.void ]);
|
1481
|
+
depth.push('try');
|
1482
|
+
|
1483
|
+
out.push(...generate(scope, decl.block));
|
1484
|
+
|
1485
|
+
if (decl.handler) {
|
1486
|
+
depth.pop();
|
1487
|
+
depth.push('catch');
|
1488
|
+
|
1489
|
+
out.push([ Opcodes.catch_all ]);
|
1490
|
+
out.push(...generate(scope, decl.handler.body));
|
1491
|
+
}
|
1492
|
+
|
1493
|
+
out.push([ Opcodes.end ]);
|
1494
|
+
depth.pop();
|
1495
|
+
|
1496
|
+
return out;
|
1497
|
+
};
|
1498
|
+
|
1499
|
+
const generateEmpty = (scope, decl) => {
|
1500
|
+
return [];
|
1501
|
+
};
|
1502
|
+
|
1503
|
+
const generateAssignPat = (scope, decl) => {
|
1504
|
+
// TODO
|
1505
|
+
// if identifier declared, use that
|
1506
|
+
// else, use default (right)
|
1507
|
+
return todo('assignment pattern (optional arg)');
|
1508
|
+
};
|
1509
|
+
|
1510
|
+
let pages = new Map();
|
1511
|
+
const allocPage = reason => {
|
1512
|
+
if (pages.has(reason)) return pages.get(reason);
|
1513
|
+
|
1514
|
+
let ind = pages.size;
|
1515
|
+
pages.set(reason, ind);
|
1516
|
+
|
1517
|
+
if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
|
1518
|
+
|
1519
|
+
return ind;
|
1520
|
+
};
|
1521
|
+
|
1522
|
+
const itemTypeToValtype = {
|
1523
|
+
i32: 'i32',
|
1524
|
+
i64: 'i64',
|
1525
|
+
f64: 'f64',
|
1526
|
+
|
1527
|
+
i8: 'i32',
|
1528
|
+
i16: 'i32'
|
1529
|
+
};
|
1530
|
+
|
1531
|
+
const storeOps = {
|
1532
|
+
i32: Opcodes.i32_store,
|
1533
|
+
i64: Opcodes.i64_store,
|
1534
|
+
f64: Opcodes.f64_store,
|
1535
|
+
|
1536
|
+
// expects i32 input!
|
1537
|
+
i16: Opcodes.i32_store16
|
1538
|
+
};
|
1539
|
+
|
1540
|
+
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1541
|
+
const out = [];
|
1542
|
+
|
1543
|
+
if (!arrays.has(name) || name === '$undeclared') {
|
1544
|
+
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1545
|
+
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1546
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
|
1547
|
+
}
|
1548
|
+
|
1549
|
+
const pointer = arrays.get(name);
|
1550
|
+
|
1551
|
+
const useRawElements = !!decl.rawElements;
|
1552
|
+
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1553
|
+
|
1554
|
+
const length = elements.length;
|
1555
|
+
|
1556
|
+
// store length as 0th array
|
1557
|
+
out.push(
|
1558
|
+
...number(0, Valtype.i32),
|
1559
|
+
...number(length, Valtype.i32),
|
1560
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1561
|
+
);
|
1562
|
+
|
1563
|
+
const storeOp = storeOps[itemType];
|
1564
|
+
const valtype = itemTypeToValtype[itemType];
|
1565
|
+
|
1566
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1567
|
+
if (elements[i] == null) continue;
|
1568
|
+
|
1569
|
+
out.push(
|
1570
|
+
...number(0, Valtype.i32),
|
1571
|
+
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
1572
|
+
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
1573
|
+
);
|
1574
|
+
}
|
1575
|
+
|
1576
|
+
// local value as pointer
|
1577
|
+
out.push(...number(pointer));
|
1578
|
+
|
1579
|
+
scope.memory = true;
|
1580
|
+
|
1581
|
+
return out;
|
1582
|
+
};
|
1583
|
+
|
1584
|
+
let arrays = new Map();
|
1585
|
+
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1586
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype);
|
1587
|
+
};
|
1588
|
+
|
1589
|
+
export const generateMember = (scope, decl, _global, _name) => {
|
1590
|
+
const type = getNodeType(scope, decl.object);
|
1591
|
+
|
1592
|
+
// hack: .length
|
1593
|
+
if (decl.property.name === 'length') {
|
1594
|
+
// if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
|
1595
|
+
|
1596
|
+
const name = decl.object.name;
|
1597
|
+
const pointer = arrays.get(name);
|
1598
|
+
|
1599
|
+
scope.memory = true;
|
1600
|
+
|
1601
|
+
const aotPointer = pointer != null;
|
1602
|
+
|
1603
|
+
return [
|
1604
|
+
...(aotPointer ? number(0, Valtype.i32) : [
|
1605
|
+
...generate(scope, decl.object),
|
1606
|
+
Opcodes.i32_to_u
|
1607
|
+
]),
|
1608
|
+
|
1609
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
|
1610
|
+
Opcodes.i32_from_u
|
1611
|
+
];
|
1612
|
+
}
|
1613
|
+
|
1614
|
+
// this is just for arr[ind] for now. objects are partially supported via object hack (a.b -> __a_b)
|
1615
|
+
if (![TYPES._array, TYPES.string].includes(type)) return todo(`computed member expression for objects are not supported yet`);
|
1616
|
+
|
1617
|
+
const name = decl.object.name;
|
1618
|
+
const pointer = arrays.get(name);
|
1619
|
+
|
1620
|
+
scope.memory = true;
|
1621
|
+
|
1622
|
+
const aotPointer = pointer != null;
|
1623
|
+
|
1624
|
+
if (type === TYPES._array) {
|
1625
|
+
return [
|
1626
|
+
// get index as valtype
|
1627
|
+
...generate(scope, decl.property),
|
1628
|
+
|
1629
|
+
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
1630
|
+
Opcodes.i32_to_u,
|
1631
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
1632
|
+
[ Opcodes.i32_mul ],
|
1633
|
+
|
1634
|
+
...(aotPointer ? [] : [
|
1635
|
+
...generate(scope, decl.object),
|
1636
|
+
Opcodes.i32_to_u,
|
1637
|
+
[ Opcodes.i32_add ]
|
1638
|
+
]),
|
1639
|
+
|
1640
|
+
// read from memory
|
1641
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1642
|
+
];
|
1643
|
+
}
|
1644
|
+
|
1645
|
+
// string
|
1646
|
+
|
1647
|
+
const newOut = makeArray(scope, {
|
1648
|
+
rawElements: new Array(1)
|
1649
|
+
}, _global, _name, true, 'i16');
|
1650
|
+
const newPointer = arrays.get(_name ?? '$undeclared');
|
1651
|
+
|
1652
|
+
return [
|
1653
|
+
// setup new/out array
|
1654
|
+
...newOut,
|
1655
|
+
[ Opcodes.drop ],
|
1656
|
+
|
1657
|
+
...number(0, Valtype.i32), // base 0 for store later
|
1658
|
+
|
1659
|
+
...generate(scope, decl.property),
|
1660
|
+
|
1661
|
+
Opcodes.i32_to_u,
|
1662
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
1663
|
+
[ Opcodes.i32_mul ],
|
1664
|
+
|
1665
|
+
...(aotPointer ? [] : [
|
1666
|
+
...generate(scope, decl.object),
|
1667
|
+
Opcodes.i32_to_u,
|
1668
|
+
[ Opcodes.i32_add ]
|
1669
|
+
]),
|
1670
|
+
|
1671
|
+
// load current string ind {arg}
|
1672
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
1673
|
+
|
1674
|
+
// store to new string ind 0
|
1675
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
1676
|
+
|
1677
|
+
// return new string (page)
|
1678
|
+
...number(newPointer)
|
1679
|
+
];
|
1680
|
+
};
|
1681
|
+
|
1682
|
+
const randId = () => Math.random().toString(16).slice(0, -4);
|
1683
|
+
|
1684
|
+
const objectHack = node => {
|
1685
|
+
if (!node) return node;
|
1686
|
+
|
1687
|
+
if (node.type === 'MemberExpression') {
|
1688
|
+
if (node.computed || node.optional) return node;
|
1689
|
+
|
1690
|
+
let objectName = node.object.name;
|
1691
|
+
|
1692
|
+
// if object is not identifier or another member exp, give up
|
1693
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
1694
|
+
|
1695
|
+
if (!objectName) objectName = objectHack(node.object).name.slice(2);
|
1696
|
+
|
1697
|
+
// if .length, give up (hack within a hack!)
|
1698
|
+
if (node.property.name === 'length') return node;
|
1699
|
+
|
1700
|
+
const name = '__' + objectName + '_' + node.property.name;
|
1701
|
+
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
1702
|
+
|
1703
|
+
return {
|
1704
|
+
type: 'Identifier',
|
1705
|
+
name
|
1706
|
+
};
|
1707
|
+
}
|
1708
|
+
|
1709
|
+
for (const x in node) {
|
1710
|
+
if (node[x] != null && typeof node[x] === 'object') {
|
1711
|
+
if (node[x].type) node[x] = objectHack(node[x]);
|
1712
|
+
if (Array.isArray(node[x])) node[x] = node[x].map(y => objectHack(y));
|
1713
|
+
}
|
1714
|
+
}
|
1715
|
+
|
1716
|
+
return node;
|
1717
|
+
};
|
1718
|
+
|
1719
|
+
const generateFunc = (scope, decl) => {
|
1720
|
+
if (decl.async) return todo('async functions are not supported');
|
1721
|
+
if (decl.generator) return todo('generator functions are not supported');
|
1722
|
+
|
1723
|
+
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
1724
|
+
const params = decl.params?.map(x => x.name) ?? [];
|
1725
|
+
|
1726
|
+
// const innerScope = { ...scope };
|
1727
|
+
// TODO: share scope/locals between !!!
|
1728
|
+
const innerScope = {
|
1729
|
+
locals: {},
|
1730
|
+
localInd: 0,
|
1731
|
+
returns: [ valtypeBinary ],
|
1732
|
+
memory: false,
|
1733
|
+
throws: false,
|
1734
|
+
name
|
1735
|
+
};
|
1736
|
+
|
1737
|
+
for (let i = 0; i < params.length; i++) {
|
1738
|
+
const param = params[i];
|
1739
|
+
innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
|
1740
|
+
}
|
1741
|
+
|
1742
|
+
let body = objectHack(decl.body);
|
1743
|
+
if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
|
1744
|
+
// hack: () => 0 -> () => return 0
|
1745
|
+
body = {
|
1746
|
+
type: 'ReturnStatement',
|
1747
|
+
argument: decl.body
|
1748
|
+
};
|
1749
|
+
}
|
1750
|
+
|
1751
|
+
const wasm = generate(innerScope, body);
|
1752
|
+
const func = {
|
1753
|
+
name,
|
1754
|
+
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
1755
|
+
returns: innerScope.returns,
|
1756
|
+
returnType: innerScope.returnType ?? TYPES.number,
|
1757
|
+
locals: innerScope.locals,
|
1758
|
+
memory: innerScope.memory,
|
1759
|
+
throws: innerScope.throws,
|
1760
|
+
index: currentFuncIndex++
|
1761
|
+
};
|
1762
|
+
funcIndex[name] = func.index;
|
1763
|
+
|
1764
|
+
// quick hack fixes
|
1765
|
+
for (const inst of wasm) {
|
1766
|
+
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
1767
|
+
inst[1] = func.index;
|
1768
|
+
}
|
1769
|
+
}
|
1770
|
+
|
1771
|
+
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1772
|
+
wasm.push(...number(0), [ Opcodes.return ]);
|
1773
|
+
}
|
1774
|
+
|
1775
|
+
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
1776
|
+
let offset = 0, vecParams = 0;
|
1777
|
+
for (let i = 0; i < params.length; i++) {
|
1778
|
+
const name = params[i];
|
1779
|
+
const local = func.locals[name];
|
1780
|
+
if (local.type === Valtype.v128) {
|
1781
|
+
vecParams++;
|
1782
|
+
|
1783
|
+
/* func.memory = true; // mark func as using memory
|
1784
|
+
|
1785
|
+
wasm.unshift( // add v128 load for param
|
1786
|
+
[ Opcodes.i32_const, 0 ],
|
1787
|
+
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1788
|
+
[ Opcodes.local_set, local.idx ]
|
1789
|
+
); */
|
1790
|
+
|
1791
|
+
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
1792
|
+
|
1793
|
+
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
1794
|
+
const { vecType } = local;
|
1795
|
+
let [ type, lanes ] = vecType.split('x');
|
1796
|
+
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
1797
|
+
|
1798
|
+
lanes = parseInt(lanes);
|
1799
|
+
type = Valtype[type];
|
1800
|
+
|
1801
|
+
const name = params[i]; // get original param name
|
1802
|
+
|
1803
|
+
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
1804
|
+
|
1805
|
+
// update index of original local
|
1806
|
+
// delete func.locals[name];
|
1807
|
+
|
1808
|
+
// add new locals for params
|
1809
|
+
for (let j = 0; j < lanes; j++) {
|
1810
|
+
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
1811
|
+
}
|
1812
|
+
|
1813
|
+
// prepend wasm to generate expected v128 locals
|
1814
|
+
wasm.splice(i * 2 + offset * 2, 0,
|
1815
|
+
...i32x4(0, 0, 0, 0),
|
1816
|
+
...new Array(lanes).fill(0).flatMap((_, j) => [
|
1817
|
+
[ Opcodes.local_get, offset + j ],
|
1818
|
+
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
1819
|
+
]),
|
1820
|
+
[ Opcodes.local_set, i ]
|
1821
|
+
);
|
1822
|
+
|
1823
|
+
offset += lanes;
|
1824
|
+
|
1825
|
+
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
1826
|
+
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
1827
|
+
|
1828
|
+
// add vec type index to hash name prefix for wrapper to know how to wrap
|
1829
|
+
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
1830
|
+
const secondHash = func.name.slice(1).indexOf('#');
|
1831
|
+
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
1832
|
+
}
|
1833
|
+
}
|
1834
|
+
|
1835
|
+
if (offset !== 0) {
|
1836
|
+
// bump local indexes for all other locals after
|
1837
|
+
for (const x in func.locals) {
|
1838
|
+
const local = func.locals[x];
|
1839
|
+
if (!local.vecParamAutogen) local.idx += offset;
|
1840
|
+
}
|
1841
|
+
|
1842
|
+
// bump local indexes in wasm local.get/set
|
1843
|
+
for (let j = 0; j < wasm.length; j++) {
|
1844
|
+
const inst = wasm[j];
|
1845
|
+
if (j < offset * 2 + vecParams * 2) {
|
1846
|
+
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
1847
|
+
continue;
|
1848
|
+
}
|
1849
|
+
|
1850
|
+
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
1851
|
+
}
|
1852
|
+
}
|
1853
|
+
|
1854
|
+
// change v128 return into many <type> instead as unsupported return valtype
|
1855
|
+
const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
|
1856
|
+
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
1857
|
+
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
1858
|
+
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
1859
|
+
const { vecType } = lastReturnLocal;
|
1860
|
+
let [ type, lanes ] = vecType.split('x');
|
1861
|
+
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
1862
|
+
|
1863
|
+
lanes = parseInt(lanes);
|
1864
|
+
type = Valtype[type];
|
1865
|
+
|
1866
|
+
const vecIdx = lastReturnLocal.idx;
|
1867
|
+
|
1868
|
+
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
1869
|
+
const tmpIdx = [];
|
1870
|
+
for (let i = 0; i < lanes; i++) {
|
1871
|
+
const idx = lastIdx + i + 1;
|
1872
|
+
tmpIdx.push(idx);
|
1873
|
+
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
1874
|
+
}
|
1875
|
+
|
1876
|
+
wasm.splice(wasm.length - 1, 1,
|
1877
|
+
...new Array(lanes).fill(0).flatMap((_, i) => [
|
1878
|
+
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
1879
|
+
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
1880
|
+
[ Opcodes.local_set, tmpIdx[i] ],
|
1881
|
+
].filter(x => x !== null)),
|
1882
|
+
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
1883
|
+
);
|
1884
|
+
|
1885
|
+
func.returns = new Array(lanes).fill(type);
|
1886
|
+
}
|
1887
|
+
|
1888
|
+
func.wasm = wasm;
|
1889
|
+
|
1890
|
+
funcs.push(func);
|
1891
|
+
|
1892
|
+
return func;
|
1893
|
+
};
|
1894
|
+
|
1895
|
+
const generateCode = (scope, decl) => {
|
1896
|
+
const out = [];
|
1897
|
+
|
1898
|
+
for (const x of decl.body) {
|
1899
|
+
out.push(...generate(scope, x));
|
1900
|
+
}
|
1901
|
+
|
1902
|
+
return out;
|
1903
|
+
};
|
1904
|
+
|
1905
|
+
const internalConstrs = {
|
1906
|
+
Array: {
|
1907
|
+
generate: (scope, decl, global, name) => {
|
1908
|
+
// new Array(i0, i1, ...)
|
1909
|
+
if (decl.arguments.length > 1) return generateArray(scope, {
|
1910
|
+
elements: decl.arguments
|
1911
|
+
}, global, name);
|
1912
|
+
|
1913
|
+
// new Array(n)
|
1914
|
+
|
1915
|
+
makeArray(scope, {
|
1916
|
+
rawElements: new Array(0)
|
1917
|
+
}, global, name, true);
|
1918
|
+
const pointer = arrays.get(name ?? '$undeclared');
|
1919
|
+
|
1920
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
1921
|
+
|
1922
|
+
// todo: check in wasm instead of here
|
1923
|
+
const literalValue = arg.value ?? 0;
|
1924
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
1925
|
+
|
1926
|
+
return [
|
1927
|
+
...number(0, Valtype.i32),
|
1928
|
+
...generate(scope, arg, global, name),
|
1929
|
+
Opcodes.i32_to_u,
|
1930
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
1931
|
+
|
1932
|
+
...number(pointer)
|
1933
|
+
];
|
1934
|
+
},
|
1935
|
+
type: TYPES._array
|
1936
|
+
}
|
1937
|
+
};
|
1938
|
+
|
1939
|
+
export default program => {
|
1940
|
+
globals = {};
|
1941
|
+
globalInd = 0;
|
1942
|
+
tags = [];
|
1943
|
+
exceptions = [];
|
1944
|
+
funcs = [];
|
1945
|
+
funcIndex = {};
|
1946
|
+
depth = [];
|
1947
|
+
typeStates = {};
|
1948
|
+
arrays = new Map();
|
1949
|
+
pages = new Map();
|
1950
|
+
currentFuncIndex = importedFuncs.length;
|
1951
|
+
|
1952
|
+
globalThis.valtype = 'f64';
|
1953
|
+
|
1954
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
|
1955
|
+
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
1956
|
+
|
1957
|
+
globalThis.valtypeBinary = Valtype[valtype];
|
1958
|
+
|
1959
|
+
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
1960
|
+
|
1961
|
+
// set generic opcodes for current valtype
|
1962
|
+
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
1963
|
+
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
1964
|
+
Opcodes.eqz = [ [ [ Opcodes.i32_eqz ] ], [ [ Opcodes.i64_eqz ] ], [ ...number(0), [ Opcodes.f64_eq ] ] ][valtypeInd];
|
1965
|
+
Opcodes.mul = [ Opcodes.i32_mul, Opcodes.i64_mul, Opcodes.f64_mul ][valtypeInd];
|
1966
|
+
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
1967
|
+
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
1968
|
+
|
1969
|
+
Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
1970
|
+
Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
1971
|
+
Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
1972
|
+
Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
1973
|
+
|
1974
|
+
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
1975
|
+
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
1976
|
+
|
1977
|
+
Opcodes.lt = [ Opcodes.i32_lt_s, Opcodes.i64_lt_s, Opcodes.f64_lt ][valtypeInd];
|
1978
|
+
|
1979
|
+
builtinFuncs = new BuiltinFuncs();
|
1980
|
+
builtinVars = new BuiltinVars();
|
1981
|
+
prototypeFuncs = new PrototypeFuncs();
|
1982
|
+
|
1983
|
+
program.id = { name: 'main' };
|
1984
|
+
|
1985
|
+
globalThis.pageSize = PageSize;
|
1986
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
1987
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
1988
|
+
|
1989
|
+
const scope = {
|
1990
|
+
locals: {},
|
1991
|
+
localInd: 0
|
1992
|
+
};
|
1993
|
+
|
1994
|
+
program.body = {
|
1995
|
+
type: 'BlockStatement',
|
1996
|
+
body: program.body
|
1997
|
+
};
|
1998
|
+
|
1999
|
+
generateFunc(scope, program);
|
2000
|
+
|
2001
|
+
const main = funcs[funcs.length - 1];
|
2002
|
+
main.export = true;
|
2003
|
+
main.returns = [ valtypeBinary ];
|
2004
|
+
|
2005
|
+
const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
|
2006
|
+
if (lastInst[0] === Opcodes.drop) {
|
2007
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
2008
|
+
|
2009
|
+
const finalStatement = program.body.body[program.body.body.length - 1];
|
2010
|
+
main.returnType = getNodeType(main, finalStatement);
|
2011
|
+
}
|
2012
|
+
|
2013
|
+
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
2014
|
+
main.returns = [];
|
2015
|
+
}
|
2016
|
+
|
2017
|
+
if (lastInst[0] === Opcodes.call) {
|
2018
|
+
const func = funcs.find(x => x.index === lastInst[1]);
|
2019
|
+
if (func) main.returns = func.returns.slice();
|
2020
|
+
else main.returns = [];
|
2021
|
+
}
|
2022
|
+
|
2023
|
+
// if blank main func and other exports, remove it
|
2024
|
+
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2025
|
+
|
2026
|
+
return { funcs, globals, tags, exceptions, pages };
|
2027
|
+
};
|