porffor 0.1.1 → 0.2.0-09999e8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -204
- package/compiler/2c.js +385 -0
- package/compiler/builtins/base64.js +91 -91
- package/compiler/builtins.js +19 -13
- package/compiler/codeGen.js +1420 -530
- package/compiler/decompile.js +41 -16
- package/compiler/embedding.js +9 -5
- package/compiler/encoding.js +6 -114
- package/compiler/index.js +56 -17
- package/compiler/log.js +15 -0
- package/compiler/opt.js +367 -258
- package/compiler/parse.js +49 -3
- package/compiler/prototype.js +263 -56
- package/compiler/sections.js +51 -8
- package/compiler/wasmSpec.js +3 -0
- package/compiler/wrap.js +23 -9
- package/package.json +6 -1
- package/porf.cmd +1 -1
- package/rhemyn/README.md +37 -0
- package/rhemyn/compile.js +214 -0
- package/rhemyn/parse.js +321 -0
- package/rhemyn/test/parse.js +59 -0
- package/runner/index.js +54 -31
- package/runner/info.js +37 -2
- package/runner/profile.js +1 -2
- package/runner/repl.js +13 -11
- package/runner/results.json +1 -0
- package/runner/transform.js +15 -36
- package/runner/version.js +10 -0
- package/CNAME +0 -1
- package/index.html +0 -1264
- package/logo.png +0 -0
- package/sw.js +0 -26
package/compiler/codeGen.js
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
3
3
|
import { operatorOpcode } from "./expression.js";
|
4
4
|
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
6
|
-
import { number, i32x4 } from "./embedding.js";
|
6
|
+
import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
|
7
|
+
import { log } from "./log.js";
|
7
8
|
import parse from "./parse.js";
|
9
|
+
import * as Rhemyn from "../rhemyn/compile.js";
|
8
10
|
|
9
11
|
let globals = {};
|
10
12
|
let globalInd = 0;
|
@@ -35,7 +37,14 @@ const debug = str => {
|
|
35
37
|
};
|
36
38
|
|
37
39
|
const todo = msg => {
|
38
|
-
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
throw new TodoError(`todo: ${msg}`);
|
39
48
|
|
40
49
|
const code = [];
|
41
50
|
|
@@ -101,6 +110,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
101
110
|
case 'WhileStatement':
|
102
111
|
return generateWhile(scope, decl);
|
103
112
|
|
113
|
+
case 'ForOfStatement':
|
114
|
+
return generateForOf(scope, decl);
|
115
|
+
|
104
116
|
case 'BreakStatement':
|
105
117
|
return generateBreak(scope, decl);
|
106
118
|
|
@@ -141,47 +153,72 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
141
153
|
|
142
154
|
return [];
|
143
155
|
|
144
|
-
case 'TaggedTemplateExpression':
|
145
|
-
|
146
|
-
|
156
|
+
case 'TaggedTemplateExpression': {
|
157
|
+
const funcs = {
|
158
|
+
asm: str => {
|
159
|
+
let out = [];
|
147
160
|
|
148
|
-
|
149
|
-
|
161
|
+
for (const line of str.split('\n')) {
|
162
|
+
const asm = line.trim().split(';;')[0].split(' ');
|
163
|
+
if (asm[0] === '') continue; // blank
|
150
164
|
|
151
|
-
|
152
|
-
|
153
|
-
|
165
|
+
if (asm[0] === 'local') {
|
166
|
+
const [ name, idx, type ] = asm.slice(1);
|
167
|
+
scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
|
168
|
+
continue;
|
169
|
+
}
|
154
170
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
}
|
171
|
+
if (asm[0] === 'returns') {
|
172
|
+
scope.returns = asm.slice(1).map(x => Valtype[x]);
|
173
|
+
continue;
|
174
|
+
}
|
160
175
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
176
|
+
if (asm[0] === 'memory') {
|
177
|
+
allocPage('asm instrinsic');
|
178
|
+
// todo: add to store/load offset insts
|
179
|
+
continue;
|
180
|
+
}
|
165
181
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
182
|
+
let inst = Opcodes[asm[0].replace('.', '_')];
|
183
|
+
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
184
|
+
|
185
|
+
if (!Array.isArray(inst)) inst = [ inst ];
|
186
|
+
const immediates = asm.slice(1).map(x => parseInt(x));
|
187
|
+
|
188
|
+
out.push([ ...inst, ...immediates ]);
|
189
|
+
}
|
190
|
+
|
191
|
+
return out;
|
192
|
+
},
|
172
193
|
|
173
|
-
|
174
|
-
|
194
|
+
__internal_print_type: str => {
|
195
|
+
const type = getType(scope, str) - TYPES.number;
|
175
196
|
|
176
|
-
|
177
|
-
|
197
|
+
return [
|
198
|
+
...number(type),
|
199
|
+
[ Opcodes.call, importedFuncs.print ],
|
178
200
|
|
179
|
-
|
201
|
+
// newline
|
202
|
+
...number(10),
|
203
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
204
|
+
];
|
205
|
+
}
|
180
206
|
}
|
181
207
|
|
182
|
-
|
208
|
+
const name = decl.tag.name;
|
209
|
+
// hack for inline asm
|
210
|
+
if (!funcs[name]) return todo('tagged template expressions not implemented');
|
211
|
+
|
212
|
+
const str = decl.quasi.quasis[0].value.raw;
|
213
|
+
return funcs[name](str);
|
214
|
+
}
|
183
215
|
|
184
216
|
default:
|
217
|
+
if (decl.type.startsWith('TS')) {
|
218
|
+
// ignore typescript nodes
|
219
|
+
return [];
|
220
|
+
}
|
221
|
+
|
185
222
|
return todo(`no generation for ${decl.type}!`);
|
186
223
|
}
|
187
224
|
};
|
@@ -269,19 +306,17 @@ const generateIdent = (scope, decl) => {
|
|
269
306
|
|
270
307
|
const generateReturn = (scope, decl) => {
|
271
308
|
if (decl.argument === null) {
|
272
|
-
if (!scope.returnType) scope.returnType = TYPES.undefined;
|
273
|
-
|
274
309
|
// just bare "return"
|
275
310
|
return [
|
276
311
|
...number(UNDEFINED), // "undefined" if func returns
|
312
|
+
...number(TYPES.undefined, Valtype.i32), // type undefined
|
277
313
|
[ Opcodes.return ]
|
278
314
|
];
|
279
315
|
}
|
280
316
|
|
281
|
-
if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
|
282
|
-
|
283
317
|
return [
|
284
318
|
...generate(scope, decl.argument),
|
319
|
+
...getNodeType(scope, decl.argument),
|
285
320
|
[ Opcodes.return ]
|
286
321
|
];
|
287
322
|
};
|
@@ -295,11 +330,13 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
295
330
|
return idx;
|
296
331
|
};
|
297
332
|
|
298
|
-
const
|
333
|
+
const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
334
|
+
|
335
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
299
336
|
const checks = {
|
300
|
-
'||':
|
301
|
-
'&&':
|
302
|
-
|
337
|
+
'||': falsy,
|
338
|
+
'&&': truthy,
|
339
|
+
'??': nullish
|
303
340
|
};
|
304
341
|
|
305
342
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -307,14 +344,52 @@ const performLogicOp = (scope, op, left, right) => {
|
|
307
344
|
// generic structure for {a} OP {b}
|
308
345
|
// -->
|
309
346
|
// _ = {a}; if (OP_CHECK) {b} else _
|
347
|
+
|
348
|
+
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
349
|
+
// (like if we are in an if condition - very common)
|
350
|
+
const leftIsInt = isIntOp(left[left.length - 1]);
|
351
|
+
const rightIsInt = isIntOp(right[right.length - 1]);
|
352
|
+
|
353
|
+
const canInt = leftIsInt && rightIsInt;
|
354
|
+
|
355
|
+
if (canInt) {
|
356
|
+
// remove int -> float conversions from left and right
|
357
|
+
left.pop();
|
358
|
+
right.pop();
|
359
|
+
|
360
|
+
return [
|
361
|
+
...left,
|
362
|
+
[ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
363
|
+
...checks[op](scope, [], leftType, true, true),
|
364
|
+
[ Opcodes.if, Valtype.i32 ],
|
365
|
+
...right,
|
366
|
+
// note type
|
367
|
+
...rightType,
|
368
|
+
setLastType(scope),
|
369
|
+
[ Opcodes.else ],
|
370
|
+
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
371
|
+
// note type
|
372
|
+
...leftType,
|
373
|
+
setLastType(scope),
|
374
|
+
[ Opcodes.end ],
|
375
|
+
Opcodes.i32_from
|
376
|
+
];
|
377
|
+
}
|
378
|
+
|
310
379
|
return [
|
311
380
|
...left,
|
312
381
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
313
|
-
...checks[op],
|
382
|
+
...checks[op](scope, [], leftType, false, true),
|
314
383
|
[ Opcodes.if, valtypeBinary ],
|
315
384
|
...right,
|
385
|
+
// note type
|
386
|
+
...rightType,
|
387
|
+
setLastType(scope),
|
316
388
|
[ Opcodes.else ],
|
317
389
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
390
|
+
// note type
|
391
|
+
...leftType,
|
392
|
+
setLastType(scope),
|
318
393
|
[ Opcodes.end ]
|
319
394
|
];
|
320
395
|
};
|
@@ -325,15 +400,16 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
325
400
|
// todo: optimize by looking up names in arrays and using that if exists?
|
326
401
|
// todo: optimize this if using literals/known lengths?
|
327
402
|
|
328
|
-
scope.memory = true;
|
329
|
-
|
330
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
331
|
-
|
332
403
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
404
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
405
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
335
406
|
|
407
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
408
|
+
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
409
|
+
|
336
410
|
if (assign) {
|
411
|
+
const pointer = arrays.get(name ?? '$undeclared');
|
412
|
+
|
337
413
|
return [
|
338
414
|
// setup right
|
339
415
|
...right,
|
@@ -384,15 +460,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
384
460
|
|
385
461
|
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
386
462
|
|
387
|
-
|
463
|
+
// alloc/assign array
|
464
|
+
const [ , pointer ] = makeArray(scope, {
|
388
465
|
rawElements: new Array(0)
|
389
466
|
}, global, name, true, 'i16');
|
390
467
|
|
391
468
|
return [
|
392
|
-
// setup new/out array
|
393
|
-
...newOut,
|
394
|
-
[ Opcodes.drop ],
|
395
|
-
|
396
469
|
// setup left
|
397
470
|
...left,
|
398
471
|
Opcodes.i32_to_u,
|
@@ -458,90 +531,309 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
458
531
|
];
|
459
532
|
};
|
460
533
|
|
461
|
-
const
|
462
|
-
//
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
number(0)
|
467
|
-
];
|
468
|
-
|
469
|
-
if (type === TYPES.string) {
|
470
|
-
// if "" (length = 0)
|
471
|
-
return [
|
472
|
-
// pointer
|
473
|
-
...wasm,
|
534
|
+
const compareStrings = (scope, left, right) => {
|
535
|
+
// todo: this should be rewritten into a func
|
536
|
+
// todo: convert left and right to strings if not
|
537
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
538
|
+
// todo: optimize this if using literals/known lengths?
|
474
539
|
|
475
|
-
|
476
|
-
|
540
|
+
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
541
|
+
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
542
|
+
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
543
|
+
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
477
544
|
|
478
|
-
|
479
|
-
|
480
|
-
Opcodes.i32_from_u
|
481
|
-
]
|
482
|
-
}
|
545
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
546
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
483
547
|
|
484
|
-
// if = 0
|
485
548
|
return [
|
486
|
-
|
549
|
+
// setup left
|
550
|
+
...left,
|
551
|
+
Opcodes.i32_to_u,
|
552
|
+
[ Opcodes.local_tee, leftPointer ],
|
487
553
|
|
488
|
-
|
489
|
-
|
554
|
+
// setup right
|
555
|
+
...right,
|
556
|
+
Opcodes.i32_to_u,
|
557
|
+
[ Opcodes.local_tee, rightPointer ],
|
558
|
+
|
559
|
+
// fast path: check leftPointer == rightPointer
|
560
|
+
// use if (block) for everything after to "return" a value early
|
561
|
+
[ Opcodes.i32_ne ],
|
562
|
+
[ Opcodes.if, Valtype.i32 ],
|
563
|
+
|
564
|
+
// get lengths
|
565
|
+
[ Opcodes.local_get, leftPointer ],
|
566
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
567
|
+
[ Opcodes.local_tee, leftLength ],
|
568
|
+
|
569
|
+
[ Opcodes.local_get, rightPointer ],
|
570
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
571
|
+
[ Opcodes.local_tee, rightLength ],
|
572
|
+
|
573
|
+
// fast path: check leftLength != rightLength
|
574
|
+
[ Opcodes.i32_ne ],
|
575
|
+
[ Opcodes.if, Blocktype.void ],
|
576
|
+
...number(0, Valtype.i32),
|
577
|
+
[ Opcodes.br, 1 ],
|
578
|
+
[ Opcodes.end ],
|
579
|
+
|
580
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
581
|
+
|
582
|
+
// tmp could have already been used
|
583
|
+
...number(0, Valtype.i32),
|
584
|
+
[ Opcodes.local_set, index ],
|
585
|
+
|
586
|
+
// setup index end as length * sizeof i16 (2)
|
587
|
+
// we do this instead of having to do mul/div each iter for perf™
|
588
|
+
[ Opcodes.local_get, leftLength ],
|
589
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
590
|
+
[ Opcodes.i32_mul ],
|
591
|
+
[ Opcodes.local_set, indexEnd ],
|
592
|
+
|
593
|
+
// iterate over each char and check if eq
|
594
|
+
[ Opcodes.loop, Blocktype.void ],
|
595
|
+
|
596
|
+
// fetch left
|
597
|
+
[ Opcodes.local_get, index ],
|
598
|
+
[ Opcodes.local_get, leftPointer ],
|
599
|
+
[ Opcodes.i32_add ],
|
600
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
601
|
+
|
602
|
+
// fetch right
|
603
|
+
[ Opcodes.local_get, index ],
|
604
|
+
[ Opcodes.local_get, rightPointer ],
|
605
|
+
[ Opcodes.i32_add ],
|
606
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
607
|
+
|
608
|
+
// not equal, "return" false
|
609
|
+
[ Opcodes.i32_ne ],
|
610
|
+
[ Opcodes.if, Blocktype.void ],
|
611
|
+
...number(0, Valtype.i32),
|
612
|
+
[ Opcodes.br, 2 ],
|
613
|
+
[ Opcodes.end ],
|
614
|
+
|
615
|
+
// index += sizeof i16 (2)
|
616
|
+
[ Opcodes.local_get, index ],
|
617
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
618
|
+
[ Opcodes.i32_add ],
|
619
|
+
[ Opcodes.local_tee, index ],
|
620
|
+
|
621
|
+
// if index != index end (length * sizeof 16), loop
|
622
|
+
[ Opcodes.local_get, indexEnd ],
|
623
|
+
[ Opcodes.i32_ne ],
|
624
|
+
[ Opcodes.br_if, 0 ],
|
625
|
+
[ Opcodes.end ],
|
626
|
+
|
627
|
+
// no failed checks, so true!
|
628
|
+
...number(1, Valtype.i32),
|
629
|
+
|
630
|
+
// pointers match, so true
|
631
|
+
[ Opcodes.else ],
|
632
|
+
...number(1, Valtype.i32),
|
633
|
+
[ Opcodes.end ],
|
634
|
+
|
635
|
+
// convert i32 result to valtype
|
636
|
+
// do not do as automatically added by binary exp gen for equality ops
|
637
|
+
// Opcodes.i32_from_u
|
490
638
|
];
|
491
639
|
};
|
492
640
|
|
493
|
-
const truthy = (scope, wasm, type) => {
|
494
|
-
|
495
|
-
if (type === TYPES._array) return [
|
641
|
+
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
642
|
+
if (isIntOp(wasm[wasm.length - 1])) return [
|
496
643
|
...wasm,
|
497
|
-
[ Opcodes.
|
498
|
-
number(1)
|
644
|
+
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
499
645
|
];
|
500
646
|
|
501
|
-
|
502
|
-
// if not "" (length = 0)
|
503
|
-
return [
|
504
|
-
// pointer
|
505
|
-
...wasm,
|
506
|
-
|
507
|
-
// get length
|
508
|
-
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
647
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
509
648
|
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
Opcodes.i32_from_u
|
514
|
-
]
|
515
|
-
}
|
649
|
+
const def = [
|
650
|
+
// if value != 0
|
651
|
+
[ Opcodes.local_get, tmp ],
|
516
652
|
|
517
|
-
|
518
|
-
|
519
|
-
...wasm,
|
653
|
+
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
654
|
+
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
520
655
|
|
521
656
|
/* Opcodes.eqz,
|
522
657
|
[ Opcodes.i32_eqz ],
|
523
658
|
Opcodes.i32_from */
|
524
659
|
];
|
660
|
+
|
661
|
+
return [
|
662
|
+
...wasm,
|
663
|
+
[ Opcodes.local_set, tmp ],
|
664
|
+
|
665
|
+
...typeSwitch(scope, type, {
|
666
|
+
// [TYPES.number]: def,
|
667
|
+
[TYPES._array]: [
|
668
|
+
// arrays are always truthy
|
669
|
+
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
670
|
+
],
|
671
|
+
[TYPES.string]: [
|
672
|
+
[ Opcodes.local_get, tmp ],
|
673
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
674
|
+
|
675
|
+
// get length
|
676
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
677
|
+
|
678
|
+
// if length != 0
|
679
|
+
/* [ Opcodes.i32_eqz ],
|
680
|
+
[ Opcodes.i32_eqz ], */
|
681
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
|
+
],
|
683
|
+
default: def
|
684
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
|
+
];
|
686
|
+
};
|
687
|
+
|
688
|
+
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
689
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
690
|
+
return [
|
691
|
+
...wasm,
|
692
|
+
[ Opcodes.local_set, tmp ],
|
693
|
+
|
694
|
+
...typeSwitch(scope, type, {
|
695
|
+
[TYPES._array]: [
|
696
|
+
// arrays are always truthy
|
697
|
+
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
698
|
+
],
|
699
|
+
[TYPES.string]: [
|
700
|
+
[ Opcodes.local_get, tmp ],
|
701
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
702
|
+
|
703
|
+
// get length
|
704
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
705
|
+
|
706
|
+
// if length == 0
|
707
|
+
[ Opcodes.i32_eqz ],
|
708
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
709
|
+
],
|
710
|
+
default: [
|
711
|
+
// if value == 0
|
712
|
+
[ Opcodes.local_get, tmp ],
|
713
|
+
|
714
|
+
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
715
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
716
|
+
]
|
717
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
718
|
+
];
|
719
|
+
};
|
720
|
+
|
721
|
+
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
722
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
723
|
+
return [
|
724
|
+
...wasm,
|
725
|
+
[ Opcodes.local_set, tmp ],
|
726
|
+
|
727
|
+
...typeSwitch(scope, type, {
|
728
|
+
[TYPES.undefined]: [
|
729
|
+
// undefined
|
730
|
+
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
731
|
+
],
|
732
|
+
[TYPES.object]: [
|
733
|
+
// object, null if == 0
|
734
|
+
[ Opcodes.local_get, tmp ],
|
735
|
+
|
736
|
+
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
737
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
738
|
+
],
|
739
|
+
default: [
|
740
|
+
// not
|
741
|
+
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
742
|
+
]
|
743
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
744
|
+
];
|
525
745
|
};
|
526
746
|
|
527
|
-
const
|
747
|
+
const stringOnly = wasm => {
|
748
|
+
if (!Array.isArray(wasm[0])) return [ ...wasm, 'string_only' ];
|
749
|
+
if (wasm.length === 1) return [ [ ...wasm[0], 'string_only' ] ];
|
750
|
+
|
751
|
+
return [
|
752
|
+
[ ...wasm[0], 'string_only|start' ],
|
753
|
+
...wasm.slice(1, -1),
|
754
|
+
[ ...wasm[wasm.length - 1], 'string_only|end' ]
|
755
|
+
];
|
756
|
+
}
|
757
|
+
|
758
|
+
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
528
759
|
if (op === '||' || op === '&&' || op === '??') {
|
529
|
-
return performLogicOp(scope, op, left, right);
|
760
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
761
|
+
}
|
762
|
+
|
763
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
764
|
+
const strictOp = op === '===' || op === '!==';
|
765
|
+
|
766
|
+
const startOut = [], endOut = [];
|
767
|
+
const finalise = out => startOut.concat(out, endOut);
|
768
|
+
|
769
|
+
// if strict (in)equal check types match
|
770
|
+
if (strictOp) {
|
771
|
+
// startOut.push(
|
772
|
+
// ...leftType,
|
773
|
+
// ...rightType,
|
774
|
+
// [ Opcodes.i32_eq ]
|
775
|
+
// );
|
776
|
+
|
777
|
+
// endOut.push(
|
778
|
+
// [ Opcodes.i32_and ]
|
779
|
+
// );
|
780
|
+
|
781
|
+
// startOut.push(
|
782
|
+
// [ Opcodes.block, Valtype.i32 ],
|
783
|
+
// ...leftType,
|
784
|
+
// ...rightType,
|
785
|
+
// [ Opcodes.i32_ne ],
|
786
|
+
// [ Opcodes.if, Blocktype.void ],
|
787
|
+
// ...number(op === '===' ? 0 : 1, Valtype.i32),
|
788
|
+
// [ Opcodes.br, 1 ],
|
789
|
+
// [ Opcodes.end ]
|
790
|
+
// );
|
791
|
+
|
792
|
+
// endOut.push(
|
793
|
+
// [ Opcodes.end ]
|
794
|
+
// );
|
795
|
+
|
796
|
+
endOut.push(
|
797
|
+
...leftType,
|
798
|
+
...rightType,
|
799
|
+
...(op === '===' ? [
|
800
|
+
[ Opcodes.i32_eq ],
|
801
|
+
[ Opcodes.i32_and ]
|
802
|
+
] : [
|
803
|
+
[ Opcodes.i32_ne ],
|
804
|
+
[ Opcodes.i32_or ]
|
805
|
+
])
|
806
|
+
);
|
530
807
|
}
|
531
808
|
|
532
|
-
if
|
533
|
-
|
534
|
-
// string concat (a + b)
|
535
|
-
return concatStrings(scope, left, right, _global, _name, assign);
|
536
|
-
}
|
809
|
+
// todo: if equality op and an operand is undefined, return false
|
810
|
+
// todo: niche null hell with 0
|
537
811
|
|
538
|
-
|
539
|
-
|
812
|
+
// if (leftType === TYPES.string || rightType === TYPES.string) {
|
813
|
+
// if (op === '+') {
|
814
|
+
// // string concat (a + b)
|
815
|
+
// return finalise(concatStrings(scope, left, right, _global, _name, assign));
|
816
|
+
// }
|
540
817
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
818
|
+
// // not an equality op, NaN
|
819
|
+
// if (!eqOp) return finalise(number(NaN));
|
820
|
+
|
821
|
+
// // else leave bool ops
|
822
|
+
// // todo: convert string to number if string and number/bool
|
823
|
+
// // todo: string (>|>=|<|<=) string
|
824
|
+
|
825
|
+
// // string comparison
|
826
|
+
// if (op === '===' || op === '==') {
|
827
|
+
// return finalise(compareStrings(scope, left, right));
|
828
|
+
// }
|
829
|
+
|
830
|
+
// if (op === '!==' || op === '!=') {
|
831
|
+
// return finalise([
|
832
|
+
// ...compareStrings(scope, left, right),
|
833
|
+
// [ Opcodes.i32_eqz ]
|
834
|
+
// ]);
|
835
|
+
// }
|
836
|
+
// }
|
545
837
|
|
546
838
|
let ops = operatorOpcode[valtype][op];
|
547
839
|
|
@@ -551,35 +843,99 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
551
843
|
includeBuiltin(scope, builtinName);
|
552
844
|
const idx = funcIndex[builtinName];
|
553
845
|
|
554
|
-
return [
|
846
|
+
return finalise([
|
555
847
|
...left,
|
556
848
|
...right,
|
557
849
|
[ Opcodes.call, idx ]
|
558
|
-
];
|
850
|
+
]);
|
559
851
|
}
|
560
852
|
|
561
853
|
if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
|
562
854
|
|
563
855
|
if (!Array.isArray(ops)) ops = [ ops ];
|
856
|
+
ops = [ ops ];
|
857
|
+
|
858
|
+
let tmpLeft, tmpRight;
|
859
|
+
// if equal op, check if strings for compareStrings
|
860
|
+
if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
|
861
|
+
const knownLeft = knownType(scope, leftType);
|
862
|
+
const knownRight = knownType(scope, rightType);
|
863
|
+
|
864
|
+
// todo: intelligent partial skip later
|
865
|
+
// if neither known are string, stop this madness
|
866
|
+
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
867
|
+
return;
|
868
|
+
}
|
564
869
|
|
565
|
-
|
870
|
+
tmpLeft = localTmp(scope, '__tmpop_left');
|
871
|
+
tmpRight = localTmp(scope, '__tmpop_right');
|
872
|
+
|
873
|
+
ops.unshift(...stringOnly([
|
874
|
+
// if left is string
|
875
|
+
...leftType,
|
876
|
+
...number(TYPES.string, Valtype.i32),
|
877
|
+
[ Opcodes.i32_eq ],
|
878
|
+
|
879
|
+
// if right is string
|
880
|
+
...rightType,
|
881
|
+
...number(TYPES.string, Valtype.i32),
|
882
|
+
[ Opcodes.i32_eq ],
|
883
|
+
|
884
|
+
// if either are true
|
885
|
+
[ Opcodes.i32_or ],
|
886
|
+
[ Opcodes.if, Blocktype.void ],
|
887
|
+
|
888
|
+
// todo: convert non-strings to strings, for now fail immediately if one is not
|
889
|
+
// if left is not string
|
890
|
+
...leftType,
|
891
|
+
...number(TYPES.string, Valtype.i32),
|
892
|
+
[ Opcodes.i32_ne ],
|
893
|
+
|
894
|
+
// if right is not string
|
895
|
+
...rightType,
|
896
|
+
...number(TYPES.string, Valtype.i32),
|
897
|
+
[ Opcodes.i32_ne ],
|
898
|
+
|
899
|
+
// if either are true
|
900
|
+
[ Opcodes.i32_or ],
|
901
|
+
[ Opcodes.if, Blocktype.void ],
|
902
|
+
...number(0, Valtype.i32),
|
903
|
+
[ Opcodes.br, 1 ],
|
904
|
+
[ Opcodes.end ],
|
905
|
+
|
906
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
907
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
908
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
909
|
+
[ Opcodes.br, 1 ],
|
910
|
+
[ Opcodes.end ],
|
911
|
+
]));
|
912
|
+
|
913
|
+
// if not already in block, add a block
|
914
|
+
// if (endOut.length === 0) {
|
915
|
+
startOut.push(stringOnly([ Opcodes.block, Valtype.i32 ]));
|
916
|
+
// endOut.push(stringOnly([ Opcodes.end ]));
|
917
|
+
endOut.unshift(stringOnly([ Opcodes.end ]));
|
918
|
+
// }
|
919
|
+
})();
|
920
|
+
|
921
|
+
return finalise([
|
566
922
|
...left,
|
923
|
+
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
567
924
|
...right,
|
568
|
-
|
569
|
-
|
925
|
+
...(tmpRight != null ? stringOnly([ [ Opcodes.local_tee, tmpRight ] ]) : []),
|
926
|
+
...ops
|
927
|
+
]);
|
570
928
|
};
|
571
929
|
|
572
930
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
573
|
-
const out =
|
574
|
-
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
575
|
-
];
|
931
|
+
const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
|
576
932
|
|
577
933
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
578
934
|
|
579
935
|
return out;
|
580
936
|
};
|
581
937
|
|
582
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType,
|
938
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
|
583
939
|
const existing = funcs.find(x => x.name === name);
|
584
940
|
if (existing) return existing;
|
585
941
|
|
@@ -615,7 +971,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
615
971
|
returns,
|
616
972
|
returnType: TYPES[returnType ?? 'number'],
|
617
973
|
wasm,
|
618
|
-
memory,
|
619
974
|
internal: true,
|
620
975
|
index: currentFuncIndex++
|
621
976
|
};
|
@@ -634,21 +989,45 @@ const includeBuiltin = (scope, builtin) => {
|
|
634
989
|
};
|
635
990
|
|
636
991
|
const generateLogicExp = (scope, decl) => {
|
637
|
-
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
|
992
|
+
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
638
993
|
};
|
639
994
|
|
995
|
+
// T = JS type, V = value/pointer
|
996
|
+
// 0bTTT
|
997
|
+
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
998
|
+
// 50 bits usable: 0 11111111111 11??????????????????????????????????????????????????
|
999
|
+
// js type: 4 bits
|
1000
|
+
// internal type: ? bits
|
1001
|
+
// pointer: 32 bits
|
1002
|
+
// https://piotrduperas.com/posts/nan-boxing
|
1003
|
+
// 0x7ffc000000000000
|
1004
|
+
// budget: 50 bits
|
1005
|
+
// js type: 4 bits
|
1006
|
+
// internal type: ? bits
|
1007
|
+
// pointer: 32 bits
|
1008
|
+
|
1009
|
+
// generic
|
1010
|
+
// 1 23 4 5
|
1011
|
+
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
1012
|
+
// 1: regular iEEE 754 double NaN
|
1013
|
+
// 2: extra 1 bit to identify NaN box
|
1014
|
+
// 3: js type
|
1015
|
+
// 4: internal type
|
1016
|
+
// 5: pointer
|
1017
|
+
|
640
1018
|
const TYPES = {
|
641
|
-
number:
|
642
|
-
boolean:
|
643
|
-
string:
|
644
|
-
undefined:
|
645
|
-
object:
|
646
|
-
function:
|
647
|
-
symbol:
|
648
|
-
bigint:
|
1019
|
+
number: 0x00,
|
1020
|
+
boolean: 0x01,
|
1021
|
+
string: 0x02,
|
1022
|
+
undefined: 0x03,
|
1023
|
+
object: 0x04,
|
1024
|
+
function: 0x05,
|
1025
|
+
symbol: 0x06,
|
1026
|
+
bigint: 0x07,
|
649
1027
|
|
650
1028
|
// these are not "typeof" types but tracked internally
|
651
|
-
_array:
|
1029
|
+
_array: 0x10,
|
1030
|
+
_regexp: 0x11
|
652
1031
|
};
|
653
1032
|
|
654
1033
|
const TYPE_NAMES = {
|
@@ -661,108 +1040,201 @@ const TYPE_NAMES = {
|
|
661
1040
|
[TYPES.symbol]: 'Symbol',
|
662
1041
|
[TYPES.bigint]: 'BigInt',
|
663
1042
|
|
664
|
-
[TYPES._array]: 'Array'
|
1043
|
+
[TYPES._array]: 'Array',
|
1044
|
+
[TYPES._regexp]: 'RegExp'
|
665
1045
|
};
|
666
1046
|
|
667
|
-
let typeStates = {};
|
668
|
-
|
669
1047
|
const getType = (scope, _name) => {
|
670
1048
|
const name = mapName(_name);
|
671
|
-
if (scope.locals[name]) return typeStates[name];
|
672
1049
|
|
673
|
-
if (
|
674
|
-
if (
|
675
|
-
if (globals[name]) return typeStates[name];
|
1050
|
+
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1051
|
+
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
676
1052
|
|
677
|
-
|
678
|
-
if (name
|
1053
|
+
let type = TYPES.undefined;
|
1054
|
+
if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
|
1055
|
+
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
679
1056
|
|
680
|
-
|
1057
|
+
if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
|
1058
|
+
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1059
|
+
|
1060
|
+
return number(type, Valtype.i32);
|
681
1061
|
};
|
682
1062
|
|
683
|
-
const
|
684
|
-
|
685
|
-
return TYPES[typeof node.value];
|
686
|
-
}
|
1063
|
+
const setType = (scope, _name, type) => {
|
1064
|
+
const name = mapName(_name);
|
687
1065
|
|
688
|
-
|
689
|
-
return TYPES.function;
|
690
|
-
}
|
1066
|
+
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
691
1067
|
|
692
|
-
if (
|
693
|
-
|
694
|
-
|
1068
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1069
|
+
if (scope.locals[name]) return [
|
1070
|
+
...out,
|
1071
|
+
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1072
|
+
];
|
695
1073
|
|
696
|
-
if (
|
697
|
-
|
698
|
-
|
699
|
-
|
1074
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1075
|
+
if (globals[name]) return [
|
1076
|
+
...out,
|
1077
|
+
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
1078
|
+
];
|
1079
|
+
|
1080
|
+
// throw new Error('could not find var');
|
1081
|
+
};
|
700
1082
|
|
701
|
-
|
702
|
-
|
1083
|
+
const getLastType = scope => {
|
1084
|
+
scope.gotLastType = true;
|
1085
|
+
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1086
|
+
};
|
703
1087
|
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
const spl = name.slice(2).split('_');
|
1088
|
+
const setLastType = scope => {
|
1089
|
+
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1090
|
+
};
|
708
1091
|
|
709
|
-
|
710
|
-
|
1092
|
+
const getNodeType = (scope, node) => {
|
1093
|
+
const inner = () => {
|
1094
|
+
if (node.type === 'Literal') {
|
1095
|
+
if (node.regex) return TYPES._regexp;
|
711
1096
|
|
712
|
-
|
713
|
-
protoFunc = prototypeFuncs[baseType]?.[func];
|
1097
|
+
return TYPES[typeof node.value];
|
714
1098
|
}
|
715
1099
|
|
716
|
-
|
717
|
-
|
718
|
-
|
1100
|
+
if (isFuncType(node.type)) {
|
1101
|
+
return TYPES.function;
|
1102
|
+
}
|
719
1103
|
|
720
|
-
|
721
|
-
|
1104
|
+
if (node.type === 'Identifier') {
|
1105
|
+
return getType(scope, node.name);
|
722
1106
|
}
|
723
1107
|
|
724
|
-
if (
|
1108
|
+
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
|
+
const name = node.callee.name;
|
1110
|
+
const func = funcs.find(x => x.name === name);
|
725
1111
|
|
726
|
-
|
727
|
-
|
1112
|
+
if (func) {
|
1113
|
+
// console.log(scope, func, func.returnType);
|
1114
|
+
if (func.returnType) return func.returnType;
|
1115
|
+
}
|
728
1116
|
|
729
|
-
|
730
|
-
|
731
|
-
}
|
1117
|
+
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1118
|
+
if (internalConstrs[name]) return internalConstrs[name].type;
|
732
1119
|
|
733
|
-
|
734
|
-
|
735
|
-
|
1120
|
+
// check if this is a prototype function
|
1121
|
+
// if so and there is only one impl (eg charCodeAt)
|
1122
|
+
// use that return type as that is the only possibility
|
1123
|
+
// (if non-matching type it would error out)
|
1124
|
+
if (name.startsWith('__')) {
|
1125
|
+
const spl = name.slice(2).split('_');
|
736
1126
|
|
737
|
-
|
738
|
-
|
739
|
-
|
1127
|
+
const func = spl[spl.length - 1];
|
1128
|
+
const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
|
1129
|
+
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
|
+
}
|
740
1131
|
|
741
|
-
|
742
|
-
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1132
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
743
1133
|
|
744
|
-
|
745
|
-
|
1134
|
+
// presume
|
1135
|
+
// todo: warn here?
|
1136
|
+
return TYPES.number;
|
746
1137
|
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
}
|
1138
|
+
// let protoFunc;
|
1139
|
+
// // ident.func()
|
1140
|
+
// if (name && name.startsWith('__')) {
|
1141
|
+
// const spl = name.slice(2).split('_');
|
752
1142
|
|
753
|
-
|
754
|
-
|
1143
|
+
// const baseName = spl.slice(0, -1).join('_');
|
1144
|
+
// const baseType = getType(scope, baseName);
|
755
1145
|
|
756
|
-
|
757
|
-
|
1146
|
+
// const func = spl[spl.length - 1];
|
1147
|
+
// protoFunc = prototypeFuncs[baseType]?.[func];
|
1148
|
+
// }
|
1149
|
+
|
1150
|
+
// // literal.func()
|
1151
|
+
// if (!name && node.callee.type === 'MemberExpression') {
|
1152
|
+
// if (node.callee.object.regex) {
|
1153
|
+
// const funcName = node.callee.property.name;
|
1154
|
+
// return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
|
1155
|
+
// }
|
1156
|
+
|
1157
|
+
// const baseType = getNodeType(scope, node.callee.object);
|
1158
|
+
|
1159
|
+
// const func = node.callee.property.name;
|
1160
|
+
// protoFunc = prototypeFuncs[baseType]?.[func];
|
1161
|
+
// }
|
1162
|
+
|
1163
|
+
// if (protoFunc) return protoFunc.returnType;
|
1164
|
+
}
|
1165
|
+
|
1166
|
+
if (node.type === 'ExpressionStatement') {
|
1167
|
+
return getNodeType(scope, node.expression);
|
1168
|
+
}
|
1169
|
+
|
1170
|
+
if (node.type === 'AssignmentExpression') {
|
1171
|
+
return getNodeType(scope, node.right);
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
if (node.type === 'ArrayExpression') {
|
1175
|
+
return TYPES._array;
|
1176
|
+
}
|
1177
|
+
|
1178
|
+
if (node.type === 'BinaryExpression') {
|
1179
|
+
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1180
|
+
return TYPES.number;
|
1181
|
+
|
1182
|
+
// todo: string concat types
|
1183
|
+
// if (node.operator !== '+') return TYPES.number;
|
1184
|
+
// else return [
|
1185
|
+
// // if left is string
|
1186
|
+
// ...getNodeType(scope, node.left),
|
1187
|
+
// ...number(TYPES.string, Valtype.i32),
|
1188
|
+
// [ Opcodes.i32_eq ],
|
1189
|
+
|
1190
|
+
// // if right is string
|
1191
|
+
// ...getNodeType(scope, node.right),
|
1192
|
+
// ...number(TYPES.string, Valtype.i32),
|
1193
|
+
// [ Opcodes.i32_eq ],
|
1194
|
+
|
1195
|
+
// // if either are true
|
1196
|
+
// [ Opcodes.i32_or ],
|
1197
|
+
// ];
|
1198
|
+
}
|
1199
|
+
|
1200
|
+
if (node.type === 'UnaryExpression') {
|
1201
|
+
if (node.operator === '!') return TYPES.boolean;
|
1202
|
+
if (node.operator === 'void') return TYPES.undefined;
|
1203
|
+
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
+
if (node.operator === 'typeof') return TYPES.string;
|
758
1205
|
|
759
|
-
|
760
|
-
|
1206
|
+
return TYPES.number;
|
1207
|
+
}
|
1208
|
+
|
1209
|
+
if (node.type === 'MemberExpression') {
|
1210
|
+
// hack: if something.length, number type
|
1211
|
+
if (node.property.name === 'length') return TYPES.number;
|
1212
|
+
|
1213
|
+
// we cannot guess
|
1214
|
+
return TYPES.number;
|
1215
|
+
}
|
1216
|
+
|
1217
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1218
|
+
|
1219
|
+
// presume
|
1220
|
+
// todo: warn here?
|
1221
|
+
return TYPES.number;
|
1222
|
+
};
|
1223
|
+
|
1224
|
+
const ret = inner();
|
1225
|
+
// console.trace(node, ret);
|
1226
|
+
if (typeof ret === 'number') return number(ret, Valtype.i32);
|
1227
|
+
return ret;
|
761
1228
|
};
|
762
1229
|
|
763
1230
|
const generateLiteral = (scope, decl, global, name) => {
|
764
1231
|
if (decl.value === null) return number(NULL);
|
765
1232
|
|
1233
|
+
if (decl.regex) {
|
1234
|
+
scope.regex[name] = decl.regex;
|
1235
|
+
return number(1);
|
1236
|
+
}
|
1237
|
+
|
766
1238
|
switch (typeof decl.value) {
|
767
1239
|
case 'number':
|
768
1240
|
return number(decl.value);
|
@@ -772,27 +1244,16 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
772
1244
|
return number(decl.value ? 1 : 0);
|
773
1245
|
|
774
1246
|
case 'string':
|
775
|
-
// this is a terrible hack which changes type strings ("number" etc) to known const number values
|
776
|
-
switch (decl.value) {
|
777
|
-
case 'number': return number(TYPES.number);
|
778
|
-
case 'boolean': return number(TYPES.boolean);
|
779
|
-
case 'string': return number(TYPES.string);
|
780
|
-
case 'undefined': return number(TYPES.undefined);
|
781
|
-
case 'object': return number(TYPES.object);
|
782
|
-
case 'function': return number(TYPES.function);
|
783
|
-
case 'symbol': return number(TYPES.symbol);
|
784
|
-
case 'bigint': return number(TYPES.bigint);
|
785
|
-
}
|
786
|
-
|
787
1247
|
const str = decl.value;
|
788
1248
|
const rawElements = new Array(str.length);
|
1249
|
+
let j = 0;
|
789
1250
|
for (let i = 0; i < str.length; i++) {
|
790
1251
|
rawElements[i] = str.charCodeAt(i);
|
791
1252
|
}
|
792
1253
|
|
793
1254
|
return makeArray(scope, {
|
794
1255
|
rawElements
|
795
|
-
}, global, name, false, 'i16');
|
1256
|
+
}, global, name, false, 'i16')[0];
|
796
1257
|
|
797
1258
|
default:
|
798
1259
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -802,7 +1263,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
802
1263
|
const countLeftover = wasm => {
|
803
1264
|
let count = 0, depth = 0;
|
804
1265
|
|
805
|
-
for (
|
1266
|
+
for (let i = 0; i < wasm.length; i++) {
|
1267
|
+
const inst = wasm[i];
|
806
1268
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
807
1269
|
if (inst[0] === Opcodes.if) count--;
|
808
1270
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -811,11 +1273,12 @@ const countLeftover = wasm => {
|
|
811
1273
|
if (inst[0] === Opcodes.end) depth--;
|
812
1274
|
|
813
1275
|
if (depth === 0)
|
814
|
-
if ([Opcodes.throw,
|
1276
|
+
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
815
1277
|
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
816
1278
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
817
1279
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
818
1280
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1281
|
+
else if (inst[0] === Opcodes.return) count = 0;
|
819
1282
|
else if (inst[0] === Opcodes.call) {
|
820
1283
|
let func = funcs.find(x => x.index === inst[1]);
|
821
1284
|
if (func) {
|
@@ -823,6 +1286,8 @@ const countLeftover = wasm => {
|
|
823
1286
|
} else count--;
|
824
1287
|
if (func) count += func.returns.length;
|
825
1288
|
} else count--;
|
1289
|
+
|
1290
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
826
1291
|
}
|
827
1292
|
|
828
1293
|
return count;
|
@@ -843,7 +1308,7 @@ const generateExp = (scope, decl) => {
|
|
843
1308
|
return out;
|
844
1309
|
};
|
845
1310
|
|
846
|
-
const
|
1311
|
+
const CTArrayUtil = {
|
847
1312
|
getLengthI32: pointer => [
|
848
1313
|
...number(0, Valtype.i32),
|
849
1314
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
@@ -869,6 +1334,32 @@ const arrayUtil = {
|
|
869
1334
|
]
|
870
1335
|
};
|
871
1336
|
|
1337
|
+
const RTArrayUtil = {
|
1338
|
+
getLengthI32: pointer => [
|
1339
|
+
...pointer,
|
1340
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
1341
|
+
],
|
1342
|
+
|
1343
|
+
getLength: pointer => [
|
1344
|
+
...pointer,
|
1345
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
1346
|
+
Opcodes.i32_from_u
|
1347
|
+
],
|
1348
|
+
|
1349
|
+
setLengthI32: (pointer, value) => [
|
1350
|
+
...pointer,
|
1351
|
+
...value,
|
1352
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
1353
|
+
],
|
1354
|
+
|
1355
|
+
setLength: (pointer, value) => [
|
1356
|
+
...pointer,
|
1357
|
+
...value,
|
1358
|
+
Opcodes.i32_to_u,
|
1359
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
1360
|
+
]
|
1361
|
+
};
|
1362
|
+
|
872
1363
|
const generateCall = (scope, decl, _global, _name) => {
|
873
1364
|
/* const callee = decl.callee;
|
874
1365
|
const args = decl.arguments;
|
@@ -898,95 +1389,164 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
898
1389
|
const lastInst = out[out.length - 1];
|
899
1390
|
if (lastInst && lastInst[0] === Opcodes.drop) {
|
900
1391
|
out.splice(out.length - 1, 1);
|
1392
|
+
|
1393
|
+
const finalStatement = parsed.body[parsed.body.length - 1];
|
1394
|
+
out.push(
|
1395
|
+
...getNodeType(scope, finalStatement),
|
1396
|
+
setLastType(scope)
|
1397
|
+
);
|
901
1398
|
} else if (countLeftover(out) === 0) {
|
902
1399
|
out.push(...number(UNDEFINED));
|
1400
|
+
out.push(
|
1401
|
+
...number(TYPES.undefined, Valtype.i32),
|
1402
|
+
setLastType(scope)
|
1403
|
+
);
|
903
1404
|
}
|
904
1405
|
|
1406
|
+
// if (lastInst && lastInst[0] === Opcodes.drop) {
|
1407
|
+
// out.splice(out.length - 1, 1);
|
1408
|
+
// } else if (countLeftover(out) === 0) {
|
1409
|
+
// out.push(...number(UNDEFINED));
|
1410
|
+
// }
|
1411
|
+
|
905
1412
|
return out;
|
906
1413
|
}
|
907
1414
|
|
908
|
-
let
|
909
|
-
let protoFunc, protoName, baseType, baseName = '$undeclared';
|
1415
|
+
let protoName, target;
|
910
1416
|
// ident.func()
|
911
1417
|
if (name && name.startsWith('__')) {
|
912
1418
|
const spl = name.slice(2).split('_');
|
913
1419
|
|
914
|
-
|
915
|
-
baseType = getType(scope, baseName);
|
1420
|
+
protoName = spl[spl.length - 1];
|
916
1421
|
|
917
|
-
|
918
|
-
|
919
|
-
protoName = func;
|
1422
|
+
target = { ...decl.callee };
|
1423
|
+
target.name = spl.slice(0, -1).join('_');
|
920
1424
|
}
|
921
1425
|
|
922
1426
|
// literal.func()
|
923
1427
|
if (!name && decl.callee.type === 'MemberExpression') {
|
924
|
-
|
1428
|
+
// megahack for /regex/.func()
|
1429
|
+
if (decl.callee.object.regex) {
|
1430
|
+
const funcName = decl.callee.property.name;
|
1431
|
+
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1432
|
+
|
1433
|
+
funcIndex[func.name] = func.index;
|
1434
|
+
funcs.push(func);
|
1435
|
+
|
1436
|
+
return [
|
1437
|
+
// make string arg
|
1438
|
+
...generate(scope, decl.arguments[0]),
|
925
1439
|
|
926
|
-
|
927
|
-
|
928
|
-
|
1440
|
+
// call regex func
|
1441
|
+
Opcodes.i32_to_u,
|
1442
|
+
[ Opcodes.call, func.index ],
|
1443
|
+
Opcodes.i32_from_u,
|
1444
|
+
|
1445
|
+
...number(TYPES.boolean, Valtype.i32),
|
1446
|
+
setLastType(scope)
|
1447
|
+
];
|
1448
|
+
}
|
929
1449
|
|
930
|
-
|
931
|
-
|
1450
|
+
protoName = decl.callee.property.name;
|
1451
|
+
|
1452
|
+
target = decl.callee.object;
|
932
1453
|
}
|
933
1454
|
|
934
|
-
if (
|
935
|
-
|
1455
|
+
// if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
|
1456
|
+
// const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
|
936
1457
|
|
937
|
-
|
1458
|
+
// funcIndex[func.name] = func.index;
|
1459
|
+
// funcs.push(func);
|
938
1460
|
|
939
|
-
|
940
|
-
|
941
|
-
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
1461
|
+
// return [
|
1462
|
+
// generate(scope, decl.callee.object)
|
942
1463
|
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
1464
|
+
// // call regex func
|
1465
|
+
// [ Opcodes.call, func.index ],
|
1466
|
+
// Opcodes.i32_from_u
|
1467
|
+
// ];
|
1468
|
+
// }
|
948
1469
|
|
949
|
-
|
1470
|
+
if (protoName) {
|
1471
|
+
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1472
|
+
const f = prototypeFuncs[x][protoName];
|
1473
|
+
if (f) acc[x] = f;
|
1474
|
+
return acc;
|
1475
|
+
}, {});
|
950
1476
|
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
1477
|
+
// no prototype function candidates, ignore
|
1478
|
+
if (Object.keys(protoCands).length > 0) {
|
1479
|
+
// use local for cached i32 length as commonly used
|
1480
|
+
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1481
|
+
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1482
|
+
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
957
1483
|
|
958
|
-
|
959
|
-
];
|
960
|
-
}
|
1484
|
+
// TODO: long-term, prototypes should be their individual separate funcs
|
961
1485
|
|
962
|
-
|
1486
|
+
let lengthI32CacheUsed = false;
|
1487
|
+
const protoBC = {};
|
1488
|
+
for (const x in protoCands) {
|
1489
|
+
const protoFunc = protoCands[x];
|
1490
|
+
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
1491
|
+
protoBC[x] = [
|
1492
|
+
...RTArrayUtil.getLength(getPointer),
|
963
1493
|
|
964
|
-
|
1494
|
+
...number(TYPES.number, Valtype.i32),
|
1495
|
+
setLastType(scope)
|
1496
|
+
];
|
1497
|
+
continue;
|
1498
|
+
}
|
965
1499
|
|
966
|
-
|
967
|
-
|
1500
|
+
// const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1501
|
+
// const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1502
|
+
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1503
|
+
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1504
|
+
|
1505
|
+
const protoOut = protoFunc(getPointer, {
|
1506
|
+
getCachedI32: () => {
|
1507
|
+
lengthI32CacheUsed = true;
|
1508
|
+
return [ [ Opcodes.local_get, lengthLocal ] ];
|
1509
|
+
},
|
1510
|
+
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1511
|
+
get: () => RTArrayUtil.getLength(getPointer),
|
1512
|
+
getI32: () => RTArrayUtil.getLengthI32(getPointer),
|
1513
|
+
set: value => RTArrayUtil.setLength(getPointer, value),
|
1514
|
+
setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
|
1515
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1516
|
+
return makeArray(scope, {
|
1517
|
+
rawElements: new Array(length)
|
1518
|
+
}, _global, _name, true, itemType);
|
1519
|
+
});
|
1520
|
+
|
1521
|
+
protoBC[x] = [
|
1522
|
+
[ Opcodes.block, valtypeBinary ],
|
1523
|
+
...protoOut,
|
1524
|
+
|
1525
|
+
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1526
|
+
setLastType(scope),
|
1527
|
+
[ Opcodes.end ]
|
1528
|
+
];
|
1529
|
+
}
|
968
1530
|
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
},
|
986
|
-
|
987
|
-
|
988
|
-
[ Opcodes.end ]
|
989
|
-
];
|
1531
|
+
return [
|
1532
|
+
...generate(scope, target),
|
1533
|
+
|
1534
|
+
Opcodes.i32_to_u,
|
1535
|
+
[ Opcodes.local_set, pointerLocal ],
|
1536
|
+
|
1537
|
+
...(!lengthI32CacheUsed ? [] : [
|
1538
|
+
...RTArrayUtil.getLengthI32(getPointer),
|
1539
|
+
[ Opcodes.local_set, lengthLocal ],
|
1540
|
+
]),
|
1541
|
+
|
1542
|
+
...typeSwitch(scope, getNodeType(scope, target), {
|
1543
|
+
...protoBC,
|
1544
|
+
|
1545
|
+
// TODO: error better
|
1546
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1547
|
+
}, valtypeBinary),
|
1548
|
+
];
|
1549
|
+
}
|
990
1550
|
}
|
991
1551
|
|
992
1552
|
// TODO: only allows callee as literal
|
@@ -1030,34 +1590,50 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1030
1590
|
|
1031
1591
|
const func = funcs.find(x => x.index === idx);
|
1032
1592
|
|
1593
|
+
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1594
|
+
const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
|
1595
|
+
|
1033
1596
|
let args = decl.arguments;
|
1034
|
-
if (func && args.length <
|
1597
|
+
if (func && args.length < paramCount) {
|
1035
1598
|
// too little args, push undefineds
|
1036
|
-
args = args.concat(new Array(
|
1599
|
+
args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
|
1037
1600
|
}
|
1038
1601
|
|
1039
|
-
if (func && args.length >
|
1602
|
+
if (func && args.length > paramCount) {
|
1040
1603
|
// too many args, slice extras off
|
1041
|
-
args = args.slice(0,
|
1604
|
+
args = args.slice(0, paramCount);
|
1042
1605
|
}
|
1043
1606
|
|
1044
|
-
if (func && func.memory) scope.memory = true;
|
1045
1607
|
if (func && func.throws) scope.throws = true;
|
1046
1608
|
|
1609
|
+
let out = [];
|
1047
1610
|
for (const arg of args) {
|
1048
|
-
out.
|
1611
|
+
out = out.concat(generate(scope, arg));
|
1612
|
+
if (userFunc) out = out.concat(getNodeType(scope, arg));
|
1049
1613
|
}
|
1050
1614
|
|
1051
1615
|
out.push([ Opcodes.call, idx ]);
|
1052
1616
|
|
1617
|
+
if (!userFunc) {
|
1618
|
+
// let type;
|
1619
|
+
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1620
|
+
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
1621
|
+
// if (importedFuncs[name] && importedFuncs[]) type =
|
1622
|
+
|
1623
|
+
// if (type) out.push(
|
1624
|
+
// ...number(type, Valtype.i32),
|
1625
|
+
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1626
|
+
// );
|
1627
|
+
} else out.push(setLastType(scope));
|
1628
|
+
|
1053
1629
|
return out;
|
1054
1630
|
};
|
1055
1631
|
|
1056
1632
|
const generateNew = (scope, decl, _global, _name) => {
|
1057
1633
|
// hack: basically treat this as a normal call for builtins for now
|
1058
1634
|
const name = mapName(decl.callee.name);
|
1059
|
-
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1060
|
-
if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1635
|
+
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1636
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1061
1637
|
|
1062
1638
|
return generateCall(scope, decl, _global, _name);
|
1063
1639
|
};
|
@@ -1073,14 +1649,130 @@ const unhackName = name => {
|
|
1073
1649
|
return name;
|
1074
1650
|
};
|
1075
1651
|
|
1652
|
+
const knownType = (scope, type) => {
|
1653
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
1654
|
+
return type[0][1];
|
1655
|
+
}
|
1656
|
+
|
1657
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
1658
|
+
const idx = type[0][1];
|
1659
|
+
|
1660
|
+
// type idx = var idx + 1
|
1661
|
+
const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
|
1662
|
+
if (v.metadata?.type != null) return v.metadata.type;
|
1663
|
+
}
|
1664
|
+
|
1665
|
+
return null;
|
1666
|
+
};
|
1667
|
+
|
1668
|
+
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1669
|
+
const known = knownType(scope, type);
|
1670
|
+
if (known != null) {
|
1671
|
+
return bc[known] ?? bc.default;
|
1672
|
+
}
|
1673
|
+
|
1674
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1675
|
+
|
1676
|
+
const out = [
|
1677
|
+
...type,
|
1678
|
+
[ Opcodes.local_set, tmp ],
|
1679
|
+
[ Opcodes.block, returns ]
|
1680
|
+
];
|
1681
|
+
|
1682
|
+
// todo: use br_table?
|
1683
|
+
|
1684
|
+
for (const x in bc) {
|
1685
|
+
if (x === 'default') continue;
|
1686
|
+
|
1687
|
+
// if type == x
|
1688
|
+
out.push([ Opcodes.local_get, tmp ]);
|
1689
|
+
out.push(...number(x, Valtype.i32));
|
1690
|
+
out.push([ Opcodes.i32_eq ]);
|
1691
|
+
|
1692
|
+
out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
|
1693
|
+
out.push(...bc[x]);
|
1694
|
+
out.push([ Opcodes.br, 1 ]);
|
1695
|
+
out.push([ Opcodes.end ]);
|
1696
|
+
}
|
1697
|
+
|
1698
|
+
// default
|
1699
|
+
if (bc.default) out.push(...bc.default);
|
1700
|
+
else if (returns !== Blocktype.void) out.push(...number(0, returns));
|
1701
|
+
|
1702
|
+
out.push([ Opcodes.end, 'TYPESWITCH_end' ]);
|
1703
|
+
|
1704
|
+
return out;
|
1705
|
+
};
|
1706
|
+
|
1707
|
+
const allocVar = (scope, name, global = false) => {
|
1708
|
+
const target = global ? globals : scope.locals;
|
1709
|
+
|
1710
|
+
// already declared
|
1711
|
+
if (target[name]) {
|
1712
|
+
// parser should catch this but sanity check anyway
|
1713
|
+
// if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1714
|
+
|
1715
|
+
return target[name].idx;
|
1716
|
+
}
|
1717
|
+
|
1718
|
+
let idx = global ? globalInd++ : scope.localInd++;
|
1719
|
+
target[name] = { idx, type: valtypeBinary };
|
1720
|
+
|
1721
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
1722
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
1723
|
+
|
1724
|
+
return idx;
|
1725
|
+
};
|
1726
|
+
|
1727
|
+
const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
1728
|
+
const target = global ? globals : scope.locals;
|
1729
|
+
|
1730
|
+
target[name].metadata ??= {};
|
1731
|
+
for (const x in metadata) {
|
1732
|
+
if (metadata[x] != null) target[name].metadata[x] = metadata[x];
|
1733
|
+
}
|
1734
|
+
};
|
1735
|
+
|
1736
|
+
const typeAnnoToPorfType = x => {
|
1737
|
+
if (TYPES[x]) return TYPES[x];
|
1738
|
+
if (TYPES['_' + x]) return TYPES['_' + x];
|
1739
|
+
|
1740
|
+
switch (x) {
|
1741
|
+
case 'i32':
|
1742
|
+
return TYPES.number;
|
1743
|
+
}
|
1744
|
+
|
1745
|
+
return null;
|
1746
|
+
};
|
1747
|
+
|
1748
|
+
const extractTypeAnnotation = decl => {
|
1749
|
+
let a = decl;
|
1750
|
+
while (a.typeAnnotation) a = a.typeAnnotation;
|
1751
|
+
|
1752
|
+
let type, elementType;
|
1753
|
+
if (a.typeName) {
|
1754
|
+
type = a.typeName.name;
|
1755
|
+
} else if (a.type.endsWith('Keyword')) {
|
1756
|
+
type = a.type.slice(2, -7).toLowerCase();
|
1757
|
+
} else if (a.type === 'TSArrayType') {
|
1758
|
+
type = 'array';
|
1759
|
+
elementType = extractTypeAnnotation(a.elementType).type;
|
1760
|
+
}
|
1761
|
+
|
1762
|
+
type = typeAnnoToPorfType(type);
|
1763
|
+
|
1764
|
+
// if (decl.name) console.log(decl.name, { type, elementType });
|
1765
|
+
|
1766
|
+
return { type, elementType };
|
1767
|
+
};
|
1768
|
+
|
1076
1769
|
const generateVar = (scope, decl) => {
|
1077
|
-
|
1770
|
+
let out = [];
|
1078
1771
|
|
1079
1772
|
const topLevel = scope.name === 'main';
|
1080
1773
|
|
1081
1774
|
// global variable if in top scope (main) and var ..., or if wanted
|
1082
|
-
const global = decl.kind === 'var';
|
1083
|
-
const target = global ? globals : scope.locals;
|
1775
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1084
1776
|
|
1085
1777
|
for (const x of decl.declarations) {
|
1086
1778
|
const name = mapName(x.id.name);
|
@@ -1100,45 +1792,19 @@ const generateVar = (scope, decl) => {
|
|
1100
1792
|
continue; // always ignore
|
1101
1793
|
}
|
1102
1794
|
|
1103
|
-
let idx;
|
1104
|
-
|
1105
|
-
|
1106
|
-
// parser should catch this but sanity check anyway
|
1107
|
-
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1795
|
+
let idx = allocVar(scope, name, global);
|
1796
|
+
if (x.init) {
|
1797
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1108
1798
|
|
1109
|
-
|
1110
|
-
|
1111
|
-
idx = global ? globalInd++ : scope.localInd++;
|
1112
|
-
target[name] = { idx, type: valtypeBinary };
|
1799
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1800
|
+
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1113
1801
|
}
|
1114
1802
|
|
1115
|
-
|
1116
|
-
|
1117
|
-
// x.init ??= DEFAULT_VALUE;
|
1118
|
-
if (x.init) {
|
1119
|
-
out.push(...generate(scope, x.init, global, name));
|
1120
|
-
|
1121
|
-
// if our value is the result of a function, infer the type from that func's return value
|
1122
|
-
if (out[out.length - 1][0] === Opcodes.call) {
|
1123
|
-
const ind = out[out.length - 1][1];
|
1124
|
-
if (ind >= importedFuncs.length) { // not an imported func
|
1125
|
-
const func = funcs.find(x => x.index === ind);
|
1126
|
-
if (!func) throw new Error('could not find func being called as var value to infer type'); // sanity check
|
1127
|
-
|
1128
|
-
const returns = func.returns;
|
1129
|
-
if (returns.length > 1) throw new Error('func returning >1 value being set as 1 local'); // sanity check
|
1130
|
-
|
1131
|
-
target[name].type = func.returns[0];
|
1132
|
-
if (target[name].type === Valtype.v128) {
|
1133
|
-
// specify vec subtype inferred from first vec type in function name
|
1134
|
-
target[name].vecType = func.name.split('_').find(x => x.includes('x'));
|
1135
|
-
}
|
1136
|
-
} else {
|
1137
|
-
// we do not have imports that return yet, ignore for now
|
1138
|
-
}
|
1139
|
-
}
|
1803
|
+
// hack: this follows spec properly but is mostly unneeded 😅
|
1804
|
+
// out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
|
1140
1805
|
|
1141
|
-
|
1806
|
+
if (typedInput && x.id.typeAnnotation) {
|
1807
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1142
1808
|
}
|
1143
1809
|
}
|
1144
1810
|
|
@@ -1165,8 +1831,6 @@ const generateAssign = (scope, decl) => {
|
|
1165
1831
|
const name = decl.left.object.name;
|
1166
1832
|
const pointer = arrays.get(name);
|
1167
1833
|
|
1168
|
-
scope.memory = true;
|
1169
|
-
|
1170
1834
|
const aotPointer = pointer != null;
|
1171
1835
|
|
1172
1836
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1177,7 +1841,7 @@ const generateAssign = (scope, decl) => {
|
|
1177
1841
|
Opcodes.i32_to_u
|
1178
1842
|
]),
|
1179
1843
|
|
1180
|
-
...generate(scope, decl.right
|
1844
|
+
...generate(scope, decl.right),
|
1181
1845
|
[ Opcodes.local_tee, newValueTmp ],
|
1182
1846
|
|
1183
1847
|
Opcodes.i32_to_u,
|
@@ -1187,13 +1851,76 @@ const generateAssign = (scope, decl) => {
|
|
1187
1851
|
];
|
1188
1852
|
}
|
1189
1853
|
|
1854
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1855
|
+
|
1856
|
+
// arr[i]
|
1857
|
+
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1858
|
+
const name = decl.left.object.name;
|
1859
|
+
const pointer = arrays.get(name);
|
1860
|
+
|
1861
|
+
const aotPointer = pointer != null;
|
1862
|
+
|
1863
|
+
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1864
|
+
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1865
|
+
|
1866
|
+
return [
|
1867
|
+
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
1868
|
+
[TYPES._array]: [
|
1869
|
+
...(aotPointer ? [] : [
|
1870
|
+
...generate(scope, decl.left.object),
|
1871
|
+
Opcodes.i32_to_u
|
1872
|
+
]),
|
1873
|
+
|
1874
|
+
// get index as valtype
|
1875
|
+
...generate(scope, decl.left.property),
|
1876
|
+
Opcodes.i32_to_u,
|
1877
|
+
|
1878
|
+
// turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
1879
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
1880
|
+
[ Opcodes.i32_mul ],
|
1881
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1882
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1883
|
+
|
1884
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
1885
|
+
[ Opcodes.local_get, pointerTmp ],
|
1886
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1887
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
|
1888
|
+
[ Opcodes.local_tee, newValueTmp ],
|
1889
|
+
|
1890
|
+
[ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1891
|
+
],
|
1892
|
+
|
1893
|
+
default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
|
1894
|
+
|
1895
|
+
// [TYPES.string]: [
|
1896
|
+
// // turn into byte offset by * sizeof i16
|
1897
|
+
// ...number(ValtypeSize.i16, Valtype.i32),
|
1898
|
+
// [ Opcodes.i32_mul ],
|
1899
|
+
// ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1900
|
+
// ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1901
|
+
|
1902
|
+
// ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
1903
|
+
// [ Opcodes.local_get, pointerTmp ],
|
1904
|
+
// [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1905
|
+
// ], generate(scope, decl.right), number(TYPES.string, Valtype.i32), getNodeType(scope, decl.right))),
|
1906
|
+
// [ Opcodes.local_tee, newValueTmp ],
|
1907
|
+
|
1908
|
+
// Opcodes.i32_to_u,
|
1909
|
+
// [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1910
|
+
// ]
|
1911
|
+
}, Blocktype.void),
|
1912
|
+
|
1913
|
+
[ Opcodes.local_get, newValueTmp ]
|
1914
|
+
];
|
1915
|
+
}
|
1916
|
+
|
1190
1917
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1191
1918
|
|
1192
1919
|
if (local === undefined) {
|
1193
|
-
// todo: this should be a
|
1920
|
+
// todo: this should be a sloppy mode only thing
|
1194
1921
|
|
1195
1922
|
// only allow = for this
|
1196
|
-
if (
|
1923
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1197
1924
|
|
1198
1925
|
if (builtinVars[name]) {
|
1199
1926
|
// just return rhs (eg `NaN = 2`)
|
@@ -1202,25 +1929,53 @@ const generateAssign = (scope, decl) => {
|
|
1202
1929
|
|
1203
1930
|
// set global and return (eg a = 2)
|
1204
1931
|
return [
|
1205
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1932
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1206
1933
|
[ Opcodes.global_get, globals[name].idx ]
|
1207
1934
|
];
|
1208
1935
|
}
|
1209
1936
|
|
1210
|
-
if (
|
1211
|
-
typeStates[name] = getNodeType(scope, decl.right);
|
1212
|
-
|
1937
|
+
if (op === '=') {
|
1213
1938
|
return [
|
1214
1939
|
...generate(scope, decl.right, isGlobal, name),
|
1215
1940
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1216
|
-
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1941
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1942
|
+
|
1943
|
+
...setType(scope, name, getNodeType(scope, decl.right))
|
1944
|
+
];
|
1945
|
+
}
|
1946
|
+
|
1947
|
+
if (op === '||' || op === '&&' || op === '??') {
|
1948
|
+
// todo: is this needed?
|
1949
|
+
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
1950
|
+
// instead, left @ (left = right)
|
1951
|
+
// eg, x &&= y ~= x && (x = y)
|
1952
|
+
|
1953
|
+
return [
|
1954
|
+
...performOp(scope, op, [
|
1955
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1956
|
+
], [
|
1957
|
+
...generate(scope, decl.right),
|
1958
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1959
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1960
|
+
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1961
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1962
|
+
|
1963
|
+
getLastType(scope),
|
1964
|
+
// hack: type is idx+1
|
1965
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1217
1966
|
];
|
1218
1967
|
}
|
1219
1968
|
|
1220
1969
|
return [
|
1221
|
-
...performOp(scope,
|
1970
|
+
...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),
|
1222
1971
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1223
|
-
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1972
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1973
|
+
|
1974
|
+
// todo: string concat types
|
1975
|
+
|
1976
|
+
// hack: type is idx+1
|
1977
|
+
...number(TYPES.number, Valtype.i32),
|
1978
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1224
1979
|
];
|
1225
1980
|
};
|
1226
1981
|
|
@@ -1245,13 +2000,14 @@ const generateUnary = (scope, decl) => {
|
|
1245
2000
|
|
1246
2001
|
case '!':
|
1247
2002
|
// !=
|
1248
|
-
return falsy(scope, generate(scope, decl.argument));
|
2003
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
|
1249
2004
|
|
1250
2005
|
case '~':
|
2006
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1251
2007
|
return [
|
1252
2008
|
...generate(scope, decl.argument),
|
1253
2009
|
Opcodes.i32_to,
|
1254
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
2010
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1255
2011
|
[ Opcodes.i32_xor ],
|
1256
2012
|
Opcodes.i32_from
|
1257
2013
|
];
|
@@ -1289,11 +2045,16 @@ const generateUnary = (scope, decl) => {
|
|
1289
2045
|
return out;
|
1290
2046
|
|
1291
2047
|
case 'typeof':
|
1292
|
-
|
2048
|
+
return typeSwitch(scope, getNodeType(scope, decl.argument), {
|
2049
|
+
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2050
|
+
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2051
|
+
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2052
|
+
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2053
|
+
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1293
2054
|
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
2055
|
+
// object and internal types
|
2056
|
+
default: makeString(scope, 'object', false, '#typeof_result'),
|
2057
|
+
});
|
1297
2058
|
|
1298
2059
|
default:
|
1299
2060
|
return todo(`unary operator ${decl.operator} not implemented yet`);
|
@@ -1332,9 +2093,9 @@ const generateUpdate = (scope, decl) => {
|
|
1332
2093
|
};
|
1333
2094
|
|
1334
2095
|
const generateIf = (scope, decl) => {
|
1335
|
-
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
2096
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
|
1336
2097
|
|
1337
|
-
out.push(
|
2098
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
1338
2099
|
depth.push('if');
|
1339
2100
|
|
1340
2101
|
const consOut = generate(scope, decl.consequent);
|
@@ -1356,16 +2117,28 @@ const generateIf = (scope, decl) => {
|
|
1356
2117
|
};
|
1357
2118
|
|
1358
2119
|
const generateConditional = (scope, decl) => {
|
1359
|
-
const out =
|
2120
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
|
1360
2121
|
|
1361
|
-
out.push(
|
2122
|
+
out.push([ Opcodes.if, valtypeBinary ]);
|
1362
2123
|
depth.push('if');
|
1363
2124
|
|
1364
2125
|
out.push(...generate(scope, decl.consequent));
|
1365
2126
|
|
2127
|
+
// note type
|
2128
|
+
out.push(
|
2129
|
+
...getNodeType(scope, decl.consequent),
|
2130
|
+
setLastType(scope)
|
2131
|
+
);
|
2132
|
+
|
1366
2133
|
out.push([ Opcodes.else ]);
|
1367
2134
|
out.push(...generate(scope, decl.alternate));
|
1368
2135
|
|
2136
|
+
// note type
|
2137
|
+
out.push(
|
2138
|
+
...getNodeType(scope, decl.alternate),
|
2139
|
+
setLastType(scope)
|
2140
|
+
);
|
2141
|
+
|
1369
2142
|
out.push([ Opcodes.end ]);
|
1370
2143
|
depth.pop();
|
1371
2144
|
|
@@ -1422,9 +2195,148 @@ const generateWhile = (scope, decl) => {
|
|
1422
2195
|
return out;
|
1423
2196
|
};
|
1424
2197
|
|
2198
|
+
const generateForOf = (scope, decl) => {
|
2199
|
+
const out = [];
|
2200
|
+
|
2201
|
+
// todo: for of inside for of might fuck up?
|
2202
|
+
const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
|
2203
|
+
const length = localTmp(scope, 'forof_length', Valtype.i32);
|
2204
|
+
const counter = localTmp(scope, 'forof_counter', Valtype.i32);
|
2205
|
+
|
2206
|
+
out.push(
|
2207
|
+
// set pointer as right
|
2208
|
+
...generate(scope, decl.right),
|
2209
|
+
Opcodes.i32_to_u,
|
2210
|
+
[ Opcodes.local_set, pointer ],
|
2211
|
+
|
2212
|
+
// set counter as 0 (could be already used)
|
2213
|
+
...number(0, Valtype.i32),
|
2214
|
+
[ Opcodes.local_set, counter ],
|
2215
|
+
|
2216
|
+
// get length
|
2217
|
+
[ Opcodes.local_get, pointer ],
|
2218
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2219
|
+
[ Opcodes.local_set, length ]
|
2220
|
+
);
|
2221
|
+
|
2222
|
+
depth.push('forof');
|
2223
|
+
|
2224
|
+
// setup local for left
|
2225
|
+
generate(scope, decl.left);
|
2226
|
+
|
2227
|
+
const leftName = decl.left.declarations[0].id.name;
|
2228
|
+
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2229
|
+
|
2230
|
+
depth.push('block');
|
2231
|
+
depth.push('block');
|
2232
|
+
|
2233
|
+
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2234
|
+
// hack: this is naughty and will break things!
|
2235
|
+
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2236
|
+
if (pages.hasString) {
|
2237
|
+
0, [ newOut, newPointer ] = makeArray(scope, {
|
2238
|
+
rawElements: new Array(1)
|
2239
|
+
}, isGlobal, leftName, true, 'i16');
|
2240
|
+
}
|
2241
|
+
|
2242
|
+
// set type for local
|
2243
|
+
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2244
|
+
[TYPES._array]: [
|
2245
|
+
...setType(scope, leftName, TYPES.number),
|
2246
|
+
|
2247
|
+
[ Opcodes.loop, Blocktype.void ],
|
2248
|
+
|
2249
|
+
[ Opcodes.local_get, pointer ],
|
2250
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
2251
|
+
|
2252
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2253
|
+
|
2254
|
+
[ Opcodes.block, Blocktype.void ],
|
2255
|
+
[ Opcodes.block, Blocktype.void ],
|
2256
|
+
...generate(scope, decl.body),
|
2257
|
+
[ Opcodes.end ],
|
2258
|
+
|
2259
|
+
// increment iter pointer by valtype size
|
2260
|
+
[ Opcodes.local_get, pointer ],
|
2261
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
2262
|
+
[ Opcodes.i32_add ],
|
2263
|
+
[ Opcodes.local_set, pointer ],
|
2264
|
+
|
2265
|
+
// increment counter by 1
|
2266
|
+
[ Opcodes.local_get, counter ],
|
2267
|
+
...number(1, Valtype.i32),
|
2268
|
+
[ Opcodes.i32_add ],
|
2269
|
+
[ Opcodes.local_tee, counter ],
|
2270
|
+
|
2271
|
+
// loop if counter != length
|
2272
|
+
[ Opcodes.local_get, length ],
|
2273
|
+
[ Opcodes.i32_ne ],
|
2274
|
+
[ Opcodes.br_if, 1 ],
|
2275
|
+
|
2276
|
+
[ Opcodes.end ],
|
2277
|
+
[ Opcodes.end ]
|
2278
|
+
],
|
2279
|
+
[TYPES.string]: [
|
2280
|
+
...setType(scope, leftName, TYPES.string),
|
2281
|
+
|
2282
|
+
[ Opcodes.loop, Blocktype.void ],
|
2283
|
+
|
2284
|
+
// setup new/out array
|
2285
|
+
...newOut,
|
2286
|
+
[ Opcodes.drop ],
|
2287
|
+
|
2288
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2289
|
+
|
2290
|
+
// load current string ind {arg}
|
2291
|
+
[ Opcodes.local_get, pointer ],
|
2292
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
2293
|
+
|
2294
|
+
// store to new string ind 0
|
2295
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2296
|
+
|
2297
|
+
// return new string (page)
|
2298
|
+
...number(newPointer),
|
2299
|
+
|
2300
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2301
|
+
|
2302
|
+
[ Opcodes.block, Blocktype.void ],
|
2303
|
+
[ Opcodes.block, Blocktype.void ],
|
2304
|
+
...generate(scope, decl.body),
|
2305
|
+
[ Opcodes.end ],
|
2306
|
+
|
2307
|
+
// increment iter pointer by valtype size
|
2308
|
+
[ Opcodes.local_get, pointer ],
|
2309
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
2310
|
+
[ Opcodes.i32_add ],
|
2311
|
+
[ Opcodes.local_set, pointer ],
|
2312
|
+
|
2313
|
+
// increment counter by 1
|
2314
|
+
[ Opcodes.local_get, counter ],
|
2315
|
+
...number(1, Valtype.i32),
|
2316
|
+
[ Opcodes.i32_add ],
|
2317
|
+
[ Opcodes.local_tee, counter ],
|
2318
|
+
|
2319
|
+
// loop if counter != length
|
2320
|
+
[ Opcodes.local_get, length ],
|
2321
|
+
[ Opcodes.i32_ne ],
|
2322
|
+
[ Opcodes.br_if, 1 ],
|
2323
|
+
|
2324
|
+
[ Opcodes.end ],
|
2325
|
+
[ Opcodes.end ]
|
2326
|
+
],
|
2327
|
+
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2328
|
+
}, Blocktype.void));
|
2329
|
+
|
2330
|
+
depth.pop();
|
2331
|
+
depth.pop();
|
2332
|
+
depth.pop();
|
2333
|
+
|
2334
|
+
return out;
|
2335
|
+
};
|
2336
|
+
|
1425
2337
|
const getNearestLoop = () => {
|
1426
2338
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1427
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
2339
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1428
2340
|
}
|
1429
2341
|
|
1430
2342
|
return -1;
|
@@ -1508,13 +2420,25 @@ const generateAssignPat = (scope, decl) => {
|
|
1508
2420
|
};
|
1509
2421
|
|
1510
2422
|
let pages = new Map();
|
1511
|
-
const allocPage = reason => {
|
1512
|
-
if (pages.has(reason)) return pages.get(reason);
|
2423
|
+
const allocPage = (reason, type) => {
|
2424
|
+
if (pages.has(reason)) return pages.get(reason).ind;
|
2425
|
+
|
2426
|
+
if (reason.startsWith('array:')) pages.hasArray = true;
|
2427
|
+
if (reason.startsWith('string:')) pages.hasString = true;
|
2428
|
+
|
2429
|
+
const ind = pages.size;
|
2430
|
+
pages.set(reason, { ind, type });
|
2431
|
+
|
2432
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2433
|
+
|
2434
|
+
return ind;
|
2435
|
+
};
|
1513
2436
|
|
1514
|
-
|
1515
|
-
pages.
|
2437
|
+
const freePage = reason => {
|
2438
|
+
const { ind } = pages.get(reason);
|
2439
|
+
pages.delete(reason);
|
1516
2440
|
|
1517
|
-
if (
|
2441
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1518
2442
|
|
1519
2443
|
return ind;
|
1520
2444
|
};
|
@@ -1528,7 +2452,7 @@ const itemTypeToValtype = {
|
|
1528
2452
|
i16: 'i32'
|
1529
2453
|
};
|
1530
2454
|
|
1531
|
-
const
|
2455
|
+
const StoreOps = {
|
1532
2456
|
i32: Opcodes.i32_store,
|
1533
2457
|
i64: Opcodes.i64_store,
|
1534
2458
|
f64: Opcodes.f64_store,
|
@@ -1537,13 +2461,32 @@ const storeOps = {
|
|
1537
2461
|
i16: Opcodes.i32_store16
|
1538
2462
|
};
|
1539
2463
|
|
2464
|
+
let data = [];
|
2465
|
+
|
2466
|
+
const compileBytes = (val, itemType, signed = true) => {
|
2467
|
+
// todo: this is a mess and needs confirming / ????
|
2468
|
+
switch (itemType) {
|
2469
|
+
case 'i8': return [ val % 256 ];
|
2470
|
+
case 'i16': return [ val % 256, Math.floor(val / 256) ];
|
2471
|
+
|
2472
|
+
case 'i32':
|
2473
|
+
case 'i64':
|
2474
|
+
return enforceFourBytes(signedLEB128(val));
|
2475
|
+
|
2476
|
+
case 'f64': return ieee754_binary64(val);
|
2477
|
+
}
|
2478
|
+
};
|
2479
|
+
|
1540
2480
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1541
2481
|
const out = [];
|
1542
2482
|
|
2483
|
+
let firstAssign = false;
|
1543
2484
|
if (!arrays.has(name) || name === '$undeclared') {
|
2485
|
+
firstAssign = true;
|
2486
|
+
|
1544
2487
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1545
2488
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1546
|
-
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}
|
2489
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
1547
2490
|
}
|
1548
2491
|
|
1549
2492
|
const pointer = arrays.get(name);
|
@@ -1551,8 +2494,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1551
2494
|
const useRawElements = !!decl.rawElements;
|
1552
2495
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1553
2496
|
|
2497
|
+
const valtype = itemTypeToValtype[itemType];
|
1554
2498
|
const length = elements.length;
|
1555
2499
|
|
2500
|
+
if (firstAssign && useRawElements) {
|
2501
|
+
let bytes = compileBytes(length, 'i32');
|
2502
|
+
|
2503
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2504
|
+
if (elements[i] == null) continue;
|
2505
|
+
|
2506
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2507
|
+
}
|
2508
|
+
|
2509
|
+
data.push({
|
2510
|
+
offset: pointer,
|
2511
|
+
bytes
|
2512
|
+
});
|
2513
|
+
|
2514
|
+
// local value as pointer
|
2515
|
+
out.push(...number(pointer));
|
2516
|
+
|
2517
|
+
return [ out, pointer ];
|
2518
|
+
}
|
2519
|
+
|
1556
2520
|
// store length as 0th array
|
1557
2521
|
out.push(
|
1558
2522
|
...number(0, Valtype.i32),
|
@@ -1560,8 +2524,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1560
2524
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1561
2525
|
);
|
1562
2526
|
|
1563
|
-
const storeOp =
|
1564
|
-
const valtype = itemTypeToValtype[itemType];
|
2527
|
+
const storeOp = StoreOps[itemType];
|
1565
2528
|
|
1566
2529
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1567
2530
|
if (elements[i] == null) continue;
|
@@ -1576,30 +2539,34 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1576
2539
|
// local value as pointer
|
1577
2540
|
out.push(...number(pointer));
|
1578
2541
|
|
1579
|
-
|
2542
|
+
return [ out, pointer ];
|
2543
|
+
};
|
2544
|
+
|
2545
|
+
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2546
|
+
const rawElements = new Array(str.length);
|
2547
|
+
for (let i = 0; i < str.length; i++) {
|
2548
|
+
rawElements[i] = str.charCodeAt(i);
|
2549
|
+
}
|
1580
2550
|
|
1581
|
-
return
|
2551
|
+
return makeArray(scope, {
|
2552
|
+
rawElements
|
2553
|
+
}, global, name, false, 'i16')[0];
|
1582
2554
|
};
|
1583
2555
|
|
1584
2556
|
let arrays = new Map();
|
1585
2557
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1586
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype);
|
2558
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1587
2559
|
};
|
1588
2560
|
|
1589
2561
|
export const generateMember = (scope, decl, _global, _name) => {
|
1590
|
-
const
|
2562
|
+
const name = decl.object.name;
|
2563
|
+
const pointer = arrays.get(name);
|
2564
|
+
|
2565
|
+
const aotPointer = pointer != null;
|
1591
2566
|
|
1592
2567
|
// hack: .length
|
1593
2568
|
if (decl.property.name === 'length') {
|
1594
2569
|
// if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
|
1595
|
-
|
1596
|
-
const name = decl.object.name;
|
1597
|
-
const pointer = arrays.get(name);
|
1598
|
-
|
1599
|
-
scope.memory = true;
|
1600
|
-
|
1601
|
-
const aotPointer = pointer != null;
|
1602
|
-
|
1603
2570
|
return [
|
1604
2571
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1605
2572
|
...generate(scope, decl.object),
|
@@ -1611,18 +2578,17 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1611
2578
|
];
|
1612
2579
|
}
|
1613
2580
|
|
1614
|
-
//
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
const aotPointer = pointer != null;
|
2581
|
+
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2582
|
+
// hack: this is naughty and will break things!
|
2583
|
+
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2584
|
+
if (pages.hasString) {
|
2585
|
+
0, [ newOut, newPointer ] = makeArray(scope, {
|
2586
|
+
rawElements: new Array(1)
|
2587
|
+
}, _global, _name, true, 'i16');
|
2588
|
+
}
|
1623
2589
|
|
1624
|
-
|
1625
|
-
|
2590
|
+
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2591
|
+
[TYPES._array]: [
|
1626
2592
|
// get index as valtype
|
1627
2593
|
...generate(scope, decl.property),
|
1628
2594
|
|
@@ -1638,45 +2604,46 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1638
2604
|
]),
|
1639
2605
|
|
1640
2606
|
// read from memory
|
1641
|
-
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1642
|
-
];
|
1643
|
-
}
|
2607
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
1644
2608
|
|
1645
|
-
|
2609
|
+
...number(TYPES.number, Valtype.i32),
|
2610
|
+
setLastType(scope)
|
2611
|
+
],
|
1646
2612
|
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
2613
|
+
[TYPES.string]: [
|
2614
|
+
// setup new/out array
|
2615
|
+
...newOut,
|
2616
|
+
[ Opcodes.drop ],
|
1651
2617
|
|
1652
|
-
|
1653
|
-
// setup new/out array
|
1654
|
-
...newOut,
|
1655
|
-
[ Opcodes.drop ],
|
2618
|
+
...number(0, Valtype.i32), // base 0 for store later
|
1656
2619
|
|
1657
|
-
|
2620
|
+
...generate(scope, decl.property),
|
1658
2621
|
|
1659
|
-
|
2622
|
+
Opcodes.i32_to_u,
|
2623
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
2624
|
+
[ Opcodes.i32_mul ],
|
1660
2625
|
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
2626
|
+
...(aotPointer ? [] : [
|
2627
|
+
...generate(scope, decl.object),
|
2628
|
+
Opcodes.i32_to_u,
|
2629
|
+
[ Opcodes.i32_add ]
|
2630
|
+
]),
|
1664
2631
|
|
1665
|
-
|
1666
|
-
|
1667
|
-
Opcodes.i32_to_u,
|
1668
|
-
[ Opcodes.i32_add ]
|
1669
|
-
]),
|
2632
|
+
// load current string ind {arg}
|
2633
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
1670
2634
|
|
1671
|
-
|
1672
|
-
|
2635
|
+
// store to new string ind 0
|
2636
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
1673
2637
|
|
1674
|
-
|
1675
|
-
|
2638
|
+
// return new string (page)
|
2639
|
+
...number(newPointer),
|
1676
2640
|
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
2641
|
+
...number(TYPES.string, Valtype.i32),
|
2642
|
+
setLastType(scope)
|
2643
|
+
],
|
2644
|
+
|
2645
|
+
default: [ [ Opcodes.unreachable ] ]
|
2646
|
+
});
|
1680
2647
|
};
|
1681
2648
|
|
1682
2649
|
const randId = () => Math.random().toString(16).slice(0, -4);
|
@@ -1721,22 +2688,25 @@ const generateFunc = (scope, decl) => {
|
|
1721
2688
|
if (decl.generator) return todo('generator functions are not supported');
|
1722
2689
|
|
1723
2690
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
1724
|
-
const params = decl.params
|
2691
|
+
const params = decl.params ?? [];
|
1725
2692
|
|
1726
2693
|
// const innerScope = { ...scope };
|
1727
2694
|
// TODO: share scope/locals between !!!
|
1728
2695
|
const innerScope = {
|
1729
2696
|
locals: {},
|
1730
2697
|
localInd: 0,
|
1731
|
-
|
1732
|
-
|
2698
|
+
// value, type
|
2699
|
+
returns: [ valtypeBinary, Valtype.i32 ],
|
1733
2700
|
throws: false,
|
1734
2701
|
name
|
1735
2702
|
};
|
1736
2703
|
|
1737
2704
|
for (let i = 0; i < params.length; i++) {
|
1738
|
-
|
1739
|
-
|
2705
|
+
allocVar(innerScope, params[i].name, false);
|
2706
|
+
|
2707
|
+
if (typedInput && params[i].typeAnnotation) {
|
2708
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
2709
|
+
}
|
1740
2710
|
}
|
1741
2711
|
|
1742
2712
|
let body = objectHack(decl.body);
|
@@ -1751,11 +2721,9 @@ const generateFunc = (scope, decl) => {
|
|
1751
2721
|
const wasm = generate(innerScope, body);
|
1752
2722
|
const func = {
|
1753
2723
|
name,
|
1754
|
-
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
2724
|
+
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
1755
2725
|
returns: innerScope.returns,
|
1756
|
-
returnType: innerScope.returnType ?? TYPES.number,
|
1757
2726
|
locals: innerScope.locals,
|
1758
|
-
memory: innerScope.memory,
|
1759
2727
|
throws: innerScope.throws,
|
1760
2728
|
index: currentFuncIndex++
|
1761
2729
|
};
|
@@ -1768,121 +2736,13 @@ const generateFunc = (scope, decl) => {
|
|
1768
2736
|
}
|
1769
2737
|
}
|
1770
2738
|
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
for (let i = 0; i < params.length; i++) {
|
1778
|
-
const name = params[i];
|
1779
|
-
const local = func.locals[name];
|
1780
|
-
if (local.type === Valtype.v128) {
|
1781
|
-
vecParams++;
|
1782
|
-
|
1783
|
-
/* func.memory = true; // mark func as using memory
|
1784
|
-
|
1785
|
-
wasm.unshift( // add v128 load for param
|
1786
|
-
[ Opcodes.i32_const, 0 ],
|
1787
|
-
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1788
|
-
[ Opcodes.local_set, local.idx ]
|
1789
|
-
); */
|
1790
|
-
|
1791
|
-
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
1792
|
-
|
1793
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
1794
|
-
const { vecType } = local;
|
1795
|
-
let [ type, lanes ] = vecType.split('x');
|
1796
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
1797
|
-
|
1798
|
-
lanes = parseInt(lanes);
|
1799
|
-
type = Valtype[type];
|
1800
|
-
|
1801
|
-
const name = params[i]; // get original param name
|
1802
|
-
|
1803
|
-
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
1804
|
-
|
1805
|
-
// update index of original local
|
1806
|
-
// delete func.locals[name];
|
1807
|
-
|
1808
|
-
// add new locals for params
|
1809
|
-
for (let j = 0; j < lanes; j++) {
|
1810
|
-
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
1811
|
-
}
|
1812
|
-
|
1813
|
-
// prepend wasm to generate expected v128 locals
|
1814
|
-
wasm.splice(i * 2 + offset * 2, 0,
|
1815
|
-
...i32x4(0, 0, 0, 0),
|
1816
|
-
...new Array(lanes).fill(0).flatMap((_, j) => [
|
1817
|
-
[ Opcodes.local_get, offset + j ],
|
1818
|
-
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
1819
|
-
]),
|
1820
|
-
[ Opcodes.local_set, i ]
|
1821
|
-
);
|
1822
|
-
|
1823
|
-
offset += lanes;
|
1824
|
-
|
1825
|
-
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
1826
|
-
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
1827
|
-
|
1828
|
-
// add vec type index to hash name prefix for wrapper to know how to wrap
|
1829
|
-
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
1830
|
-
const secondHash = func.name.slice(1).indexOf('#');
|
1831
|
-
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
1832
|
-
}
|
1833
|
-
}
|
1834
|
-
|
1835
|
-
if (offset !== 0) {
|
1836
|
-
// bump local indexes for all other locals after
|
1837
|
-
for (const x in func.locals) {
|
1838
|
-
const local = func.locals[x];
|
1839
|
-
if (!local.vecParamAutogen) local.idx += offset;
|
1840
|
-
}
|
1841
|
-
|
1842
|
-
// bump local indexes in wasm local.get/set
|
1843
|
-
for (let j = 0; j < wasm.length; j++) {
|
1844
|
-
const inst = wasm[j];
|
1845
|
-
if (j < offset * 2 + vecParams * 2) {
|
1846
|
-
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
1847
|
-
continue;
|
1848
|
-
}
|
1849
|
-
|
1850
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
1851
|
-
}
|
1852
|
-
}
|
1853
|
-
|
1854
|
-
// change v128 return into many <type> instead as unsupported return valtype
|
1855
|
-
const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
|
1856
|
-
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
1857
|
-
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
1858
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
1859
|
-
const { vecType } = lastReturnLocal;
|
1860
|
-
let [ type, lanes ] = vecType.split('x');
|
1861
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
1862
|
-
|
1863
|
-
lanes = parseInt(lanes);
|
1864
|
-
type = Valtype[type];
|
1865
|
-
|
1866
|
-
const vecIdx = lastReturnLocal.idx;
|
1867
|
-
|
1868
|
-
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
1869
|
-
const tmpIdx = [];
|
1870
|
-
for (let i = 0; i < lanes; i++) {
|
1871
|
-
const idx = lastIdx + i + 1;
|
1872
|
-
tmpIdx.push(idx);
|
1873
|
-
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
1874
|
-
}
|
1875
|
-
|
1876
|
-
wasm.splice(wasm.length - 1, 1,
|
1877
|
-
...new Array(lanes).fill(0).flatMap((_, i) => [
|
1878
|
-
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
1879
|
-
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
1880
|
-
[ Opcodes.local_set, tmpIdx[i] ],
|
1881
|
-
].filter(x => x !== null)),
|
1882
|
-
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
2739
|
+
// add end return if not found
|
2740
|
+
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
2741
|
+
wasm.push(
|
2742
|
+
...number(0),
|
2743
|
+
...number(TYPES.undefined, Valtype.i32),
|
2744
|
+
[ Opcodes.return ]
|
1883
2745
|
);
|
1884
|
-
|
1885
|
-
func.returns = new Array(lanes).fill(type);
|
1886
2746
|
}
|
1887
2747
|
|
1888
2748
|
func.wasm = wasm;
|
@@ -1893,10 +2753,10 @@ const generateFunc = (scope, decl) => {
|
|
1893
2753
|
};
|
1894
2754
|
|
1895
2755
|
const generateCode = (scope, decl) => {
|
1896
|
-
|
2756
|
+
let out = [];
|
1897
2757
|
|
1898
2758
|
for (const x of decl.body) {
|
1899
|
-
out.
|
2759
|
+
out = out.concat(generate(scope, x));
|
1900
2760
|
}
|
1901
2761
|
|
1902
2762
|
return out;
|
@@ -1912,10 +2772,9 @@ const internalConstrs = {
|
|
1912
2772
|
|
1913
2773
|
// new Array(n)
|
1914
2774
|
|
1915
|
-
makeArray(scope, {
|
2775
|
+
const [ , pointer ] = makeArray(scope, {
|
1916
2776
|
rawElements: new Array(0)
|
1917
2777
|
}, global, name, true);
|
1918
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
1919
2778
|
|
1920
2779
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
1921
2780
|
|
@@ -1933,9 +2792,38 @@ const internalConstrs = {
|
|
1933
2792
|
];
|
1934
2793
|
},
|
1935
2794
|
type: TYPES._array
|
2795
|
+
},
|
2796
|
+
|
2797
|
+
__Array_of: {
|
2798
|
+
// this is not a constructor but best fits internal structure here
|
2799
|
+
generate: (scope, decl, global, name) => {
|
2800
|
+
// Array.of(i0, i1, ...)
|
2801
|
+
return generateArray(scope, {
|
2802
|
+
elements: decl.arguments
|
2803
|
+
}, global, name);
|
2804
|
+
},
|
2805
|
+
type: TYPES._array,
|
2806
|
+
notConstr: true
|
1936
2807
|
}
|
1937
2808
|
};
|
1938
2809
|
|
2810
|
+
// const _ = Array.prototype.push;
|
2811
|
+
// Array.prototype.push = function (a) {
|
2812
|
+
// const check = arr => {
|
2813
|
+
// for (const x of arr) {
|
2814
|
+
// if (x === undefined) {
|
2815
|
+
// console.trace(arr);
|
2816
|
+
// process.exit();
|
2817
|
+
// }
|
2818
|
+
// if (Array.isArray(x)) check(x);
|
2819
|
+
// }
|
2820
|
+
// };
|
2821
|
+
// if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
|
2822
|
+
// // if (Array.isArray(a)) check(a);
|
2823
|
+
|
2824
|
+
// return _.apply(this, arguments);
|
2825
|
+
// };
|
2826
|
+
|
1939
2827
|
export default program => {
|
1940
2828
|
globals = {};
|
1941
2829
|
globalInd = 0;
|
@@ -1944,9 +2832,9 @@ export default program => {
|
|
1944
2832
|
funcs = [];
|
1945
2833
|
funcIndex = {};
|
1946
2834
|
depth = [];
|
1947
|
-
typeStates = {};
|
1948
2835
|
arrays = new Map();
|
1949
2836
|
pages = new Map();
|
2837
|
+
data = [];
|
1950
2838
|
currentFuncIndex = importedFuncs.length;
|
1951
2839
|
|
1952
2840
|
globalThis.valtype = 'f64';
|
@@ -1996,18 +2884,20 @@ export default program => {
|
|
1996
2884
|
body: program.body
|
1997
2885
|
};
|
1998
2886
|
|
2887
|
+
if (process.argv.includes('-ast-log')) console.log(program.body.body);
|
2888
|
+
|
1999
2889
|
generateFunc(scope, program);
|
2000
2890
|
|
2001
2891
|
const main = funcs[funcs.length - 1];
|
2002
2892
|
main.export = true;
|
2003
|
-
main.returns = [ valtypeBinary ];
|
2893
|
+
main.returns = [ valtypeBinary, Valtype.i32 ];
|
2004
2894
|
|
2005
2895
|
const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
|
2006
2896
|
if (lastInst[0] === Opcodes.drop) {
|
2007
2897
|
main.wasm.splice(main.wasm.length - 1, 1);
|
2008
2898
|
|
2009
2899
|
const finalStatement = program.body.body[program.body.body.length - 1];
|
2010
|
-
main.
|
2900
|
+
main.wasm.push(...getNodeType(main, finalStatement));
|
2011
2901
|
}
|
2012
2902
|
|
2013
2903
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
@@ -2023,5 +2913,5 @@ export default program => {
|
|
2023
2913
|
// if blank main func and other exports, remove it
|
2024
2914
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2025
2915
|
|
2026
|
-
return { funcs, globals, tags, exceptions, pages };
|
2916
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2027
2917
|
};
|