porffor 0.0.0-1989c22

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,2155 @@
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 rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
331
+ const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
332
+ const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
333
+
334
+ if (assign) {
335
+ const pointer = arrays.get(name ?? '$undeclared');
336
+
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
+ // alloc/assign array
388
+ const [ , pointer ] = makeArray(scope, {
389
+ rawElements: new Array(0)
390
+ }, global, name, true, 'i16');
391
+
392
+ return [
393
+ // setup left
394
+ ...left,
395
+ Opcodes.i32_to_u,
396
+ [ Opcodes.local_set, leftPointer ],
397
+
398
+ // setup right
399
+ ...right,
400
+ Opcodes.i32_to_u,
401
+ [ Opcodes.local_set, rightPointer ],
402
+
403
+ // calculate length
404
+ ...number(0, Valtype.i32), // base 0 for store later
405
+
406
+ [ Opcodes.local_get, leftPointer ],
407
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
408
+ [ Opcodes.local_tee, leftLength ],
409
+
410
+ [ Opcodes.local_get, rightPointer ],
411
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
412
+ [ Opcodes.local_tee, rightLength ],
413
+
414
+ [ Opcodes.i32_add ],
415
+
416
+ // store length
417
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
418
+
419
+ // copy left
420
+ // dst = out pointer + length size
421
+ ...number(pointer + ValtypeSize.i32, Valtype.i32),
422
+
423
+ // src = left pointer + length size
424
+ [ Opcodes.local_get, leftPointer ],
425
+ ...number(ValtypeSize.i32, Valtype.i32),
426
+ [ Opcodes.i32_add ],
427
+
428
+ // size = PageSize - length size. we do not need to calculate length as init value
429
+ ...number(pageSize - ValtypeSize.i32, Valtype.i32),
430
+ [ ...Opcodes.memory_copy, 0x00, 0x00 ],
431
+
432
+ // copy right
433
+ // dst = out pointer + length size + left length * i16 size
434
+ ...number(pointer + ValtypeSize.i32, Valtype.i32),
435
+
436
+ [ Opcodes.local_get, leftLength ],
437
+ ...number(ValtypeSize.i16, Valtype.i32),
438
+ [ Opcodes.i32_mul ],
439
+ [ Opcodes.i32_add ],
440
+
441
+ // src = right pointer + length size
442
+ [ Opcodes.local_get, rightPointer ],
443
+ ...number(ValtypeSize.i32, Valtype.i32),
444
+ [ Opcodes.i32_add ],
445
+
446
+ // size = right length * i16 size
447
+ [ Opcodes.local_get, rightLength ],
448
+ ...number(ValtypeSize.i16, Valtype.i32),
449
+ [ Opcodes.i32_mul ],
450
+
451
+ [ ...Opcodes.memory_copy, 0x00, 0x00 ],
452
+
453
+ // return new string (page)
454
+ ...number(pointer)
455
+ ];
456
+ };
457
+
458
+ const compareStrings = (scope, left, right) => {
459
+ // todo: this should be rewritten into a built-in/func: String.prototype.concat
460
+ // todo: convert left and right to strings if not
461
+ // todo: optimize by looking up names in arrays and using that if exists?
462
+ // todo: optimize this if using literals/known lengths?
463
+
464
+ scope.memory = true;
465
+
466
+ const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
467
+ const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
468
+ const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
469
+ const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
470
+
471
+ const index = localTmp(scope, 'compare_index', Valtype.i32);
472
+ const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
473
+
474
+ return [
475
+ // use block to "return" a value early
476
+ [ Opcodes.block, Valtype.i32 ],
477
+
478
+ // setup left
479
+ ...left,
480
+ Opcodes.i32_to_u,
481
+ [ Opcodes.local_tee, leftPointer ],
482
+
483
+ // setup right
484
+ ...right,
485
+ Opcodes.i32_to_u,
486
+ [ Opcodes.local_tee, rightPointer ],
487
+
488
+ // fast path: check leftPointer == rightPointer
489
+ [ Opcodes.i32_eq ],
490
+ [ Opcodes.if, Blocktype.void ],
491
+ ...number(1, Valtype.i32),
492
+ [ Opcodes.br, 1 ],
493
+ [ Opcodes.end ],
494
+
495
+ // get lengths
496
+ [ Opcodes.local_get, leftPointer ],
497
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
498
+ [ Opcodes.local_tee, leftLength ],
499
+
500
+ [ Opcodes.local_get, rightPointer ],
501
+ [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
502
+ [ Opcodes.local_tee, rightLength ],
503
+
504
+ // fast path: check leftLength != rightLength
505
+ [ Opcodes.i32_ne ],
506
+ [ Opcodes.if, Blocktype.void ],
507
+ ...number(0, Valtype.i32),
508
+ [ Opcodes.br, 1 ],
509
+ [ Opcodes.end ],
510
+
511
+ // no fast path for length = 0 as it would probably be slower for most of the time?
512
+
513
+ // setup index end as length * sizeof i16 (2)
514
+ // we do this instead of having to do mul/div each iter for perf™
515
+ [ Opcodes.local_get, leftLength ],
516
+ ...number(ValtypeSize.i16, Valtype.i32),
517
+ [ Opcodes.i32_mul ],
518
+ [ Opcodes.local_set, indexEnd ],
519
+
520
+ // iterate over each char and check if eq
521
+ [ Opcodes.loop, Blocktype.void ],
522
+
523
+ // fetch left
524
+ [ Opcodes.local_get, index ],
525
+ [ Opcodes.local_get, leftPointer ],
526
+ [ Opcodes.i32_add ],
527
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
528
+
529
+ // fetch right
530
+ [ Opcodes.local_get, index ],
531
+ [ Opcodes.local_get, rightPointer ],
532
+ [ Opcodes.i32_add ],
533
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
534
+
535
+ // not equal, "return" false
536
+ [ Opcodes.i32_ne ],
537
+ [ Opcodes.if, Blocktype.void ],
538
+ ...number(0, Valtype.i32),
539
+ [ Opcodes.br, 2 ],
540
+ [ Opcodes.end ],
541
+
542
+ // index += sizeof i16 (2)
543
+ [ Opcodes.local_get, index ],
544
+ ...number(ValtypeSize.i16, Valtype.i32),
545
+ [ Opcodes.i32_add ],
546
+ [ Opcodes.local_tee, index ],
547
+
548
+ // if index != index end (length * sizeof 16), loop
549
+ [ Opcodes.local_get, indexEnd ],
550
+ [ Opcodes.i32_ne ],
551
+ [ Opcodes.br_if, 0 ],
552
+ [ Opcodes.end ],
553
+
554
+ // no failed checks, so true!
555
+ ...number(1, Valtype.i32),
556
+ [ Opcodes.end ],
557
+
558
+ // convert i32 result to valtype
559
+ // do not do as automatically added by binary exp gen for equality ops
560
+ // Opcodes.i32_from_u
561
+ ];
562
+ };
563
+
564
+ const falsy = (scope, wasm, type) => {
565
+ // arrays are always truthy
566
+ if (type === TYPES._array) return [
567
+ ...wasm,
568
+ [ Opcodes.drop ],
569
+ number(0)
570
+ ];
571
+
572
+ if (type === TYPES.string) {
573
+ // if "" (length = 0)
574
+ return [
575
+ // pointer
576
+ ...wasm,
577
+
578
+ // get length
579
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
580
+
581
+ // if length == 0
582
+ [ Opcodes.i32_eqz ],
583
+ Opcodes.i32_from_u
584
+ ]
585
+ }
586
+
587
+ // if = 0
588
+ return [
589
+ ...wasm,
590
+
591
+ ...Opcodes.eqz,
592
+ Opcodes.i32_from_u
593
+ ];
594
+ };
595
+
596
+ const truthy = (scope, wasm, type) => {
597
+ // arrays are always truthy
598
+ if (type === TYPES._array) return [
599
+ ...wasm,
600
+ [ Opcodes.drop ],
601
+ number(1)
602
+ ];
603
+
604
+ if (type === TYPES.string) {
605
+ // if not "" (length = 0)
606
+ return [
607
+ // pointer
608
+ ...wasm,
609
+
610
+ // get length
611
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
612
+
613
+ // if length != 0
614
+ /* [ Opcodes.i32_eqz ],
615
+ [ Opcodes.i32_eqz ], */
616
+ Opcodes.i32_from_u
617
+ ]
618
+ }
619
+
620
+ // if != 0
621
+ return [
622
+ ...wasm,
623
+
624
+ /* Opcodes.eqz,
625
+ [ Opcodes.i32_eqz ],
626
+ Opcodes.i32_from */
627
+ ];
628
+ };
629
+
630
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
631
+ if (op === '||' || op === '&&' || op === '??') {
632
+ return performLogicOp(scope, op, left, right);
633
+ }
634
+
635
+ if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
636
+
637
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
638
+ if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
639
+ return [
640
+ ...left,
641
+ ...right,
642
+
643
+ // drop values
644
+ [ Opcodes.drop ],
645
+ [ Opcodes.drop ],
646
+
647
+ // return false (===)/true (!==)
648
+ ...number(op === '===' ? 0 : 1, Valtype.i32)
649
+ ];
650
+ }
651
+
652
+ if (leftType === TYPES.string || rightType === TYPES.string) {
653
+ if (op === '+') {
654
+ // string concat (a + b)
655
+ return concatStrings(scope, left, right, _global, _name, assign);
656
+ }
657
+
658
+ // any other math op, NaN
659
+ if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
660
+
661
+ // else leave bool ops
662
+ // todo: convert string to number if string and number/bool
663
+ // todo: string (>|>=|<|<=) string
664
+
665
+ // string comparison
666
+ if (op === '===' || op === '==') {
667
+ return compareStrings(scope, left, right);
668
+ }
669
+
670
+ if (op === '!==' || op === '!=') {
671
+ return [
672
+ ...compareStrings(scope, left, right),
673
+ [ Opcodes.i32_eqz ]
674
+ ];
675
+ }
676
+ }
677
+
678
+ let ops = operatorOpcode[valtype][op];
679
+
680
+ // some complex ops are implemented as builtin funcs
681
+ const builtinName = `${valtype}_${op}`;
682
+ if (!ops && builtinFuncs[builtinName]) {
683
+ includeBuiltin(scope, builtinName);
684
+ const idx = funcIndex[builtinName];
685
+
686
+ return [
687
+ ...left,
688
+ ...right,
689
+ [ Opcodes.call, idx ]
690
+ ];
691
+ }
692
+
693
+ if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
694
+
695
+ if (!Array.isArray(ops)) ops = [ ops ];
696
+
697
+ return [
698
+ ...left,
699
+ ...right,
700
+ ops
701
+ ];
702
+ };
703
+
704
+ let binaryExpDepth = 0;
705
+ const generateBinaryExp = (scope, decl, _global, _name) => {
706
+ binaryExpDepth++;
707
+
708
+ const out = [
709
+ ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
710
+ ];
711
+
712
+ if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
713
+
714
+ binaryExpDepth--;
715
+ return out;
716
+ };
717
+
718
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
719
+ const existing = funcs.find(x => x.name === name);
720
+ if (existing) return existing;
721
+
722
+ const nameParam = i => localNames[i] ?? (i >= params.length ? ['a', 'b', 'c'][i - params.length] : ['x', 'y', 'z'][i]);
723
+
724
+ const allLocals = params.concat(localTypes);
725
+ const locals = {};
726
+ for (let i = 0; i < allLocals.length; i++) {
727
+ locals[nameParam(i)] = { idx: i, type: allLocals[i] };
728
+ }
729
+
730
+ let baseGlobalIdx, i = 0;
731
+ for (const type of globalTypes) {
732
+ if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
733
+
734
+ globals[globalNames[i] ?? `${name}_global_${i}`] = { idx: globalInd++, type, init: globalInits[i] ?? 0 };
735
+ i++;
736
+ }
737
+
738
+ if (globalTypes.length !== 0) {
739
+ // offset global ops for base global idx
740
+ for (const inst of wasm) {
741
+ if (inst[0] === Opcodes.global_get || inst[0] === Opcodes.global_set) {
742
+ inst[1] += baseGlobalIdx;
743
+ }
744
+ }
745
+ }
746
+
747
+ const func = {
748
+ name,
749
+ params,
750
+ locals,
751
+ returns,
752
+ returnType: TYPES[returnType ?? 'number'],
753
+ wasm,
754
+ memory,
755
+ internal: true,
756
+ index: currentFuncIndex++
757
+ };
758
+
759
+ funcs.push(func);
760
+ funcIndex[name] = func.index;
761
+
762
+ return func;
763
+ };
764
+
765
+ const includeBuiltin = (scope, builtin) => {
766
+ const code = builtinFuncs[builtin];
767
+ if (code.wasm) return asmFunc(builtin, code);
768
+
769
+ return code.body.map(x => generate(scope, x));
770
+ };
771
+
772
+ const generateLogicExp = (scope, decl) => {
773
+ return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
774
+ };
775
+
776
+ const TYPES = {
777
+ number: 0xffffffffffff0,
778
+ boolean: 0xffffffffffff1,
779
+ string: 0xffffffffffff2,
780
+ undefined: 0xffffffffffff3,
781
+ object: 0xffffffffffff4,
782
+ function: 0xffffffffffff5,
783
+ symbol: 0xffffffffffff6,
784
+ bigint: 0xffffffffffff7,
785
+
786
+ // these are not "typeof" types but tracked internally
787
+ _array: 0xffffffffffff8
788
+ };
789
+
790
+ const TYPE_NAMES = {
791
+ [TYPES.number]: 'Number',
792
+ [TYPES.boolean]: 'Boolean',
793
+ [TYPES.string]: 'String',
794
+ [TYPES.undefined]: 'undefined',
795
+ [TYPES.object]: 'Object',
796
+ [TYPES.function]: 'Function',
797
+ [TYPES.symbol]: 'Symbol',
798
+ [TYPES.bigint]: 'BigInt',
799
+
800
+ [TYPES._array]: 'Array'
801
+ };
802
+
803
+ let typeStates = {};
804
+
805
+ const getType = (scope, _name) => {
806
+ const name = mapName(_name);
807
+ if (scope.locals[name]) return typeStates[name];
808
+
809
+ if (builtinVars[name]) return TYPES[builtinVars[name].type ?? 'number'];
810
+ if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) return TYPES.function;
811
+ if (globals[name]) return typeStates[name];
812
+
813
+ if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)]) return TYPES.function;
814
+ if (name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) return TYPES.function;
815
+
816
+ return TYPES.undefined;
817
+ };
818
+
819
+ const getNodeType = (scope, node) => {
820
+ if (node.type === 'Literal') {
821
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
822
+ return TYPES[typeof node.value];
823
+ }
824
+
825
+ if (isFuncType(node.type)) {
826
+ return TYPES.function;
827
+ }
828
+
829
+ if (node.type === 'Identifier') {
830
+ return getType(scope, node.name);
831
+ }
832
+
833
+ if (node.type === 'CallExpression' || node.type === 'NewExpression') {
834
+ const name = node.callee.name;
835
+ const func = funcs.find(x => x.name === name);
836
+ if (func) return func.returnType;
837
+
838
+ if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
839
+ if (internalConstrs[name]) return internalConstrs[name].type;
840
+
841
+ let protoFunc;
842
+ // ident.func()
843
+ if (name && name.startsWith('__')) {
844
+ const spl = name.slice(2).split('_');
845
+
846
+ const baseName = spl.slice(0, -1).join('_');
847
+ const baseType = getType(scope, baseName);
848
+
849
+ const func = spl[spl.length - 1];
850
+ protoFunc = prototypeFuncs[baseType]?.[func];
851
+ }
852
+
853
+ // literal.func()
854
+ if (!name && node.callee.type === 'MemberExpression') {
855
+ const baseType = getNodeType(scope, node.callee.object);
856
+
857
+ const func = node.callee.property.name;
858
+ protoFunc = prototypeFuncs[baseType]?.[func];
859
+ }
860
+
861
+ if (protoFunc) return protoFunc.returnType;
862
+ }
863
+
864
+ if (node.type === 'ExpressionStatement') {
865
+ return getNodeType(scope, node.expression);
866
+ }
867
+
868
+ if (node.type === 'AssignmentExpression') {
869
+ return getNodeType(scope, node.right);
870
+ }
871
+
872
+ if (node.type === 'ArrayExpression') {
873
+ return TYPES._array;
874
+ }
875
+
876
+ if (node.type === 'BinaryExpression') {
877
+ if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
878
+
879
+ if (node.operator === '+' && (getNodeType(scope, node.left) === TYPES.string || getNodeType(scope, node.right) === TYPES.string)) return TYPES.string;
880
+ }
881
+
882
+ if (node.type === 'UnaryExpression') {
883
+ if (node.operator === '!') return TYPES.boolean;
884
+ if (node.operator === 'void') return TYPES.undefined;
885
+ if (node.operator === 'delete') return TYPES.boolean;
886
+ }
887
+
888
+ if (node.type === 'MemberExpression') {
889
+ const objectType = getNodeType(scope, node.object);
890
+
891
+ if (objectType === TYPES.string && node.computed) return TYPES.string;
892
+ }
893
+ };
894
+
895
+ const generateLiteral = (scope, decl, global, name) => {
896
+ if (decl.value === null) return number(NULL);
897
+
898
+ switch (typeof decl.value) {
899
+ case 'number':
900
+ return number(decl.value);
901
+
902
+ case 'boolean':
903
+ // hack: bool as int (1/0)
904
+ return number(decl.value ? 1 : 0);
905
+
906
+ case 'string':
907
+ // this is a terrible hack which changes type strings ("number" etc) to known const number values
908
+ switch (decl.value) {
909
+ case 'number': return number(TYPES.number);
910
+ case 'boolean': return number(TYPES.boolean);
911
+ case 'string': return number(TYPES.string);
912
+ case 'undefined': return number(TYPES.undefined);
913
+ case 'object': return number(TYPES.object);
914
+ case 'function': return number(TYPES.function);
915
+ case 'symbol': return number(TYPES.symbol);
916
+ case 'bigint': return number(TYPES.bigint);
917
+ }
918
+
919
+ const str = decl.value;
920
+ const rawElements = new Array(str.length);
921
+ for (let i = 0; i < str.length; i++) {
922
+ rawElements[i] = str.charCodeAt(i);
923
+ }
924
+
925
+ return makeArray(scope, {
926
+ rawElements
927
+ }, global, name, false, 'i16')[0];
928
+
929
+ default:
930
+ return todo(`cannot generate literal of type ${typeof decl.value}`);
931
+ }
932
+ };
933
+
934
+ const countLeftover = wasm => {
935
+ let count = 0, depth = 0;
936
+
937
+ for (const inst of wasm) {
938
+ if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
939
+ if (inst[0] === Opcodes.if) count--;
940
+ if (inst[1] !== Blocktype.void) count++;
941
+ }
942
+ if ([Opcodes.if, Opcodes.try, Opcodes.loop, Opcodes.block].includes(inst[0])) depth++;
943
+ if (inst[0] === Opcodes.end) depth--;
944
+
945
+ if (depth === 0)
946
+ if ([Opcodes.throw, Opcodes.return, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
947
+ 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)) {}
948
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
949
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
950
+ else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
951
+ else if (inst[0] === Opcodes.call) {
952
+ let func = funcs.find(x => x.index === inst[1]);
953
+ if (func) {
954
+ count -= func.params.length;
955
+ } else count--;
956
+ if (func) count += func.returns.length;
957
+ } else count--;
958
+ }
959
+
960
+ return count;
961
+ };
962
+
963
+ const disposeLeftover = wasm => {
964
+ let leftover = countLeftover(wasm);
965
+
966
+ for (let i = 0; i < leftover; i++) wasm.push([ Opcodes.drop ]);
967
+ };
968
+
969
+ const generateExp = (scope, decl) => {
970
+ const expression = decl.expression;
971
+
972
+ const out = generate(scope, expression);
973
+ disposeLeftover(out);
974
+
975
+ return out;
976
+ };
977
+
978
+ const arrayUtil = {
979
+ getLengthI32: pointer => [
980
+ ...number(0, Valtype.i32),
981
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
982
+ ],
983
+
984
+ getLength: pointer => [
985
+ ...number(0, Valtype.i32),
986
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
987
+ Opcodes.i32_from_u
988
+ ],
989
+
990
+ setLengthI32: (pointer, value) => [
991
+ ...number(0, Valtype.i32),
992
+ ...value,
993
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
994
+ ],
995
+
996
+ setLength: (pointer, value) => [
997
+ ...number(0, Valtype.i32),
998
+ ...value,
999
+ Opcodes.i32_to_u,
1000
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1001
+ ]
1002
+ };
1003
+
1004
+ const generateCall = (scope, decl, _global, _name) => {
1005
+ /* const callee = decl.callee;
1006
+ const args = decl.arguments;
1007
+
1008
+ return [
1009
+ ...generate(args),
1010
+ ...generate(callee),
1011
+ Opcodes.call_indirect,
1012
+ ]; */
1013
+
1014
+ let name = mapName(decl.callee.name);
1015
+ if (isFuncType(decl.callee.type)) { // iife
1016
+ const func = generateFunc(scope, decl.callee);
1017
+ name = func.name;
1018
+ }
1019
+
1020
+ if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1021
+ // literal eval hack
1022
+ const code = decl.arguments[0].value;
1023
+ const parsed = parse(code, []);
1024
+
1025
+ const out = generate(scope, {
1026
+ type: 'BlockStatement',
1027
+ body: parsed.body
1028
+ });
1029
+
1030
+ const lastInst = out[out.length - 1];
1031
+ if (lastInst && lastInst[0] === Opcodes.drop) {
1032
+ out.splice(out.length - 1, 1);
1033
+ } else if (countLeftover(out) === 0) {
1034
+ out.push(...number(UNDEFINED));
1035
+ }
1036
+
1037
+ return out;
1038
+ }
1039
+
1040
+ let out = [];
1041
+ let protoFunc, protoName, baseType, baseName = '$undeclared';
1042
+ // ident.func()
1043
+ if (name && name.startsWith('__')) {
1044
+ const spl = name.slice(2).split('_');
1045
+
1046
+ baseName = spl.slice(0, -1).join('_');
1047
+ baseType = getType(scope, baseName);
1048
+
1049
+ const func = spl[spl.length - 1];
1050
+ protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
1051
+ protoName = func;
1052
+ }
1053
+
1054
+ // literal.func()
1055
+ if (!name && decl.callee.type === 'MemberExpression') {
1056
+ baseType = getNodeType(scope, decl.callee.object);
1057
+
1058
+ const func = decl.callee.property.name;
1059
+ protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
1060
+ protoName = func;
1061
+
1062
+ out = generate(scope, decl.callee.object);
1063
+ out.push([ Opcodes.drop ]);
1064
+ }
1065
+
1066
+ if (protoFunc) {
1067
+ scope.memory = true;
1068
+
1069
+ let pointer = arrays.get(baseName);
1070
+
1071
+ if (pointer == null) {
1072
+ // unknown dynamic pointer, so clone to new pointer which we know aot. now that's what I call inefficient™
1073
+ if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1074
+
1075
+ // register array
1076
+ const [ , pointer ] = makeArray(scope, {
1077
+ rawElements: new Array(0)
1078
+ }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1079
+
1080
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1081
+
1082
+ out = [
1083
+ // clone to new pointer
1084
+ ...number(pointer, Valtype.i32), // dst = new pointer
1085
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ], // src = unknown pointer
1086
+ Opcodes.i32_to_u,
1087
+ ...number(pageSize, Valtype.i32), // size = pagesize
1088
+
1089
+ [ ...Opcodes.memory_copy, 0x00, 0x00 ],
1090
+ ];
1091
+ }
1092
+
1093
+ if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1094
+
1095
+ let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1096
+
1097
+ // use local for cached i32 length as commonly used
1098
+ let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1099
+
1100
+ return [
1101
+ ...out,
1102
+
1103
+ ...arrayUtil.getLengthI32(pointer),
1104
+ [ Opcodes.local_set, lengthLocal ],
1105
+
1106
+ [ Opcodes.block, valtypeBinary ],
1107
+ ...protoFunc(pointer, {
1108
+ cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1109
+ get: arrayUtil.getLength(pointer),
1110
+ getI32: arrayUtil.getLengthI32(pointer),
1111
+ set: value => arrayUtil.setLength(pointer, value),
1112
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1113
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1114
+ return makeArray(scope, {
1115
+ rawElements: new Array(length)
1116
+ }, _global, _name, true, itemType);
1117
+ }),
1118
+ [ Opcodes.end ]
1119
+ ];
1120
+ }
1121
+
1122
+ // TODO: only allows callee as literal
1123
+ if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1124
+
1125
+ let idx = funcIndex[name] ?? importedFuncs[name];
1126
+ if (idx === undefined && builtinFuncs[name]) {
1127
+ if (builtinFuncs[name].floatOnly && valtype !== 'f64') throw new Error(`Cannot use built-in ${unhackName(name)} with integer valtype`);
1128
+
1129
+ includeBuiltin(scope, name);
1130
+ idx = funcIndex[name];
1131
+
1132
+ // infer arguments types from builtins params
1133
+ const func = funcs.find(x => x.name === name);
1134
+ for (let i = 0; i < decl.arguments.length; i++) {
1135
+ const arg = decl.arguments[i];
1136
+ if (!arg.name) continue;
1137
+
1138
+ const local = scope.locals[arg.name];
1139
+ if (!local) continue;
1140
+
1141
+ local.type = func.params[i];
1142
+ if (local.type === Valtype.v128) {
1143
+ // specify vec subtype inferred from last vec type in function name
1144
+ local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+ if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1150
+
1151
+ if (idx === undefined && name === scope.name) {
1152
+ // hack: calling self, func generator will fix later
1153
+ idx = -1;
1154
+ }
1155
+
1156
+ if (idx === undefined) {
1157
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1158
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1159
+ }
1160
+
1161
+ const func = funcs.find(x => x.index === idx);
1162
+
1163
+ let args = decl.arguments;
1164
+ if (func && args.length < func.params.length) {
1165
+ // too little args, push undefineds
1166
+ args = args.concat(new Array(func.params.length - args.length).fill(DEFAULT_VALUE));
1167
+ }
1168
+
1169
+ if (func && args.length > func.params.length) {
1170
+ // too many args, slice extras off
1171
+ args = args.slice(0, func.params.length);
1172
+ }
1173
+
1174
+ if (func && func.memory) scope.memory = true;
1175
+ if (func && func.throws) scope.throws = true;
1176
+
1177
+ for (const arg of args) {
1178
+ out.push(...generate(scope, arg));
1179
+ }
1180
+
1181
+ out.push([ Opcodes.call, idx ]);
1182
+
1183
+ return out;
1184
+ };
1185
+
1186
+ const generateNew = (scope, decl, _global, _name) => {
1187
+ // hack: basically treat this as a normal call for builtins for now
1188
+ const name = mapName(decl.callee.name);
1189
+ if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1190
+ if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1191
+
1192
+ return generateCall(scope, decl, _global, _name);
1193
+ };
1194
+
1195
+ // bad hack for undefined and null working without additional logic
1196
+ const DEFAULT_VALUE = {
1197
+ type: 'Identifier',
1198
+ name: 'undefined'
1199
+ };
1200
+
1201
+ const unhackName = name => {
1202
+ if (name.startsWith('__')) return name.slice(2).replaceAll('_', '.');
1203
+ return name;
1204
+ };
1205
+
1206
+ const generateVar = (scope, decl) => {
1207
+ const out = [];
1208
+
1209
+ const topLevel = scope.name === 'main';
1210
+
1211
+ // global variable if in top scope (main) and var ..., or if wanted
1212
+ const global = decl.kind === 'var';
1213
+ const target = global ? globals : scope.locals;
1214
+
1215
+ for (const x of decl.declarations) {
1216
+ const name = mapName(x.id.name);
1217
+
1218
+ if (x.init && isFuncType(x.init.type)) {
1219
+ // hack for let a = function () { ... }
1220
+ x.init.id = { name };
1221
+ generateFunc(scope, x.init);
1222
+ continue;
1223
+ }
1224
+
1225
+ // console.log(name);
1226
+ if (topLevel && builtinVars[name]) {
1227
+ // cannot redeclare
1228
+ if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
1229
+
1230
+ continue; // always ignore
1231
+ }
1232
+
1233
+ let idx;
1234
+ // already declared
1235
+ if (target[name]) {
1236
+ // parser should catch this but sanity check anyway
1237
+ if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
1238
+
1239
+ idx = target[name].idx;
1240
+ } else {
1241
+ idx = global ? globalInd++ : scope.localInd++;
1242
+ target[name] = { idx, type: valtypeBinary };
1243
+ }
1244
+
1245
+ typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
1246
+
1247
+ // x.init ??= DEFAULT_VALUE;
1248
+ if (x.init) {
1249
+ out.push(...generate(scope, x.init, global, name));
1250
+
1251
+ // if our value is the result of a function, infer the type from that func's return value
1252
+ if (out[out.length - 1][0] === Opcodes.call) {
1253
+ const ind = out[out.length - 1][1];
1254
+ if (ind >= importedFuncs.length) { // not an imported func
1255
+ const func = funcs.find(x => x.index === ind);
1256
+ if (!func) throw new Error('could not find func being called as var value to infer type'); // sanity check
1257
+
1258
+ const returns = func.returns;
1259
+ if (returns.length > 1) throw new Error('func returning >1 value being set as 1 local'); // sanity check
1260
+
1261
+ target[name].type = func.returns[0];
1262
+ if (target[name].type === Valtype.v128) {
1263
+ // specify vec subtype inferred from first vec type in function name
1264
+ target[name].vecType = func.name.split('_').find(x => x.includes('x'));
1265
+ }
1266
+ } else {
1267
+ // we do not have imports that return yet, ignore for now
1268
+ }
1269
+ }
1270
+
1271
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1272
+ }
1273
+ }
1274
+
1275
+ return out;
1276
+ };
1277
+
1278
+ const generateAssign = (scope, decl) => {
1279
+ const { type, name } = decl.left;
1280
+
1281
+ if (type === 'ObjectPattern') {
1282
+ // hack: ignore object parts of `var a = {} = 2`
1283
+ return generate(scope, decl.right);
1284
+ }
1285
+
1286
+ if (isFuncType(decl.right.type)) {
1287
+ // hack for a = function () { ... }
1288
+ decl.right.id = { name };
1289
+ generateFunc(scope, decl.right);
1290
+ return [];
1291
+ }
1292
+
1293
+ // hack: .length setter
1294
+ if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1295
+ const name = decl.left.object.name;
1296
+ const pointer = arrays.get(name);
1297
+
1298
+ scope.memory = true;
1299
+
1300
+ const aotPointer = pointer != null;
1301
+
1302
+ const newValueTmp = localTmp(scope, '__length_setter_tmp');
1303
+
1304
+ return [
1305
+ ...(aotPointer ? number(0, Valtype.i32) : [
1306
+ ...generate(scope, decl.left.object),
1307
+ Opcodes.i32_to_u
1308
+ ]),
1309
+
1310
+ ...generate(scope, decl.right, false, name),
1311
+ [ Opcodes.local_tee, newValueTmp ],
1312
+
1313
+ Opcodes.i32_to_u,
1314
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
1315
+
1316
+ [ Opcodes.local_get, newValueTmp ]
1317
+ ];
1318
+ }
1319
+
1320
+ const [ local, isGlobal ] = lookupName(scope, name);
1321
+
1322
+ if (local === undefined) {
1323
+ // todo: this should be a devtools/repl/??? only thing
1324
+
1325
+ // only allow = for this
1326
+ if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1327
+
1328
+ if (builtinVars[name]) {
1329
+ // just return rhs (eg `NaN = 2`)
1330
+ return generate(scope, decl.right);
1331
+ }
1332
+
1333
+ // set global and return (eg a = 2)
1334
+ return [
1335
+ ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1336
+ [ Opcodes.global_get, globals[name].idx ]
1337
+ ];
1338
+ }
1339
+
1340
+ if (decl.operator === '=') {
1341
+ typeStates[name] = getNodeType(scope, decl.right);
1342
+
1343
+ return [
1344
+ ...generate(scope, decl.right, isGlobal, name),
1345
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1346
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1347
+ ];
1348
+ }
1349
+
1350
+ return [
1351
+ ...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),
1352
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1353
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1354
+ ];
1355
+ };
1356
+
1357
+ const generateUnary = (scope, decl) => {
1358
+ switch (decl.operator) {
1359
+ case '+':
1360
+ // stub
1361
+ return generate(scope, decl.argument);
1362
+
1363
+ case '-':
1364
+ // * -1
1365
+
1366
+ if (decl.prefix && decl.argument.type === 'Literal' && typeof decl.argument.value === 'number') {
1367
+ // if -<N>, just return that
1368
+ return number(-1 * decl.argument.value);
1369
+ }
1370
+
1371
+ return [
1372
+ ...generate(scope, decl.argument),
1373
+ ...(valtype === 'f64' ? [ [ Opcodes.f64_neg ] ] : [ ...number(-1), [ Opcodes.mul ] ])
1374
+ ];
1375
+
1376
+ case '!':
1377
+ // !=
1378
+ return falsy(scope, generate(scope, decl.argument));
1379
+
1380
+ case '~':
1381
+ return [
1382
+ ...generate(scope, decl.argument),
1383
+ Opcodes.i32_to,
1384
+ [ Opcodes.i32_const, signedLEB128(-1) ],
1385
+ [ Opcodes.i32_xor ],
1386
+ Opcodes.i32_from
1387
+ ];
1388
+
1389
+ case 'void': {
1390
+ // drop current expression value after running, give undefined
1391
+ const out = generate(scope, decl.argument);
1392
+ disposeLeftover(out);
1393
+
1394
+ out.push(...number(UNDEFINED));
1395
+ return out;
1396
+ }
1397
+
1398
+ case 'delete':
1399
+ let toReturn = true, toGenerate = true;
1400
+
1401
+ if (decl.argument.type === 'Identifier') {
1402
+ const out = generateIdent(scope, decl.argument);
1403
+
1404
+ // if ReferenceError (undeclared var), ignore and return true. otherwise false
1405
+ if (!out[1]) {
1406
+ // exists
1407
+ toReturn = false;
1408
+ } else {
1409
+ // does not exist (2 ops from throw)
1410
+ toReturn = true;
1411
+ toGenerate = false;
1412
+ }
1413
+ }
1414
+
1415
+ const out = toGenerate ? generate(scope, decl.argument) : [];
1416
+ disposeLeftover(out);
1417
+
1418
+ out.push(...number(toReturn ? 1 : 0));
1419
+ return out;
1420
+
1421
+ case 'typeof':
1422
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1423
+
1424
+ // for custom types, just return object
1425
+ if (type > 0xffffffffffff7) return number(TYPES.object);
1426
+ return number(type);
1427
+
1428
+ default:
1429
+ return todo(`unary operator ${decl.operator} not implemented yet`);
1430
+ }
1431
+ };
1432
+
1433
+ const generateUpdate = (scope, decl) => {
1434
+ const { name } = decl.argument;
1435
+
1436
+ const [ local, isGlobal ] = lookupName(scope, name);
1437
+
1438
+ if (local === undefined) {
1439
+ return todo(`update expression with undefined variable`);
1440
+ }
1441
+
1442
+ const idx = local.idx;
1443
+ const out = [];
1444
+
1445
+ out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
1446
+ if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
1447
+
1448
+ switch (decl.operator) {
1449
+ case '++':
1450
+ out.push(...number(1), [ Opcodes.add ]);
1451
+ break;
1452
+
1453
+ case '--':
1454
+ out.push(...number(1), [ Opcodes.sub ]);
1455
+ break;
1456
+ }
1457
+
1458
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
1459
+ if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
1460
+
1461
+ return out;
1462
+ };
1463
+
1464
+ const generateIf = (scope, decl) => {
1465
+ const out = truthy(scope, generate(scope, decl.test), decl.test);
1466
+
1467
+ out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1468
+ depth.push('if');
1469
+
1470
+ const consOut = generate(scope, decl.consequent);
1471
+ disposeLeftover(consOut);
1472
+ out.push(...consOut);
1473
+
1474
+ if (decl.alternate) {
1475
+ out.push([ Opcodes.else ]);
1476
+
1477
+ const altOut = generate(scope, decl.alternate);
1478
+ disposeLeftover(altOut);
1479
+ out.push(...altOut);
1480
+ }
1481
+
1482
+ out.push([ Opcodes.end ]);
1483
+ depth.pop();
1484
+
1485
+ return out;
1486
+ };
1487
+
1488
+ const generateConditional = (scope, decl) => {
1489
+ const out = [ ...generate(scope, decl.test) ];
1490
+
1491
+ out.push(Opcodes.i32_to, [ Opcodes.if, valtypeBinary ]);
1492
+ depth.push('if');
1493
+
1494
+ out.push(...generate(scope, decl.consequent));
1495
+
1496
+ out.push([ Opcodes.else ]);
1497
+ out.push(...generate(scope, decl.alternate));
1498
+
1499
+ out.push([ Opcodes.end ]);
1500
+ depth.pop();
1501
+
1502
+ return out;
1503
+ };
1504
+
1505
+ let depth = [];
1506
+ const generateFor = (scope, decl) => {
1507
+ const out = [];
1508
+
1509
+ if (decl.init) {
1510
+ out.push(...generate(scope, decl.init));
1511
+ disposeLeftover(out);
1512
+ }
1513
+
1514
+ out.push([ Opcodes.loop, Blocktype.void ]);
1515
+ depth.push('for');
1516
+
1517
+ out.push(...generate(scope, decl.test));
1518
+ out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1519
+ depth.push('if');
1520
+
1521
+ out.push([ Opcodes.block, Blocktype.void ]);
1522
+ depth.push('block');
1523
+ out.push(...generate(scope, decl.body));
1524
+ out.push([ Opcodes.end ]);
1525
+
1526
+ out.push(...generate(scope, decl.update));
1527
+ depth.pop();
1528
+
1529
+ out.push([ Opcodes.br, 1 ]);
1530
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
1531
+ depth.pop(); depth.pop();
1532
+
1533
+ return out;
1534
+ };
1535
+
1536
+ const generateWhile = (scope, decl) => {
1537
+ const out = [];
1538
+
1539
+ out.push([ Opcodes.loop, Blocktype.void ]);
1540
+ depth.push('while');
1541
+
1542
+ out.push(...generate(scope, decl.test));
1543
+ out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1544
+ depth.push('if');
1545
+
1546
+ out.push(...generate(scope, decl.body));
1547
+
1548
+ out.push([ Opcodes.br, 1 ]);
1549
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
1550
+ depth.pop(); depth.pop();
1551
+
1552
+ return out;
1553
+ };
1554
+
1555
+ const getNearestLoop = () => {
1556
+ for (let i = depth.length - 1; i >= 0; i--) {
1557
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1558
+ }
1559
+
1560
+ return -1;
1561
+ };
1562
+
1563
+ const generateBreak = (scope, decl) => {
1564
+ const nearestLoop = depth.length - getNearestLoop();
1565
+ return [
1566
+ [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
1567
+ ];
1568
+ };
1569
+
1570
+ const generateContinue = (scope, decl) => {
1571
+ const nearestLoop = depth.length - getNearestLoop();
1572
+ return [
1573
+ [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
1574
+ ];
1575
+ };
1576
+
1577
+ const generateThrow = (scope, decl) => {
1578
+ scope.throws = true;
1579
+
1580
+ let message = decl.argument.value, constructor = null;
1581
+
1582
+ // hack: throw new X("...") -> throw "..."
1583
+ if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
1584
+ constructor = decl.argument.callee.name;
1585
+ message = decl.argument.arguments[0].value;
1586
+ }
1587
+
1588
+ if (tags.length === 0) tags.push({
1589
+ params: [ Valtype.i32 ],
1590
+ results: [],
1591
+ idx: tags.length
1592
+ });
1593
+
1594
+ let exceptId = exceptions.push({ constructor, message }) - 1;
1595
+ let tagIdx = tags[0].idx;
1596
+
1597
+ // todo: write a description of how this works lol
1598
+
1599
+ return [
1600
+ [ Opcodes.i32_const, signedLEB128(exceptId) ],
1601
+ [ Opcodes.throw, tagIdx ]
1602
+ ];
1603
+ };
1604
+
1605
+ const generateTry = (scope, decl) => {
1606
+ if (decl.finalizer) return todo('try finally not implemented yet');
1607
+
1608
+ const out = [];
1609
+
1610
+ out.push([ Opcodes.try, Blocktype.void ]);
1611
+ depth.push('try');
1612
+
1613
+ out.push(...generate(scope, decl.block));
1614
+
1615
+ if (decl.handler) {
1616
+ depth.pop();
1617
+ depth.push('catch');
1618
+
1619
+ out.push([ Opcodes.catch_all ]);
1620
+ out.push(...generate(scope, decl.handler.body));
1621
+ }
1622
+
1623
+ out.push([ Opcodes.end ]);
1624
+ depth.pop();
1625
+
1626
+ return out;
1627
+ };
1628
+
1629
+ const generateEmpty = (scope, decl) => {
1630
+ return [];
1631
+ };
1632
+
1633
+ const generateAssignPat = (scope, decl) => {
1634
+ // TODO
1635
+ // if identifier declared, use that
1636
+ // else, use default (right)
1637
+ return todo('assignment pattern (optional arg)');
1638
+ };
1639
+
1640
+ let pages = new Map();
1641
+ const allocPage = reason => {
1642
+ if (pages.has(reason)) return pages.get(reason);
1643
+
1644
+ let ind = pages.size;
1645
+ pages.set(reason, ind);
1646
+
1647
+ if (codeLog) log('codegen', `allocated new page of memory (${ind}) | ${reason}`);
1648
+
1649
+ return ind;
1650
+ };
1651
+
1652
+ const itemTypeToValtype = {
1653
+ i32: 'i32',
1654
+ i64: 'i64',
1655
+ f64: 'f64',
1656
+
1657
+ i8: 'i32',
1658
+ i16: 'i32'
1659
+ };
1660
+
1661
+ const storeOps = {
1662
+ i32: Opcodes.i32_store,
1663
+ i64: Opcodes.i64_store,
1664
+ f64: Opcodes.f64_store,
1665
+
1666
+ // expects i32 input!
1667
+ i16: Opcodes.i32_store16
1668
+ };
1669
+
1670
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1671
+ const out = [];
1672
+
1673
+ if (!arrays.has(name) || name === '$undeclared') {
1674
+ // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1675
+ const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1676
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
1677
+ }
1678
+
1679
+ const pointer = arrays.get(name);
1680
+
1681
+ const useRawElements = !!decl.rawElements;
1682
+ const elements = useRawElements ? decl.rawElements : decl.elements;
1683
+
1684
+ const length = elements.length;
1685
+
1686
+ // store length as 0th array
1687
+ out.push(
1688
+ ...number(0, Valtype.i32),
1689
+ ...number(length, Valtype.i32),
1690
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1691
+ );
1692
+
1693
+ const storeOp = storeOps[itemType];
1694
+ const valtype = itemTypeToValtype[itemType];
1695
+
1696
+ if (!initEmpty) for (let i = 0; i < length; i++) {
1697
+ if (elements[i] == null) continue;
1698
+
1699
+ out.push(
1700
+ ...number(0, Valtype.i32),
1701
+ ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
1702
+ [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
1703
+ );
1704
+ }
1705
+
1706
+ // local value as pointer
1707
+ out.push(...number(pointer));
1708
+
1709
+ scope.memory = true;
1710
+
1711
+ return [ out, pointer ];
1712
+ };
1713
+
1714
+ let arrays = new Map();
1715
+ const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1716
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1717
+ };
1718
+
1719
+ export const generateMember = (scope, decl, _global, _name) => {
1720
+ const type = getNodeType(scope, decl.object);
1721
+
1722
+ // hack: .length
1723
+ if (decl.property.name === 'length') {
1724
+ // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
1725
+
1726
+ const name = decl.object.name;
1727
+ const pointer = arrays.get(name);
1728
+
1729
+ scope.memory = true;
1730
+
1731
+ const aotPointer = pointer != null;
1732
+
1733
+ return [
1734
+ ...(aotPointer ? number(0, Valtype.i32) : [
1735
+ ...generate(scope, decl.object),
1736
+ Opcodes.i32_to_u
1737
+ ]),
1738
+
1739
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
1740
+ Opcodes.i32_from_u
1741
+ ];
1742
+ }
1743
+
1744
+ // this is just for arr[ind] for now. objects are partially supported via object hack (a.b -> __a_b)
1745
+ if (![TYPES._array, TYPES.string].includes(type)) return todo(`computed member expression for objects are not supported yet`);
1746
+
1747
+ const name = decl.object.name;
1748
+ const pointer = arrays.get(name);
1749
+
1750
+ scope.memory = true;
1751
+
1752
+ const aotPointer = pointer != null;
1753
+
1754
+ if (type === TYPES._array) {
1755
+ return [
1756
+ // get index as valtype
1757
+ ...generate(scope, decl.property),
1758
+
1759
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1760
+ Opcodes.i32_to_u,
1761
+ ...number(ValtypeSize[valtype], Valtype.i32),
1762
+ [ Opcodes.i32_mul ],
1763
+
1764
+ ...(aotPointer ? [] : [
1765
+ ...generate(scope, decl.object),
1766
+ Opcodes.i32_to_u,
1767
+ [ Opcodes.i32_add ]
1768
+ ]),
1769
+
1770
+ // read from memory
1771
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1772
+ ];
1773
+ }
1774
+
1775
+ // string
1776
+
1777
+ const [ newOut, newPointer ] = makeArray(scope, {
1778
+ rawElements: new Array(1)
1779
+ }, _global, _name, true, 'i16');
1780
+
1781
+ return [
1782
+ // setup new/out array
1783
+ ...newOut,
1784
+ [ Opcodes.drop ],
1785
+
1786
+ ...number(0, Valtype.i32), // base 0 for store later
1787
+
1788
+ ...generate(scope, decl.property),
1789
+
1790
+ Opcodes.i32_to_u,
1791
+ ...number(ValtypeSize.i16, Valtype.i32),
1792
+ [ Opcodes.i32_mul ],
1793
+
1794
+ ...(aotPointer ? [] : [
1795
+ ...generate(scope, decl.object),
1796
+ Opcodes.i32_to_u,
1797
+ [ Opcodes.i32_add ]
1798
+ ]),
1799
+
1800
+ // load current string ind {arg}
1801
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
1802
+
1803
+ // store to new string ind 0
1804
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1805
+
1806
+ // return new string (page)
1807
+ ...number(newPointer)
1808
+ ];
1809
+ };
1810
+
1811
+ const randId = () => Math.random().toString(16).slice(0, -4);
1812
+
1813
+ const objectHack = node => {
1814
+ if (!node) return node;
1815
+
1816
+ if (node.type === 'MemberExpression') {
1817
+ if (node.computed || node.optional) return node;
1818
+
1819
+ let objectName = node.object.name;
1820
+
1821
+ // if object is not identifier or another member exp, give up
1822
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
1823
+
1824
+ if (!objectName) objectName = objectHack(node.object).name.slice(2);
1825
+
1826
+ // if .length, give up (hack within a hack!)
1827
+ if (node.property.name === 'length') return node;
1828
+
1829
+ const name = '__' + objectName + '_' + node.property.name;
1830
+ if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
1831
+
1832
+ return {
1833
+ type: 'Identifier',
1834
+ name
1835
+ };
1836
+ }
1837
+
1838
+ for (const x in node) {
1839
+ if (node[x] != null && typeof node[x] === 'object') {
1840
+ if (node[x].type) node[x] = objectHack(node[x]);
1841
+ if (Array.isArray(node[x])) node[x] = node[x].map(y => objectHack(y));
1842
+ }
1843
+ }
1844
+
1845
+ return node;
1846
+ };
1847
+
1848
+ const generateFunc = (scope, decl) => {
1849
+ if (decl.async) return todo('async functions are not supported');
1850
+ if (decl.generator) return todo('generator functions are not supported');
1851
+
1852
+ const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
1853
+ const params = decl.params?.map(x => x.name) ?? [];
1854
+
1855
+ // const innerScope = { ...scope };
1856
+ // TODO: share scope/locals between !!!
1857
+ const innerScope = {
1858
+ locals: {},
1859
+ localInd: 0,
1860
+ returns: [ valtypeBinary ],
1861
+ memory: false,
1862
+ throws: false,
1863
+ name
1864
+ };
1865
+
1866
+ for (let i = 0; i < params.length; i++) {
1867
+ const param = params[i];
1868
+ innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
1869
+ }
1870
+
1871
+ let body = objectHack(decl.body);
1872
+ if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
1873
+ // hack: () => 0 -> () => return 0
1874
+ body = {
1875
+ type: 'ReturnStatement',
1876
+ argument: decl.body
1877
+ };
1878
+ }
1879
+
1880
+ const wasm = generate(innerScope, body);
1881
+ const func = {
1882
+ name,
1883
+ params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1884
+ returns: innerScope.returns,
1885
+ returnType: innerScope.returnType,
1886
+ locals: innerScope.locals,
1887
+ memory: innerScope.memory,
1888
+ throws: innerScope.throws,
1889
+ index: currentFuncIndex++
1890
+ };
1891
+ funcIndex[name] = func.index;
1892
+
1893
+ // quick hack fixes
1894
+ for (const inst of wasm) {
1895
+ if (inst[0] === Opcodes.call && inst[1] === -1) {
1896
+ inst[1] = func.index;
1897
+ }
1898
+ }
1899
+
1900
+ if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1901
+ wasm.push(...number(0), [ Opcodes.return ]);
1902
+ }
1903
+
1904
+ // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
1905
+ let offset = 0, vecParams = 0;
1906
+ for (let i = 0; i < params.length; i++) {
1907
+ const name = params[i];
1908
+ const local = func.locals[name];
1909
+ if (local.type === Valtype.v128) {
1910
+ vecParams++;
1911
+
1912
+ /* func.memory = true; // mark func as using memory
1913
+
1914
+ wasm.unshift( // add v128 load for param
1915
+ [ Opcodes.i32_const, 0 ],
1916
+ [ ...Opcodes.v128_load, 0, i * 16 ],
1917
+ [ Opcodes.local_set, local.idx ]
1918
+ ); */
1919
+
1920
+ // using params and replace_lane is noticably faster than just loading from memory (above) somehow
1921
+
1922
+ // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
1923
+ const { vecType } = local;
1924
+ let [ type, lanes ] = vecType.split('x');
1925
+ if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
1926
+
1927
+ lanes = parseInt(lanes);
1928
+ type = Valtype[type];
1929
+
1930
+ const name = params[i]; // get original param name
1931
+
1932
+ func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
1933
+
1934
+ // update index of original local
1935
+ // delete func.locals[name];
1936
+
1937
+ // add new locals for params
1938
+ for (let j = 0; j < lanes; j++) {
1939
+ func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
1940
+ }
1941
+
1942
+ // prepend wasm to generate expected v128 locals
1943
+ wasm.splice(i * 2 + offset * 2, 0,
1944
+ ...i32x4(0, 0, 0, 0),
1945
+ ...new Array(lanes).fill(0).flatMap((_, j) => [
1946
+ [ Opcodes.local_get, offset + j ],
1947
+ [ ...Opcodes[vecType + '_replace_lane'], j ]
1948
+ ]),
1949
+ [ Opcodes.local_set, i ]
1950
+ );
1951
+
1952
+ offset += lanes;
1953
+
1954
+ // note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
1955
+ /* if (!func.name.startsWith('#')) func.name = '##' + func.name;
1956
+
1957
+ // add vec type index to hash name prefix for wrapper to know how to wrap
1958
+ const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
1959
+ const secondHash = func.name.slice(1).indexOf('#');
1960
+ func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
1961
+ }
1962
+ }
1963
+
1964
+ if (offset !== 0) {
1965
+ // bump local indexes for all other locals after
1966
+ for (const x in func.locals) {
1967
+ const local = func.locals[x];
1968
+ if (!local.vecParamAutogen) local.idx += offset;
1969
+ }
1970
+
1971
+ // bump local indexes in wasm local.get/set
1972
+ for (let j = 0; j < wasm.length; j++) {
1973
+ const inst = wasm[j];
1974
+ if (j < offset * 2 + vecParams * 2) {
1975
+ if (inst[0] === Opcodes.local_set) inst[1] += offset;
1976
+ continue;
1977
+ }
1978
+
1979
+ if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
1980
+ }
1981
+ }
1982
+
1983
+ // change v128 return into many <type> instead as unsupported return valtype
1984
+ 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]);
1985
+ if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
1986
+ const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
1987
+ // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
1988
+ const { vecType } = lastReturnLocal;
1989
+ let [ type, lanes ] = vecType.split('x');
1990
+ if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
1991
+
1992
+ lanes = parseInt(lanes);
1993
+ type = Valtype[type];
1994
+
1995
+ const vecIdx = lastReturnLocal.idx;
1996
+
1997
+ const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
1998
+ const tmpIdx = [];
1999
+ for (let i = 0; i < lanes; i++) {
2000
+ const idx = lastIdx + i + 1;
2001
+ tmpIdx.push(idx);
2002
+ func.locals[name + i] = { idx, type, vecReturnAutogen: true };
2003
+ }
2004
+
2005
+ wasm.splice(wasm.length - 1, 1,
2006
+ ...new Array(lanes).fill(0).flatMap((_, i) => [
2007
+ i === 0 ? null : [ Opcodes.local_get, vecIdx ],
2008
+ [ ...Opcodes[vecType + '_extract_lane'], i ],
2009
+ [ Opcodes.local_set, tmpIdx[i] ],
2010
+ ].filter(x => x !== null)),
2011
+ ...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
2012
+ );
2013
+
2014
+ func.returns = new Array(lanes).fill(type);
2015
+ }
2016
+
2017
+ func.wasm = wasm;
2018
+
2019
+ funcs.push(func);
2020
+
2021
+ return func;
2022
+ };
2023
+
2024
+ const generateCode = (scope, decl) => {
2025
+ const out = [];
2026
+
2027
+ for (const x of decl.body) {
2028
+ out.push(...generate(scope, x));
2029
+ }
2030
+
2031
+ return out;
2032
+ };
2033
+
2034
+ const internalConstrs = {
2035
+ Array: {
2036
+ generate: (scope, decl, global, name) => {
2037
+ // new Array(i0, i1, ...)
2038
+ if (decl.arguments.length > 1) return generateArray(scope, {
2039
+ elements: decl.arguments
2040
+ }, global, name);
2041
+
2042
+ // new Array(n)
2043
+
2044
+ const [ , pointer ] = makeArray(scope, {
2045
+ rawElements: new Array(0)
2046
+ }, global, name, true);
2047
+
2048
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
2049
+
2050
+ // todo: check in wasm instead of here
2051
+ const literalValue = arg.value ?? 0;
2052
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
2053
+
2054
+ return [
2055
+ ...number(0, Valtype.i32),
2056
+ ...generate(scope, arg, global, name),
2057
+ Opcodes.i32_to_u,
2058
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
2059
+
2060
+ ...number(pointer)
2061
+ ];
2062
+ },
2063
+ type: TYPES._array
2064
+ }
2065
+ };
2066
+
2067
+ export default program => {
2068
+ globals = {};
2069
+ globalInd = 0;
2070
+ tags = [];
2071
+ exceptions = [];
2072
+ funcs = [];
2073
+ funcIndex = {};
2074
+ depth = [];
2075
+ typeStates = {};
2076
+ arrays = new Map();
2077
+ pages = new Map();
2078
+ currentFuncIndex = importedFuncs.length;
2079
+
2080
+ globalThis.valtype = 'f64';
2081
+
2082
+ const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
2083
+ if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
2084
+
2085
+ globalThis.valtypeBinary = Valtype[valtype];
2086
+
2087
+ const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2088
+
2089
+ // set generic opcodes for current valtype
2090
+ Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2091
+ Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
2092
+ Opcodes.eqz = [ [ [ Opcodes.i32_eqz ] ], [ [ Opcodes.i64_eqz ] ], [ ...number(0), [ Opcodes.f64_eq ] ] ][valtypeInd];
2093
+ Opcodes.mul = [ Opcodes.i32_mul, Opcodes.i64_mul, Opcodes.f64_mul ][valtypeInd];
2094
+ Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2095
+ Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2096
+
2097
+ Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2098
+ Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2099
+ Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2100
+ Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
2101
+
2102
+ Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2103
+ Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
2104
+
2105
+ Opcodes.lt = [ Opcodes.i32_lt_s, Opcodes.i64_lt_s, Opcodes.f64_lt ][valtypeInd];
2106
+
2107
+ builtinFuncs = new BuiltinFuncs();
2108
+ builtinVars = new BuiltinVars();
2109
+ prototypeFuncs = new PrototypeFuncs();
2110
+
2111
+ program.id = { name: 'main' };
2112
+
2113
+ globalThis.pageSize = PageSize;
2114
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2115
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2116
+
2117
+ const scope = {
2118
+ locals: {},
2119
+ localInd: 0
2120
+ };
2121
+
2122
+ program.body = {
2123
+ type: 'BlockStatement',
2124
+ body: program.body
2125
+ };
2126
+
2127
+ generateFunc(scope, program);
2128
+
2129
+ const main = funcs[funcs.length - 1];
2130
+ main.export = true;
2131
+ main.returns = [ valtypeBinary ];
2132
+
2133
+ const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
2134
+ if (lastInst[0] === Opcodes.drop) {
2135
+ main.wasm.splice(main.wasm.length - 1, 1);
2136
+
2137
+ const finalStatement = program.body.body[program.body.body.length - 1];
2138
+ main.returnType = getNodeType(main, finalStatement);
2139
+ }
2140
+
2141
+ if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
2142
+ main.returns = [];
2143
+ }
2144
+
2145
+ if (lastInst[0] === Opcodes.call) {
2146
+ const func = funcs.find(x => x.index === lastInst[1]);
2147
+ if (func) main.returns = func.returns.slice();
2148
+ else main.returns = [];
2149
+ }
2150
+
2151
+ // if blank main func and other exports, remove it
2152
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2153
+
2154
+ return { funcs, globals, tags, exceptions, pages };
2155
+ };