porffor 0.0.0-8c0bdaa

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.
@@ -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
+ };