porffor 0.0.0-05f898f

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