porffor 0.1.1 → 0.2.0-c6c8c81
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 +184 -204
- package/compiler/2c.js +377 -0
- package/compiler/builtins/base64.js +91 -91
- package/compiler/builtins.js +19 -13
- package/compiler/codeGen.js +1313 -418
- package/compiler/decompile.js +35 -9
- package/compiler/embedding.js +9 -5
- package/compiler/encoding.js +8 -2
- package/compiler/index.js +55 -17
- package/compiler/log.js +15 -0
- package/compiler/opt.js +357 -258
- package/compiler/parse.js +24 -1
- package/compiler/prototype.js +263 -56
- package/compiler/sections.js +51 -8
- package/compiler/wasmSpec.js +3 -0
- package/compiler/wrap.js +20 -6
- 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,45 +153,65 @@ 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:
|
185
217
|
return todo(`no generation for ${decl.type}!`);
|
@@ -269,19 +301,17 @@ const generateIdent = (scope, decl) => {
|
|
269
301
|
|
270
302
|
const generateReturn = (scope, decl) => {
|
271
303
|
if (decl.argument === null) {
|
272
|
-
if (!scope.returnType) scope.returnType = TYPES.undefined;
|
273
|
-
|
274
304
|
// just bare "return"
|
275
305
|
return [
|
276
306
|
...number(UNDEFINED), // "undefined" if func returns
|
307
|
+
...number(TYPES.undefined, Valtype.i32), // type undefined
|
277
308
|
[ Opcodes.return ]
|
278
309
|
];
|
279
310
|
}
|
280
311
|
|
281
|
-
if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
|
282
|
-
|
283
312
|
return [
|
284
313
|
...generate(scope, decl.argument),
|
314
|
+
...getNodeType(scope, decl.argument),
|
285
315
|
[ Opcodes.return ]
|
286
316
|
];
|
287
317
|
};
|
@@ -295,11 +325,13 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
295
325
|
return idx;
|
296
326
|
};
|
297
327
|
|
298
|
-
const
|
328
|
+
const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
329
|
+
|
330
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
299
331
|
const checks = {
|
300
|
-
'||':
|
301
|
-
'&&':
|
302
|
-
|
332
|
+
'||': falsy,
|
333
|
+
'&&': truthy,
|
334
|
+
'??': nullish
|
303
335
|
};
|
304
336
|
|
305
337
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -307,14 +339,52 @@ const performLogicOp = (scope, op, left, right) => {
|
|
307
339
|
// generic structure for {a} OP {b}
|
308
340
|
// -->
|
309
341
|
// _ = {a}; if (OP_CHECK) {b} else _
|
342
|
+
|
343
|
+
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
344
|
+
// (like if we are in an if condition - very common)
|
345
|
+
const leftIsInt = isIntOp(left[left.length - 1]);
|
346
|
+
const rightIsInt = isIntOp(right[right.length - 1]);
|
347
|
+
|
348
|
+
const canInt = leftIsInt && rightIsInt;
|
349
|
+
|
350
|
+
if (canInt) {
|
351
|
+
// remove int -> float conversions from left and right
|
352
|
+
left.pop();
|
353
|
+
right.pop();
|
354
|
+
|
355
|
+
return [
|
356
|
+
...left,
|
357
|
+
[ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
358
|
+
...checks[op](scope, [], leftType, true, true),
|
359
|
+
[ Opcodes.if, Valtype.i32 ],
|
360
|
+
...right,
|
361
|
+
// note type
|
362
|
+
...rightType,
|
363
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
364
|
+
[ Opcodes.else ],
|
365
|
+
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
366
|
+
// note type
|
367
|
+
...leftType,
|
368
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
369
|
+
[ Opcodes.end ],
|
370
|
+
Opcodes.i32_from
|
371
|
+
];
|
372
|
+
}
|
373
|
+
|
310
374
|
return [
|
311
375
|
...left,
|
312
376
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
313
|
-
...checks[op],
|
377
|
+
...checks[op](scope, [], leftType, false, true),
|
314
378
|
[ Opcodes.if, valtypeBinary ],
|
315
379
|
...right,
|
380
|
+
// note type
|
381
|
+
...rightType,
|
382
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
316
383
|
[ Opcodes.else ],
|
317
384
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
385
|
+
// note type
|
386
|
+
...leftType,
|
387
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
318
388
|
[ Opcodes.end ]
|
319
389
|
];
|
320
390
|
};
|
@@ -325,15 +395,16 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
325
395
|
// todo: optimize by looking up names in arrays and using that if exists?
|
326
396
|
// todo: optimize this if using literals/known lengths?
|
327
397
|
|
328
|
-
scope.memory = true;
|
329
|
-
|
330
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
331
|
-
|
332
398
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
399
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
400
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
335
401
|
|
402
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
403
|
+
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
404
|
+
|
336
405
|
if (assign) {
|
406
|
+
const pointer = arrays.get(name ?? '$undeclared');
|
407
|
+
|
337
408
|
return [
|
338
409
|
// setup right
|
339
410
|
...right,
|
@@ -384,15 +455,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
384
455
|
|
385
456
|
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
386
457
|
|
387
|
-
|
458
|
+
// alloc/assign array
|
459
|
+
const [ , pointer ] = makeArray(scope, {
|
388
460
|
rawElements: new Array(0)
|
389
461
|
}, global, name, true, 'i16');
|
390
462
|
|
391
463
|
return [
|
392
|
-
// setup new/out array
|
393
|
-
...newOut,
|
394
|
-
[ Opcodes.drop ],
|
395
|
-
|
396
464
|
// setup left
|
397
465
|
...left,
|
398
466
|
Opcodes.i32_to_u,
|
@@ -458,90 +526,309 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
458
526
|
];
|
459
527
|
};
|
460
528
|
|
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,
|
529
|
+
const compareStrings = (scope, left, right) => {
|
530
|
+
// todo: this should be rewritten into a func
|
531
|
+
// todo: convert left and right to strings if not
|
532
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
533
|
+
// todo: optimize this if using literals/known lengths?
|
474
534
|
|
475
|
-
|
476
|
-
|
535
|
+
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
536
|
+
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
537
|
+
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
538
|
+
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
477
539
|
|
478
|
-
|
479
|
-
|
480
|
-
Opcodes.i32_from_u
|
481
|
-
]
|
482
|
-
}
|
540
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
541
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
483
542
|
|
484
|
-
// if = 0
|
485
543
|
return [
|
486
|
-
|
544
|
+
// setup left
|
545
|
+
...left,
|
546
|
+
Opcodes.i32_to_u,
|
547
|
+
[ Opcodes.local_tee, leftPointer ],
|
487
548
|
|
488
|
-
|
489
|
-
|
549
|
+
// setup right
|
550
|
+
...right,
|
551
|
+
Opcodes.i32_to_u,
|
552
|
+
[ Opcodes.local_tee, rightPointer ],
|
553
|
+
|
554
|
+
// fast path: check leftPointer == rightPointer
|
555
|
+
// use if (block) for everything after to "return" a value early
|
556
|
+
[ Opcodes.i32_ne ],
|
557
|
+
[ Opcodes.if, Valtype.i32 ],
|
558
|
+
|
559
|
+
// get lengths
|
560
|
+
[ Opcodes.local_get, leftPointer ],
|
561
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
562
|
+
[ Opcodes.local_tee, leftLength ],
|
563
|
+
|
564
|
+
[ Opcodes.local_get, rightPointer ],
|
565
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
566
|
+
[ Opcodes.local_tee, rightLength ],
|
567
|
+
|
568
|
+
// fast path: check leftLength != rightLength
|
569
|
+
[ Opcodes.i32_ne ],
|
570
|
+
[ Opcodes.if, Blocktype.void ],
|
571
|
+
...number(0, Valtype.i32),
|
572
|
+
[ Opcodes.br, 1 ],
|
573
|
+
[ Opcodes.end ],
|
574
|
+
|
575
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
576
|
+
|
577
|
+
// tmp could have already been used
|
578
|
+
...number(0, Valtype.i32),
|
579
|
+
[ Opcodes.local_set, index ],
|
580
|
+
|
581
|
+
// setup index end as length * sizeof i16 (2)
|
582
|
+
// we do this instead of having to do mul/div each iter for perf™
|
583
|
+
[ Opcodes.local_get, leftLength ],
|
584
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
585
|
+
[ Opcodes.i32_mul ],
|
586
|
+
[ Opcodes.local_set, indexEnd ],
|
587
|
+
|
588
|
+
// iterate over each char and check if eq
|
589
|
+
[ Opcodes.loop, Blocktype.void ],
|
590
|
+
|
591
|
+
// fetch left
|
592
|
+
[ Opcodes.local_get, index ],
|
593
|
+
[ Opcodes.local_get, leftPointer ],
|
594
|
+
[ Opcodes.i32_add ],
|
595
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
596
|
+
|
597
|
+
// fetch right
|
598
|
+
[ Opcodes.local_get, index ],
|
599
|
+
[ Opcodes.local_get, rightPointer ],
|
600
|
+
[ Opcodes.i32_add ],
|
601
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
602
|
+
|
603
|
+
// not equal, "return" false
|
604
|
+
[ Opcodes.i32_ne ],
|
605
|
+
[ Opcodes.if, Blocktype.void ],
|
606
|
+
...number(0, Valtype.i32),
|
607
|
+
[ Opcodes.br, 2 ],
|
608
|
+
[ Opcodes.end ],
|
609
|
+
|
610
|
+
// index += sizeof i16 (2)
|
611
|
+
[ Opcodes.local_get, index ],
|
612
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
613
|
+
[ Opcodes.i32_add ],
|
614
|
+
[ Opcodes.local_tee, index ],
|
615
|
+
|
616
|
+
// if index != index end (length * sizeof 16), loop
|
617
|
+
[ Opcodes.local_get, indexEnd ],
|
618
|
+
[ Opcodes.i32_ne ],
|
619
|
+
[ Opcodes.br_if, 0 ],
|
620
|
+
[ Opcodes.end ],
|
621
|
+
|
622
|
+
// no failed checks, so true!
|
623
|
+
...number(1, Valtype.i32),
|
624
|
+
|
625
|
+
// pointers match, so true
|
626
|
+
[ Opcodes.else ],
|
627
|
+
...number(1, Valtype.i32),
|
628
|
+
[ Opcodes.end ],
|
629
|
+
|
630
|
+
// convert i32 result to valtype
|
631
|
+
// do not do as automatically added by binary exp gen for equality ops
|
632
|
+
// Opcodes.i32_from_u
|
490
633
|
];
|
491
634
|
};
|
492
635
|
|
493
|
-
const truthy = (scope, wasm, type) => {
|
494
|
-
|
495
|
-
if (type === TYPES._array) return [
|
636
|
+
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
637
|
+
if (isIntOp(wasm[wasm.length - 1])) return [
|
496
638
|
...wasm,
|
497
|
-
[ Opcodes.
|
498
|
-
number(1)
|
639
|
+
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
499
640
|
];
|
500
641
|
|
501
|
-
|
502
|
-
// if not "" (length = 0)
|
503
|
-
return [
|
504
|
-
// pointer
|
505
|
-
...wasm,
|
642
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
506
643
|
|
507
|
-
|
508
|
-
|
644
|
+
const def = [
|
645
|
+
// if value != 0
|
646
|
+
[ Opcodes.local_get, tmp ],
|
509
647
|
|
510
|
-
|
511
|
-
|
512
|
-
[ Opcodes.i32_eqz ], */
|
513
|
-
Opcodes.i32_from_u
|
514
|
-
]
|
515
|
-
}
|
516
|
-
|
517
|
-
// if != 0
|
518
|
-
return [
|
519
|
-
...wasm,
|
648
|
+
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
649
|
+
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
520
650
|
|
521
651
|
/* Opcodes.eqz,
|
522
652
|
[ Opcodes.i32_eqz ],
|
523
653
|
Opcodes.i32_from */
|
524
654
|
];
|
655
|
+
|
656
|
+
return [
|
657
|
+
...wasm,
|
658
|
+
[ Opcodes.local_set, tmp ],
|
659
|
+
|
660
|
+
...typeSwitch(scope, type, {
|
661
|
+
// [TYPES.number]: def,
|
662
|
+
[TYPES._array]: [
|
663
|
+
// arrays are always truthy
|
664
|
+
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
665
|
+
],
|
666
|
+
[TYPES.string]: [
|
667
|
+
[ Opcodes.local_get, tmp ],
|
668
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
669
|
+
|
670
|
+
// get length
|
671
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
672
|
+
|
673
|
+
// if length != 0
|
674
|
+
/* [ Opcodes.i32_eqz ],
|
675
|
+
[ Opcodes.i32_eqz ], */
|
676
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
677
|
+
],
|
678
|
+
default: def
|
679
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
680
|
+
];
|
681
|
+
};
|
682
|
+
|
683
|
+
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
684
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
685
|
+
return [
|
686
|
+
...wasm,
|
687
|
+
[ Opcodes.local_set, tmp ],
|
688
|
+
|
689
|
+
...typeSwitch(scope, type, {
|
690
|
+
[TYPES._array]: [
|
691
|
+
// arrays are always truthy
|
692
|
+
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
693
|
+
],
|
694
|
+
[TYPES.string]: [
|
695
|
+
[ Opcodes.local_get, tmp ],
|
696
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
697
|
+
|
698
|
+
// get length
|
699
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
700
|
+
|
701
|
+
// if length == 0
|
702
|
+
[ Opcodes.i32_eqz ],
|
703
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
704
|
+
],
|
705
|
+
default: [
|
706
|
+
// if value == 0
|
707
|
+
[ Opcodes.local_get, tmp ],
|
708
|
+
|
709
|
+
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
710
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
711
|
+
]
|
712
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
713
|
+
];
|
525
714
|
};
|
526
715
|
|
527
|
-
const
|
716
|
+
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
717
|
+
const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
718
|
+
return [
|
719
|
+
...wasm,
|
720
|
+
[ Opcodes.local_set, tmp ],
|
721
|
+
|
722
|
+
...typeSwitch(scope, type, {
|
723
|
+
[TYPES.undefined]: [
|
724
|
+
// undefined
|
725
|
+
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
726
|
+
],
|
727
|
+
[TYPES.object]: [
|
728
|
+
// object, null if == 0
|
729
|
+
[ Opcodes.local_get, tmp ],
|
730
|
+
|
731
|
+
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
732
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
733
|
+
],
|
734
|
+
default: [
|
735
|
+
// not
|
736
|
+
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
737
|
+
]
|
738
|
+
}, intOut ? Valtype.i32 : valtypeBinary)
|
739
|
+
];
|
740
|
+
};
|
741
|
+
|
742
|
+
const stringOnly = wasm => {
|
743
|
+
if (!Array.isArray(wasm[0])) return [ ...wasm, 'string_only' ];
|
744
|
+
if (wasm.length === 1) return [ [ ...wasm[0], 'string_only' ] ];
|
745
|
+
|
746
|
+
return [
|
747
|
+
[ ...wasm[0], 'string_only|start' ],
|
748
|
+
...wasm.slice(1, -1),
|
749
|
+
[ ...wasm[wasm.length - 1], 'string_only|end' ]
|
750
|
+
];
|
751
|
+
}
|
752
|
+
|
753
|
+
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
528
754
|
if (op === '||' || op === '&&' || op === '??') {
|
529
|
-
return performLogicOp(scope, op, left, right);
|
755
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
756
|
+
}
|
757
|
+
|
758
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
759
|
+
const strictOp = op === '===' || op === '!==';
|
760
|
+
|
761
|
+
const startOut = [], endOut = [];
|
762
|
+
const finalise = out => startOut.concat(out, endOut);
|
763
|
+
|
764
|
+
// if strict (in)equal check types match
|
765
|
+
if (strictOp) {
|
766
|
+
// startOut.push(
|
767
|
+
// ...leftType,
|
768
|
+
// ...rightType,
|
769
|
+
// [ Opcodes.i32_eq ]
|
770
|
+
// );
|
771
|
+
|
772
|
+
// endOut.push(
|
773
|
+
// [ Opcodes.i32_and ]
|
774
|
+
// );
|
775
|
+
|
776
|
+
// startOut.push(
|
777
|
+
// [ Opcodes.block, Valtype.i32 ],
|
778
|
+
// ...leftType,
|
779
|
+
// ...rightType,
|
780
|
+
// [ Opcodes.i32_ne ],
|
781
|
+
// [ Opcodes.if, Blocktype.void ],
|
782
|
+
// ...number(op === '===' ? 0 : 1, Valtype.i32),
|
783
|
+
// [ Opcodes.br, 1 ],
|
784
|
+
// [ Opcodes.end ]
|
785
|
+
// );
|
786
|
+
|
787
|
+
// endOut.push(
|
788
|
+
// [ Opcodes.end ]
|
789
|
+
// );
|
790
|
+
|
791
|
+
endOut.push(
|
792
|
+
...leftType,
|
793
|
+
...rightType,
|
794
|
+
...(op === '===' ? [
|
795
|
+
[ Opcodes.i32_eq ],
|
796
|
+
[ Opcodes.i32_and ]
|
797
|
+
] : [
|
798
|
+
[ Opcodes.i32_ne ],
|
799
|
+
[ Opcodes.i32_or ]
|
800
|
+
])
|
801
|
+
);
|
530
802
|
}
|
531
803
|
|
532
|
-
if
|
533
|
-
|
534
|
-
// string concat (a + b)
|
535
|
-
return concatStrings(scope, left, right, _global, _name, assign);
|
536
|
-
}
|
804
|
+
// todo: if equality op and an operand is undefined, return false
|
805
|
+
// todo: niche null hell with 0
|
537
806
|
|
538
|
-
|
539
|
-
|
807
|
+
// if (leftType === TYPES.string || rightType === TYPES.string) {
|
808
|
+
// if (op === '+') {
|
809
|
+
// // string concat (a + b)
|
810
|
+
// return finalise(concatStrings(scope, left, right, _global, _name, assign));
|
811
|
+
// }
|
540
812
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
813
|
+
// // not an equality op, NaN
|
814
|
+
// if (!eqOp) return finalise(number(NaN));
|
815
|
+
|
816
|
+
// // else leave bool ops
|
817
|
+
// // todo: convert string to number if string and number/bool
|
818
|
+
// // todo: string (>|>=|<|<=) string
|
819
|
+
|
820
|
+
// // string comparison
|
821
|
+
// if (op === '===' || op === '==') {
|
822
|
+
// return finalise(compareStrings(scope, left, right));
|
823
|
+
// }
|
824
|
+
|
825
|
+
// if (op === '!==' || op === '!=') {
|
826
|
+
// return finalise([
|
827
|
+
// ...compareStrings(scope, left, right),
|
828
|
+
// [ Opcodes.i32_eqz ]
|
829
|
+
// ]);
|
830
|
+
// }
|
831
|
+
// }
|
545
832
|
|
546
833
|
let ops = operatorOpcode[valtype][op];
|
547
834
|
|
@@ -551,35 +838,90 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
551
838
|
includeBuiltin(scope, builtinName);
|
552
839
|
const idx = funcIndex[builtinName];
|
553
840
|
|
554
|
-
return [
|
841
|
+
return finalise([
|
555
842
|
...left,
|
556
843
|
...right,
|
557
844
|
[ Opcodes.call, idx ]
|
558
|
-
];
|
845
|
+
]);
|
559
846
|
}
|
560
847
|
|
561
848
|
if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
|
562
849
|
|
563
850
|
if (!Array.isArray(ops)) ops = [ ops ];
|
851
|
+
ops = [ ops ];
|
852
|
+
|
853
|
+
let tmpLeft, tmpRight;
|
854
|
+
// if equal op, check if strings for compareStrings
|
855
|
+
if (op === '===' || op === '==' || op === '!==' || op === '!=') {
|
856
|
+
tmpLeft = localTmp(scope, '__tmpop_left');
|
857
|
+
tmpRight = localTmp(scope, '__tmpop_right');
|
858
|
+
|
859
|
+
ops.unshift(...stringOnly([
|
860
|
+
// if left is string
|
861
|
+
...leftType,
|
862
|
+
...number(TYPES.string, Valtype.i32),
|
863
|
+
[ Opcodes.i32_eq ],
|
864
|
+
|
865
|
+
// if right is string
|
866
|
+
...rightType,
|
867
|
+
...number(TYPES.string, Valtype.i32),
|
868
|
+
[ Opcodes.i32_eq ],
|
869
|
+
|
870
|
+
// if either are true
|
871
|
+
[ Opcodes.i32_or ],
|
872
|
+
[ Opcodes.if, Blocktype.void ],
|
873
|
+
|
874
|
+
// todo: convert non-strings to strings, for now fail immediately if one is not
|
875
|
+
// if left is not string
|
876
|
+
...leftType,
|
877
|
+
...number(TYPES.string, Valtype.i32),
|
878
|
+
[ Opcodes.i32_ne ],
|
879
|
+
|
880
|
+
// if right is not string
|
881
|
+
...rightType,
|
882
|
+
...number(TYPES.string, Valtype.i32),
|
883
|
+
[ Opcodes.i32_ne ],
|
884
|
+
|
885
|
+
// if either are true
|
886
|
+
[ Opcodes.i32_or ],
|
887
|
+
[ Opcodes.if, Blocktype.void ],
|
888
|
+
...number(0, Valtype.i32),
|
889
|
+
[ Opcodes.br, 1 ],
|
890
|
+
[ Opcodes.end ],
|
564
891
|
|
565
|
-
|
892
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
893
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
894
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
895
|
+
[ Opcodes.br, 1 ],
|
896
|
+
[ Opcodes.end ],
|
897
|
+
]));
|
898
|
+
|
899
|
+
// if not already in block, add a block
|
900
|
+
// if (endOut.length === 0) {
|
901
|
+
startOut.push(stringOnly([ Opcodes.block, Valtype.i32 ]));
|
902
|
+
// endOut.push(stringOnly([ Opcodes.end ]));
|
903
|
+
endOut.unshift(stringOnly([ Opcodes.end ]));
|
904
|
+
// }
|
905
|
+
}
|
906
|
+
|
907
|
+
return finalise([
|
566
908
|
...left,
|
909
|
+
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
567
910
|
...right,
|
568
|
-
|
569
|
-
|
911
|
+
...(tmpRight != null ? stringOnly([ [ Opcodes.local_tee, tmpRight ] ]) : []),
|
912
|
+
...ops
|
913
|
+
]);
|
570
914
|
};
|
571
915
|
|
572
916
|
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
|
-
];
|
917
|
+
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
918
|
|
577
919
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
578
920
|
|
579
921
|
return out;
|
580
922
|
};
|
581
923
|
|
582
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType,
|
924
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
|
583
925
|
const existing = funcs.find(x => x.name === name);
|
584
926
|
if (existing) return existing;
|
585
927
|
|
@@ -615,7 +957,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
615
957
|
returns,
|
616
958
|
returnType: TYPES[returnType ?? 'number'],
|
617
959
|
wasm,
|
618
|
-
memory,
|
619
960
|
internal: true,
|
620
961
|
index: currentFuncIndex++
|
621
962
|
};
|
@@ -634,21 +975,45 @@ const includeBuiltin = (scope, builtin) => {
|
|
634
975
|
};
|
635
976
|
|
636
977
|
const generateLogicExp = (scope, decl) => {
|
637
|
-
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
|
978
|
+
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
638
979
|
};
|
639
980
|
|
981
|
+
// T = JS type, V = value/pointer
|
982
|
+
// 0bTTT
|
983
|
+
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
984
|
+
// 50 bits usable: 0 11111111111 11??????????????????????????????????????????????????
|
985
|
+
// js type: 4 bits
|
986
|
+
// internal type: ? bits
|
987
|
+
// pointer: 32 bits
|
988
|
+
// https://piotrduperas.com/posts/nan-boxing
|
989
|
+
// 0x7ffc000000000000
|
990
|
+
// budget: 50 bits
|
991
|
+
// js type: 4 bits
|
992
|
+
// internal type: ? bits
|
993
|
+
// pointer: 32 bits
|
994
|
+
|
995
|
+
// generic
|
996
|
+
// 1 23 4 5
|
997
|
+
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
998
|
+
// 1: regular iEEE 754 double NaN
|
999
|
+
// 2: extra 1 bit to identify NaN box
|
1000
|
+
// 3: js type
|
1001
|
+
// 4: internal type
|
1002
|
+
// 5: pointer
|
1003
|
+
|
640
1004
|
const TYPES = {
|
641
|
-
number:
|
642
|
-
boolean:
|
643
|
-
string:
|
644
|
-
undefined:
|
645
|
-
object:
|
646
|
-
function:
|
647
|
-
symbol:
|
648
|
-
bigint:
|
1005
|
+
number: 0x00,
|
1006
|
+
boolean: 0x01,
|
1007
|
+
string: 0x02,
|
1008
|
+
undefined: 0x03,
|
1009
|
+
object: 0x04,
|
1010
|
+
function: 0x05,
|
1011
|
+
symbol: 0x06,
|
1012
|
+
bigint: 0x07,
|
649
1013
|
|
650
1014
|
// these are not "typeof" types but tracked internally
|
651
|
-
_array:
|
1015
|
+
_array: 0x10,
|
1016
|
+
_regexp: 0x11
|
652
1017
|
};
|
653
1018
|
|
654
1019
|
const TYPE_NAMES = {
|
@@ -661,108 +1026,178 @@ const TYPE_NAMES = {
|
|
661
1026
|
[TYPES.symbol]: 'Symbol',
|
662
1027
|
[TYPES.bigint]: 'BigInt',
|
663
1028
|
|
664
|
-
[TYPES._array]: 'Array'
|
1029
|
+
[TYPES._array]: 'Array',
|
1030
|
+
[TYPES._regexp]: 'RegExp'
|
665
1031
|
};
|
666
1032
|
|
667
|
-
let typeStates = {};
|
668
|
-
|
669
1033
|
const getType = (scope, _name) => {
|
670
1034
|
const name = mapName(_name);
|
671
|
-
if (scope.locals[name]) return typeStates[name];
|
672
1035
|
|
673
|
-
if (
|
674
|
-
if (
|
675
|
-
|
1036
|
+
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1037
|
+
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1038
|
+
|
1039
|
+
let type = TYPES.undefined;
|
1040
|
+
if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
|
1041
|
+
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
676
1042
|
|
677
|
-
if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)]
|
678
|
-
|
1043
|
+
if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
|
1044
|
+
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
679
1045
|
|
680
|
-
return
|
1046
|
+
return number(type, Valtype.i32);
|
681
1047
|
};
|
682
1048
|
|
683
|
-
const
|
684
|
-
|
685
|
-
return TYPES[typeof node.value];
|
686
|
-
}
|
1049
|
+
const setType = (scope, _name, type) => {
|
1050
|
+
const name = mapName(_name);
|
687
1051
|
|
688
|
-
|
689
|
-
return TYPES.function;
|
690
|
-
}
|
1052
|
+
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
691
1053
|
|
692
|
-
if (
|
693
|
-
|
694
|
-
|
1054
|
+
if (scope.locals[name]) return [
|
1055
|
+
...out,
|
1056
|
+
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1057
|
+
];
|
695
1058
|
|
696
|
-
if (
|
697
|
-
|
698
|
-
|
699
|
-
|
1059
|
+
if (globals[name]) return [
|
1060
|
+
...out,
|
1061
|
+
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
1062
|
+
];
|
1063
|
+
|
1064
|
+
// throw new Error('could not find var');
|
1065
|
+
};
|
700
1066
|
|
701
|
-
|
702
|
-
|
1067
|
+
const getNodeType = (scope, node) => {
|
1068
|
+
const inner = () => {
|
1069
|
+
if (node.type === 'Literal') {
|
1070
|
+
if (node.regex) return TYPES._regexp;
|
703
1071
|
|
704
|
-
|
705
|
-
|
706
|
-
if (name && name.startsWith('__')) {
|
707
|
-
const spl = name.slice(2).split('_');
|
1072
|
+
return TYPES[typeof node.value];
|
1073
|
+
}
|
708
1074
|
|
709
|
-
|
710
|
-
|
1075
|
+
if (isFuncType(node.type)) {
|
1076
|
+
return TYPES.function;
|
1077
|
+
}
|
711
1078
|
|
712
|
-
|
713
|
-
|
1079
|
+
if (node.type === 'Identifier') {
|
1080
|
+
return getType(scope, node.name);
|
714
1081
|
}
|
715
1082
|
|
716
|
-
|
717
|
-
|
718
|
-
const
|
1083
|
+
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1084
|
+
const name = node.callee.name;
|
1085
|
+
const func = funcs.find(x => x.name === name);
|
1086
|
+
|
1087
|
+
if (func) {
|
1088
|
+
// console.log(scope, func, func.returnType);
|
1089
|
+
if (func.returnType) return func.returnType;
|
1090
|
+
}
|
1091
|
+
|
1092
|
+
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1093
|
+
if (internalConstrs[name]) return internalConstrs[name].type;
|
1094
|
+
|
1095
|
+
if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
719
1096
|
|
720
|
-
|
721
|
-
|
1097
|
+
// presume
|
1098
|
+
// todo: warn here?
|
1099
|
+
return TYPES.number;
|
1100
|
+
|
1101
|
+
// let protoFunc;
|
1102
|
+
// // ident.func()
|
1103
|
+
// if (name && name.startsWith('__')) {
|
1104
|
+
// const spl = name.slice(2).split('_');
|
1105
|
+
|
1106
|
+
// const baseName = spl.slice(0, -1).join('_');
|
1107
|
+
// const baseType = getType(scope, baseName);
|
1108
|
+
|
1109
|
+
// const func = spl[spl.length - 1];
|
1110
|
+
// protoFunc = prototypeFuncs[baseType]?.[func];
|
1111
|
+
// }
|
1112
|
+
|
1113
|
+
// // literal.func()
|
1114
|
+
// if (!name && node.callee.type === 'MemberExpression') {
|
1115
|
+
// if (node.callee.object.regex) {
|
1116
|
+
// const funcName = node.callee.property.name;
|
1117
|
+
// return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
|
1118
|
+
// }
|
1119
|
+
|
1120
|
+
// const baseType = getNodeType(scope, node.callee.object);
|
1121
|
+
|
1122
|
+
// const func = node.callee.property.name;
|
1123
|
+
// protoFunc = prototypeFuncs[baseType]?.[func];
|
1124
|
+
// }
|
1125
|
+
|
1126
|
+
// if (protoFunc) return protoFunc.returnType;
|
722
1127
|
}
|
723
1128
|
|
724
|
-
if (
|
1129
|
+
if (node.type === 'ExpressionStatement') {
|
1130
|
+
return getNodeType(scope, node.expression);
|
1131
|
+
}
|
725
1132
|
|
726
|
-
|
727
|
-
|
1133
|
+
if (node.type === 'AssignmentExpression') {
|
1134
|
+
return getNodeType(scope, node.right);
|
1135
|
+
}
|
728
1136
|
|
729
|
-
|
730
|
-
|
731
|
-
|
1137
|
+
if (node.type === 'ArrayExpression') {
|
1138
|
+
return TYPES._array;
|
1139
|
+
}
|
732
1140
|
|
733
|
-
|
734
|
-
|
735
|
-
|
1141
|
+
if (node.type === 'BinaryExpression') {
|
1142
|
+
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1143
|
+
return TYPES.number;
|
1144
|
+
|
1145
|
+
// todo: string concat types
|
1146
|
+
// if (node.operator !== '+') return TYPES.number;
|
1147
|
+
// else return [
|
1148
|
+
// // if left is string
|
1149
|
+
// ...getNodeType(scope, node.left),
|
1150
|
+
// ...number(TYPES.string, Valtype.i32),
|
1151
|
+
// [ Opcodes.i32_eq ],
|
1152
|
+
|
1153
|
+
// // if right is string
|
1154
|
+
// ...getNodeType(scope, node.right),
|
1155
|
+
// ...number(TYPES.string, Valtype.i32),
|
1156
|
+
// [ Opcodes.i32_eq ],
|
1157
|
+
|
1158
|
+
// // if either are true
|
1159
|
+
// [ Opcodes.i32_or ],
|
1160
|
+
// ];
|
1161
|
+
}
|
736
1162
|
|
737
|
-
|
738
|
-
|
739
|
-
|
1163
|
+
if (node.type === 'UnaryExpression') {
|
1164
|
+
if (node.operator === '!') return TYPES.boolean;
|
1165
|
+
if (node.operator === 'void') return TYPES.undefined;
|
1166
|
+
if (node.operator === 'delete') return TYPES.boolean;
|
1167
|
+
if (node.operator === 'typeof') return TYPES.string;
|
740
1168
|
|
741
|
-
|
742
|
-
|
1169
|
+
return TYPES.number;
|
1170
|
+
}
|
743
1171
|
|
744
|
-
if (node.
|
745
|
-
|
1172
|
+
if (node.type === 'MemberExpression') {
|
1173
|
+
// hack: if something.length, number type
|
1174
|
+
if (node.property.name === 'length') return TYPES.number;
|
746
1175
|
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
if (node.operator === 'delete') return TYPES.boolean;
|
751
|
-
}
|
1176
|
+
// we cannot guess
|
1177
|
+
return TYPES.number;
|
1178
|
+
}
|
752
1179
|
|
753
|
-
|
754
|
-
const objectType = getNodeType(scope, node.object);
|
1180
|
+
if (scope.locals['#last_type']) return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
755
1181
|
|
756
|
-
|
757
|
-
|
1182
|
+
// presume
|
1183
|
+
// todo: warn here?
|
1184
|
+
return TYPES.number;
|
1185
|
+
};
|
758
1186
|
|
759
|
-
|
760
|
-
|
1187
|
+
const ret = inner();
|
1188
|
+
// console.trace(node, ret);
|
1189
|
+
if (typeof ret === 'number') return number(ret, Valtype.i32);
|
1190
|
+
return ret;
|
761
1191
|
};
|
762
1192
|
|
763
1193
|
const generateLiteral = (scope, decl, global, name) => {
|
764
1194
|
if (decl.value === null) return number(NULL);
|
765
1195
|
|
1196
|
+
if (decl.regex) {
|
1197
|
+
scope.regex[name] = decl.regex;
|
1198
|
+
return number(1);
|
1199
|
+
}
|
1200
|
+
|
766
1201
|
switch (typeof decl.value) {
|
767
1202
|
case 'number':
|
768
1203
|
return number(decl.value);
|
@@ -772,27 +1207,16 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
772
1207
|
return number(decl.value ? 1 : 0);
|
773
1208
|
|
774
1209
|
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
1210
|
const str = decl.value;
|
788
1211
|
const rawElements = new Array(str.length);
|
1212
|
+
let j = 0;
|
789
1213
|
for (let i = 0; i < str.length; i++) {
|
790
1214
|
rawElements[i] = str.charCodeAt(i);
|
791
1215
|
}
|
792
1216
|
|
793
1217
|
return makeArray(scope, {
|
794
1218
|
rawElements
|
795
|
-
}, global, name, false, 'i16');
|
1219
|
+
}, global, name, false, 'i16')[0];
|
796
1220
|
|
797
1221
|
default:
|
798
1222
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -802,7 +1226,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
802
1226
|
const countLeftover = wasm => {
|
803
1227
|
let count = 0, depth = 0;
|
804
1228
|
|
805
|
-
for (
|
1229
|
+
for (let i = 0; i < wasm.length; i++) {
|
1230
|
+
const inst = wasm[i];
|
806
1231
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
807
1232
|
if (inst[0] === Opcodes.if) count--;
|
808
1233
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -811,11 +1236,12 @@ const countLeftover = wasm => {
|
|
811
1236
|
if (inst[0] === Opcodes.end) depth--;
|
812
1237
|
|
813
1238
|
if (depth === 0)
|
814
|
-
if ([Opcodes.throw,
|
1239
|
+
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
815
1240
|
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
1241
|
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
1242
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
818
1243
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1244
|
+
else if (inst[0] === Opcodes.return) count = 0;
|
819
1245
|
else if (inst[0] === Opcodes.call) {
|
820
1246
|
let func = funcs.find(x => x.index === inst[1]);
|
821
1247
|
if (func) {
|
@@ -823,6 +1249,8 @@ const countLeftover = wasm => {
|
|
823
1249
|
} else count--;
|
824
1250
|
if (func) count += func.returns.length;
|
825
1251
|
} else count--;
|
1252
|
+
|
1253
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
826
1254
|
}
|
827
1255
|
|
828
1256
|
return count;
|
@@ -843,7 +1271,7 @@ const generateExp = (scope, decl) => {
|
|
843
1271
|
return out;
|
844
1272
|
};
|
845
1273
|
|
846
|
-
const
|
1274
|
+
const CTArrayUtil = {
|
847
1275
|
getLengthI32: pointer => [
|
848
1276
|
...number(0, Valtype.i32),
|
849
1277
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
@@ -869,6 +1297,32 @@ const arrayUtil = {
|
|
869
1297
|
]
|
870
1298
|
};
|
871
1299
|
|
1300
|
+
const RTArrayUtil = {
|
1301
|
+
getLengthI32: pointer => [
|
1302
|
+
...pointer,
|
1303
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
1304
|
+
],
|
1305
|
+
|
1306
|
+
getLength: pointer => [
|
1307
|
+
...pointer,
|
1308
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
1309
|
+
Opcodes.i32_from_u
|
1310
|
+
],
|
1311
|
+
|
1312
|
+
setLengthI32: (pointer, value) => [
|
1313
|
+
...pointer,
|
1314
|
+
...value,
|
1315
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
1316
|
+
],
|
1317
|
+
|
1318
|
+
setLength: (pointer, value) => [
|
1319
|
+
...pointer,
|
1320
|
+
...value,
|
1321
|
+
Opcodes.i32_to_u,
|
1322
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
1323
|
+
]
|
1324
|
+
};
|
1325
|
+
|
872
1326
|
const generateCall = (scope, decl, _global, _name) => {
|
873
1327
|
/* const callee = decl.callee;
|
874
1328
|
const args = decl.arguments;
|
@@ -898,95 +1352,166 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
898
1352
|
const lastInst = out[out.length - 1];
|
899
1353
|
if (lastInst && lastInst[0] === Opcodes.drop) {
|
900
1354
|
out.splice(out.length - 1, 1);
|
1355
|
+
|
1356
|
+
const finalStatement = parsed.body[parsed.body.length - 1];
|
1357
|
+
out.push(
|
1358
|
+
...getNodeType(scope, finalStatement),
|
1359
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1360
|
+
);
|
901
1361
|
} else if (countLeftover(out) === 0) {
|
902
1362
|
out.push(...number(UNDEFINED));
|
1363
|
+
out.push(
|
1364
|
+
...number(TYPES.undefined, Valtype.i32),
|
1365
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1366
|
+
);
|
903
1367
|
}
|
904
1368
|
|
1369
|
+
// if (lastInst && lastInst[0] === Opcodes.drop) {
|
1370
|
+
// out.splice(out.length - 1, 1);
|
1371
|
+
// } else if (countLeftover(out) === 0) {
|
1372
|
+
// out.push(...number(UNDEFINED));
|
1373
|
+
// }
|
1374
|
+
|
905
1375
|
return out;
|
906
1376
|
}
|
907
1377
|
|
908
|
-
let
|
909
|
-
let protoFunc, protoName, baseType, baseName = '$undeclared';
|
1378
|
+
let protoName, target;
|
910
1379
|
// ident.func()
|
911
1380
|
if (name && name.startsWith('__')) {
|
912
1381
|
const spl = name.slice(2).split('_');
|
913
1382
|
|
914
|
-
baseName = spl.slice(0, -1).join('_');
|
915
|
-
baseType = getType(scope, baseName);
|
916
|
-
|
917
1383
|
const func = spl[spl.length - 1];
|
918
|
-
protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
|
919
1384
|
protoName = func;
|
1385
|
+
|
1386
|
+
target = { ...decl.callee };
|
1387
|
+
target.name = spl.slice(0, -1).join('_');
|
920
1388
|
}
|
921
1389
|
|
922
1390
|
// literal.func()
|
923
1391
|
if (!name && decl.callee.type === 'MemberExpression') {
|
924
|
-
|
1392
|
+
// megahack for /regex/.func()
|
1393
|
+
if (decl.callee.object.regex) {
|
1394
|
+
const funcName = decl.callee.property.name;
|
1395
|
+
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1396
|
+
|
1397
|
+
funcIndex[func.name] = func.index;
|
1398
|
+
funcs.push(func);
|
1399
|
+
|
1400
|
+
return [
|
1401
|
+
// make string arg
|
1402
|
+
...generate(scope, decl.arguments[0]),
|
1403
|
+
|
1404
|
+
// call regex func
|
1405
|
+
Opcodes.i32_to_u,
|
1406
|
+
[ Opcodes.call, func.index ],
|
1407
|
+
Opcodes.i32_from_u,
|
1408
|
+
|
1409
|
+
...number(TYPES.boolean, Valtype.i32),
|
1410
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1411
|
+
];
|
1412
|
+
}
|
925
1413
|
|
926
1414
|
const func = decl.callee.property.name;
|
927
|
-
protoFunc = prototypeFuncs[baseType]?.[func] ?? Object.values(prototypeFuncs).map(x => x[func]).find(x => x);
|
928
1415
|
protoName = func;
|
929
1416
|
|
930
|
-
|
931
|
-
out.push([ Opcodes.drop ]);
|
1417
|
+
target = decl.callee.object;
|
932
1418
|
}
|
933
1419
|
|
934
|
-
if (
|
935
|
-
|
1420
|
+
// if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
|
1421
|
+
// const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
|
936
1422
|
|
937
|
-
|
1423
|
+
// funcIndex[func.name] = func.index;
|
1424
|
+
// funcs.push(func);
|
938
1425
|
|
939
|
-
|
940
|
-
|
941
|
-
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
1426
|
+
// return [
|
1427
|
+
// generate(scope, decl.callee.object)
|
942
1428
|
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
1429
|
+
// // call regex func
|
1430
|
+
// [ Opcodes.call, func.index ],
|
1431
|
+
// Opcodes.i32_from_u
|
1432
|
+
// ];
|
1433
|
+
// }
|
948
1434
|
|
949
|
-
|
1435
|
+
if (protoName) {
|
1436
|
+
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1437
|
+
const f = prototypeFuncs[x][protoName];
|
1438
|
+
if (f) acc[x] = f;
|
1439
|
+
return acc;
|
1440
|
+
}, {});
|
950
1441
|
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
1442
|
+
// no prototype function candidates, ignore
|
1443
|
+
if (Object.keys(protoCands).length > 0) {
|
1444
|
+
// use local for cached i32 length as commonly used
|
1445
|
+
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1446
|
+
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1447
|
+
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
957
1448
|
|
958
|
-
|
959
|
-
|
960
|
-
|
1449
|
+
// TODO: long-term, prototypes should be their individual separate funcs
|
1450
|
+
|
1451
|
+
let lengthI32CacheUsed = false;
|
1452
|
+
const protoBC = {};
|
1453
|
+
for (const x in protoCands) {
|
1454
|
+
const protoFunc = protoCands[x];
|
1455
|
+
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
1456
|
+
protoBC[x] = [
|
1457
|
+
...RTArrayUtil.getLength(getPointer),
|
1458
|
+
|
1459
|
+
...number(TYPES.number, Valtype.i32),
|
1460
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
1461
|
+
];
|
1462
|
+
continue;
|
1463
|
+
}
|
961
1464
|
|
962
|
-
|
1465
|
+
// const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1466
|
+
// const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1467
|
+
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1468
|
+
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1469
|
+
|
1470
|
+
const protoOut = protoFunc(getPointer, {
|
1471
|
+
getCachedI32: () => {
|
1472
|
+
lengthI32CacheUsed = true;
|
1473
|
+
return [ [ Opcodes.local_get, lengthLocal ] ];
|
1474
|
+
},
|
1475
|
+
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1476
|
+
get: () => RTArrayUtil.getLength(getPointer),
|
1477
|
+
getI32: () => RTArrayUtil.getLengthI32(getPointer),
|
1478
|
+
set: value => RTArrayUtil.setLength(getPointer, value),
|
1479
|
+
setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
|
1480
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1481
|
+
return makeArray(scope, {
|
1482
|
+
rawElements: new Array(length)
|
1483
|
+
}, _global, _name, true, itemType);
|
1484
|
+
});
|
1485
|
+
|
1486
|
+
protoBC[x] = [
|
1487
|
+
[ Opcodes.block, valtypeBinary ],
|
1488
|
+
...protoOut,
|
1489
|
+
|
1490
|
+
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1491
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
|
1492
|
+
[ Opcodes.end ]
|
1493
|
+
];
|
1494
|
+
}
|
963
1495
|
|
964
|
-
|
1496
|
+
return [
|
1497
|
+
...generate(scope, target),
|
965
1498
|
|
966
|
-
|
967
|
-
|
1499
|
+
Opcodes.i32_to_u,
|
1500
|
+
[ Opcodes.local_set, pointerLocal ],
|
968
1501
|
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
983
|
-
const out = makeArray(scope, {
|
984
|
-
rawElements: new Array(length)
|
985
|
-
}, _global, _name, true, itemType);
|
986
|
-
return [ out, arrays.get(_name ?? '$undeclared') ];
|
987
|
-
}),
|
988
|
-
[ Opcodes.end ]
|
989
|
-
];
|
1502
|
+
...(!lengthI32CacheUsed ? [] : [
|
1503
|
+
...RTArrayUtil.getLengthI32(getPointer),
|
1504
|
+
[ Opcodes.local_set, lengthLocal ],
|
1505
|
+
]),
|
1506
|
+
|
1507
|
+
...typeSwitch(scope, getNodeType(scope, target), {
|
1508
|
+
...protoBC,
|
1509
|
+
|
1510
|
+
// TODO: error better
|
1511
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1512
|
+
}, valtypeBinary),
|
1513
|
+
];
|
1514
|
+
}
|
990
1515
|
}
|
991
1516
|
|
992
1517
|
// TODO: only allows callee as literal
|
@@ -1030,34 +1555,50 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1030
1555
|
|
1031
1556
|
const func = funcs.find(x => x.index === idx);
|
1032
1557
|
|
1558
|
+
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1559
|
+
const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
|
1560
|
+
|
1033
1561
|
let args = decl.arguments;
|
1034
|
-
if (func && args.length <
|
1562
|
+
if (func && args.length < paramCount) {
|
1035
1563
|
// too little args, push undefineds
|
1036
|
-
args = args.concat(new Array(
|
1564
|
+
args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
|
1037
1565
|
}
|
1038
1566
|
|
1039
|
-
if (func && args.length >
|
1567
|
+
if (func && args.length > paramCount) {
|
1040
1568
|
// too many args, slice extras off
|
1041
|
-
args = args.slice(0,
|
1569
|
+
args = args.slice(0, paramCount);
|
1042
1570
|
}
|
1043
1571
|
|
1044
|
-
if (func && func.memory) scope.memory = true;
|
1045
1572
|
if (func && func.throws) scope.throws = true;
|
1046
1573
|
|
1574
|
+
let out = [];
|
1047
1575
|
for (const arg of args) {
|
1048
|
-
out.
|
1576
|
+
out = out.concat(generate(scope, arg));
|
1577
|
+
if (userFunc) out = out.concat(getNodeType(scope, arg));
|
1049
1578
|
}
|
1050
1579
|
|
1051
1580
|
out.push([ Opcodes.call, idx ]);
|
1052
1581
|
|
1582
|
+
if (!userFunc) {
|
1583
|
+
// let type;
|
1584
|
+
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1585
|
+
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
1586
|
+
// if (importedFuncs[name] && importedFuncs[]) type =
|
1587
|
+
|
1588
|
+
// if (type) out.push(
|
1589
|
+
// ...number(type, Valtype.i32),
|
1590
|
+
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1591
|
+
// );
|
1592
|
+
} else out.push([ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]);
|
1593
|
+
|
1053
1594
|
return out;
|
1054
1595
|
};
|
1055
1596
|
|
1056
1597
|
const generateNew = (scope, decl, _global, _name) => {
|
1057
1598
|
// hack: basically treat this as a normal call for builtins for now
|
1058
1599
|
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)})`);
|
1600
|
+
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1601
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1061
1602
|
|
1062
1603
|
return generateCall(scope, decl, _global, _name);
|
1063
1604
|
};
|
@@ -1073,14 +1614,67 @@ const unhackName = name => {
|
|
1073
1614
|
return name;
|
1074
1615
|
};
|
1075
1616
|
|
1617
|
+
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1618
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1619
|
+
|
1620
|
+
const out = [
|
1621
|
+
...type,
|
1622
|
+
[ Opcodes.local_set, tmp ],
|
1623
|
+
[ Opcodes.block, returns ]
|
1624
|
+
];
|
1625
|
+
|
1626
|
+
// todo: use br_table?
|
1627
|
+
|
1628
|
+
for (const x in bc) {
|
1629
|
+
if (x === 'default') continue;
|
1630
|
+
|
1631
|
+
// if type == x
|
1632
|
+
out.push([ Opcodes.local_get, tmp ]);
|
1633
|
+
out.push(...number(x, Valtype.i32));
|
1634
|
+
out.push([ Opcodes.i32_eq ]);
|
1635
|
+
|
1636
|
+
out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
|
1637
|
+
out.push(...bc[x]);
|
1638
|
+
out.push([ Opcodes.br, 1 ]);
|
1639
|
+
out.push([ Opcodes.end ]);
|
1640
|
+
}
|
1641
|
+
|
1642
|
+
// default
|
1643
|
+
if (bc.default) out.push(...bc.default);
|
1644
|
+
else if (returns !== Blocktype.void) out.push(...number(0, returns));
|
1645
|
+
|
1646
|
+
out.push([ Opcodes.end, 'TYPESWITCH_end' ]);
|
1647
|
+
|
1648
|
+
return out;
|
1649
|
+
};
|
1650
|
+
|
1651
|
+
const allocVar = (scope, name, global = false) => {
|
1652
|
+
const target = global ? globals : scope.locals;
|
1653
|
+
|
1654
|
+
// already declared
|
1655
|
+
if (target[name]) {
|
1656
|
+
// parser should catch this but sanity check anyway
|
1657
|
+
// if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1658
|
+
|
1659
|
+
return target[name].idx;
|
1660
|
+
}
|
1661
|
+
|
1662
|
+
let idx = global ? globalInd++ : scope.localInd++;
|
1663
|
+
target[name] = { idx, type: valtypeBinary };
|
1664
|
+
|
1665
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
1666
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
1667
|
+
|
1668
|
+
return idx;
|
1669
|
+
};
|
1670
|
+
|
1076
1671
|
const generateVar = (scope, decl) => {
|
1077
|
-
|
1672
|
+
let out = [];
|
1078
1673
|
|
1079
1674
|
const topLevel = scope.name === 'main';
|
1080
1675
|
|
1081
1676
|
// 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;
|
1677
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1084
1678
|
|
1085
1679
|
for (const x of decl.declarations) {
|
1086
1680
|
const name = mapName(x.id.name);
|
@@ -1100,46 +1694,16 @@ const generateVar = (scope, decl) => {
|
|
1100
1694
|
continue; // always ignore
|
1101
1695
|
}
|
1102
1696
|
|
1103
|
-
let idx;
|
1104
|
-
// already declared
|
1105
|
-
if (target[name]) {
|
1106
|
-
// parser should catch this but sanity check anyway
|
1107
|
-
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
1108
|
-
|
1109
|
-
idx = target[name].idx;
|
1110
|
-
} else {
|
1111
|
-
idx = global ? globalInd++ : scope.localInd++;
|
1112
|
-
target[name] = { idx, type: valtypeBinary };
|
1113
|
-
}
|
1114
|
-
|
1115
|
-
typeStates[name] = x.init ? getNodeType(scope, x.init) : TYPES.undefined;
|
1116
|
-
|
1117
|
-
// x.init ??= DEFAULT_VALUE;
|
1697
|
+
let idx = allocVar(scope, name, global);
|
1118
1698
|
if (x.init) {
|
1119
|
-
out.
|
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
|
-
}
|
1699
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1140
1700
|
|
1141
1701
|
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1702
|
+
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1142
1703
|
}
|
1704
|
+
|
1705
|
+
// hack: this follows spec properly but is mostly unneeded 😅
|
1706
|
+
// out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
|
1143
1707
|
}
|
1144
1708
|
|
1145
1709
|
return out;
|
@@ -1165,8 +1729,6 @@ const generateAssign = (scope, decl) => {
|
|
1165
1729
|
const name = decl.left.object.name;
|
1166
1730
|
const pointer = arrays.get(name);
|
1167
1731
|
|
1168
|
-
scope.memory = true;
|
1169
|
-
|
1170
1732
|
const aotPointer = pointer != null;
|
1171
1733
|
|
1172
1734
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1177,7 +1739,7 @@ const generateAssign = (scope, decl) => {
|
|
1177
1739
|
Opcodes.i32_to_u
|
1178
1740
|
]),
|
1179
1741
|
|
1180
|
-
...generate(scope, decl.right
|
1742
|
+
...generate(scope, decl.right),
|
1181
1743
|
[ Opcodes.local_tee, newValueTmp ],
|
1182
1744
|
|
1183
1745
|
Opcodes.i32_to_u,
|
@@ -1187,13 +1749,76 @@ const generateAssign = (scope, decl) => {
|
|
1187
1749
|
];
|
1188
1750
|
}
|
1189
1751
|
|
1752
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1753
|
+
|
1754
|
+
// arr[i]
|
1755
|
+
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1756
|
+
const name = decl.left.object.name;
|
1757
|
+
const pointer = arrays.get(name);
|
1758
|
+
|
1759
|
+
const aotPointer = pointer != null;
|
1760
|
+
|
1761
|
+
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1762
|
+
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1763
|
+
|
1764
|
+
return [
|
1765
|
+
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
1766
|
+
[TYPES._array]: [
|
1767
|
+
...(aotPointer ? [] : [
|
1768
|
+
...generate(scope, decl.left.object),
|
1769
|
+
Opcodes.i32_to_u
|
1770
|
+
]),
|
1771
|
+
|
1772
|
+
// get index as valtype
|
1773
|
+
...generate(scope, decl.left.property),
|
1774
|
+
Opcodes.i32_to_u,
|
1775
|
+
|
1776
|
+
// turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
1777
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
1778
|
+
[ Opcodes.i32_mul ],
|
1779
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1780
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1781
|
+
|
1782
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
1783
|
+
[ Opcodes.local_get, pointerTmp ],
|
1784
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1785
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
|
1786
|
+
[ Opcodes.local_tee, newValueTmp ],
|
1787
|
+
|
1788
|
+
[ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1789
|
+
],
|
1790
|
+
|
1791
|
+
default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
|
1792
|
+
|
1793
|
+
// [TYPES.string]: [
|
1794
|
+
// // turn into byte offset by * sizeof i16
|
1795
|
+
// ...number(ValtypeSize.i16, Valtype.i32),
|
1796
|
+
// [ Opcodes.i32_mul ],
|
1797
|
+
// ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1798
|
+
// ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1799
|
+
|
1800
|
+
// ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
1801
|
+
// [ Opcodes.local_get, pointerTmp ],
|
1802
|
+
// [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1803
|
+
// ], generate(scope, decl.right), number(TYPES.string, Valtype.i32), getNodeType(scope, decl.right))),
|
1804
|
+
// [ Opcodes.local_tee, newValueTmp ],
|
1805
|
+
|
1806
|
+
// Opcodes.i32_to_u,
|
1807
|
+
// [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1808
|
+
// ]
|
1809
|
+
}, Blocktype.void),
|
1810
|
+
|
1811
|
+
[ Opcodes.local_get, newValueTmp ]
|
1812
|
+
];
|
1813
|
+
}
|
1814
|
+
|
1190
1815
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1191
1816
|
|
1192
1817
|
if (local === undefined) {
|
1193
|
-
// todo: this should be a
|
1818
|
+
// todo: this should be a sloppy mode only thing
|
1194
1819
|
|
1195
1820
|
// only allow = for this
|
1196
|
-
if (
|
1821
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1197
1822
|
|
1198
1823
|
if (builtinVars[name]) {
|
1199
1824
|
// just return rhs (eg `NaN = 2`)
|
@@ -1202,25 +1827,53 @@ const generateAssign = (scope, decl) => {
|
|
1202
1827
|
|
1203
1828
|
// set global and return (eg a = 2)
|
1204
1829
|
return [
|
1205
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1830
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1206
1831
|
[ Opcodes.global_get, globals[name].idx ]
|
1207
1832
|
];
|
1208
1833
|
}
|
1209
1834
|
|
1210
|
-
if (
|
1211
|
-
typeStates[name] = getNodeType(scope, decl.right);
|
1212
|
-
|
1835
|
+
if (op === '=') {
|
1213
1836
|
return [
|
1214
1837
|
...generate(scope, decl.right, isGlobal, name),
|
1215
1838
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1216
|
-
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1839
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1840
|
+
|
1841
|
+
...setType(scope, name, getNodeType(scope, decl.right))
|
1842
|
+
];
|
1843
|
+
}
|
1844
|
+
|
1845
|
+
if (op === '||' || op === '&&' || op === '??') {
|
1846
|
+
// todo: is this needed?
|
1847
|
+
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
1848
|
+
// instead, left @ (left = right)
|
1849
|
+
// eg, x &&= y ~= x && (x = y)
|
1850
|
+
|
1851
|
+
return [
|
1852
|
+
...performOp(scope, op, [
|
1853
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1854
|
+
], [
|
1855
|
+
...generate(scope, decl.right),
|
1856
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1857
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1858
|
+
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1859
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1860
|
+
|
1861
|
+
[ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
|
1862
|
+
// hack: type is idx+1
|
1863
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1217
1864
|
];
|
1218
1865
|
}
|
1219
1866
|
|
1220
1867
|
return [
|
1221
|
-
...performOp(scope,
|
1868
|
+
...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
1869
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1223
|
-
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1870
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1871
|
+
|
1872
|
+
// todo: string concat types
|
1873
|
+
|
1874
|
+
// hack: type is idx+1
|
1875
|
+
...number(TYPES.number, Valtype.i32),
|
1876
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
1224
1877
|
];
|
1225
1878
|
};
|
1226
1879
|
|
@@ -1245,13 +1898,14 @@ const generateUnary = (scope, decl) => {
|
|
1245
1898
|
|
1246
1899
|
case '!':
|
1247
1900
|
// !=
|
1248
|
-
return falsy(scope, generate(scope, decl.argument));
|
1901
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
|
1249
1902
|
|
1250
1903
|
case '~':
|
1904
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1251
1905
|
return [
|
1252
1906
|
...generate(scope, decl.argument),
|
1253
1907
|
Opcodes.i32_to,
|
1254
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1908
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1255
1909
|
[ Opcodes.i32_xor ],
|
1256
1910
|
Opcodes.i32_from
|
1257
1911
|
];
|
@@ -1289,11 +1943,16 @@ const generateUnary = (scope, decl) => {
|
|
1289
1943
|
return out;
|
1290
1944
|
|
1291
1945
|
case 'typeof':
|
1292
|
-
|
1946
|
+
return typeSwitch(scope, getNodeType(scope, decl.argument), {
|
1947
|
+
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
1948
|
+
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
1949
|
+
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
1950
|
+
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1951
|
+
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1293
1952
|
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1953
|
+
// object and internal types
|
1954
|
+
default: makeString(scope, 'object', false, '#typeof_result'),
|
1955
|
+
});
|
1297
1956
|
|
1298
1957
|
default:
|
1299
1958
|
return todo(`unary operator ${decl.operator} not implemented yet`);
|
@@ -1332,9 +1991,9 @@ const generateUpdate = (scope, decl) => {
|
|
1332
1991
|
};
|
1333
1992
|
|
1334
1993
|
const generateIf = (scope, decl) => {
|
1335
|
-
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
1994
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
|
1336
1995
|
|
1337
|
-
out.push(
|
1996
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
1338
1997
|
depth.push('if');
|
1339
1998
|
|
1340
1999
|
const consOut = generate(scope, decl.consequent);
|
@@ -1356,16 +2015,28 @@ const generateIf = (scope, decl) => {
|
|
1356
2015
|
};
|
1357
2016
|
|
1358
2017
|
const generateConditional = (scope, decl) => {
|
1359
|
-
const out =
|
2018
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
|
1360
2019
|
|
1361
|
-
out.push(
|
2020
|
+
out.push([ Opcodes.if, valtypeBinary ]);
|
1362
2021
|
depth.push('if');
|
1363
2022
|
|
1364
2023
|
out.push(...generate(scope, decl.consequent));
|
1365
2024
|
|
2025
|
+
// note type
|
2026
|
+
out.push(
|
2027
|
+
...getNodeType(scope, decl.consequent),
|
2028
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
2029
|
+
);
|
2030
|
+
|
1366
2031
|
out.push([ Opcodes.else ]);
|
1367
2032
|
out.push(...generate(scope, decl.alternate));
|
1368
2033
|
|
2034
|
+
// note type
|
2035
|
+
out.push(
|
2036
|
+
...getNodeType(scope, decl.alternate),
|
2037
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
2038
|
+
);
|
2039
|
+
|
1369
2040
|
out.push([ Opcodes.end ]);
|
1370
2041
|
depth.pop();
|
1371
2042
|
|
@@ -1422,9 +2093,148 @@ const generateWhile = (scope, decl) => {
|
|
1422
2093
|
return out;
|
1423
2094
|
};
|
1424
2095
|
|
2096
|
+
const generateForOf = (scope, decl) => {
|
2097
|
+
const out = [];
|
2098
|
+
|
2099
|
+
// todo: for of inside for of might fuck up?
|
2100
|
+
const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
|
2101
|
+
const length = localTmp(scope, 'forof_length', Valtype.i32);
|
2102
|
+
const counter = localTmp(scope, 'forof_counter', Valtype.i32);
|
2103
|
+
|
2104
|
+
out.push(
|
2105
|
+
// set pointer as right
|
2106
|
+
...generate(scope, decl.right),
|
2107
|
+
Opcodes.i32_to_u,
|
2108
|
+
[ Opcodes.local_set, pointer ],
|
2109
|
+
|
2110
|
+
// set counter as 0 (could be already used)
|
2111
|
+
...number(0, Valtype.i32),
|
2112
|
+
[ Opcodes.local_set, counter ],
|
2113
|
+
|
2114
|
+
// get length
|
2115
|
+
[ Opcodes.local_get, pointer ],
|
2116
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2117
|
+
[ Opcodes.local_set, length ]
|
2118
|
+
);
|
2119
|
+
|
2120
|
+
depth.push('forof');
|
2121
|
+
|
2122
|
+
// setup local for left
|
2123
|
+
generate(scope, decl.left);
|
2124
|
+
|
2125
|
+
const leftName = decl.left.declarations[0].id.name;
|
2126
|
+
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2127
|
+
|
2128
|
+
depth.push('block');
|
2129
|
+
depth.push('block');
|
2130
|
+
|
2131
|
+
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2132
|
+
// hack: this is naughty and will break things!
|
2133
|
+
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2134
|
+
if (pages.hasString) {
|
2135
|
+
0, [ newOut, newPointer ] = makeArray(scope, {
|
2136
|
+
rawElements: new Array(1)
|
2137
|
+
}, isGlobal, leftName, true, 'i16');
|
2138
|
+
}
|
2139
|
+
|
2140
|
+
// set type for local
|
2141
|
+
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2142
|
+
[TYPES._array]: [
|
2143
|
+
...setType(scope, leftName, TYPES.number),
|
2144
|
+
|
2145
|
+
[ Opcodes.loop, Blocktype.void ],
|
2146
|
+
|
2147
|
+
[ Opcodes.local_get, pointer ],
|
2148
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
2149
|
+
|
2150
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2151
|
+
|
2152
|
+
[ Opcodes.block, Blocktype.void ],
|
2153
|
+
[ Opcodes.block, Blocktype.void ],
|
2154
|
+
...generate(scope, decl.body),
|
2155
|
+
[ Opcodes.end ],
|
2156
|
+
|
2157
|
+
// increment iter pointer by valtype size
|
2158
|
+
[ Opcodes.local_get, pointer ],
|
2159
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
2160
|
+
[ Opcodes.i32_add ],
|
2161
|
+
[ Opcodes.local_set, pointer ],
|
2162
|
+
|
2163
|
+
// increment counter by 1
|
2164
|
+
[ Opcodes.local_get, counter ],
|
2165
|
+
...number(1, Valtype.i32),
|
2166
|
+
[ Opcodes.i32_add ],
|
2167
|
+
[ Opcodes.local_tee, counter ],
|
2168
|
+
|
2169
|
+
// loop if counter != length
|
2170
|
+
[ Opcodes.local_get, length ],
|
2171
|
+
[ Opcodes.i32_ne ],
|
2172
|
+
[ Opcodes.br_if, 1 ],
|
2173
|
+
|
2174
|
+
[ Opcodes.end ],
|
2175
|
+
[ Opcodes.end ]
|
2176
|
+
],
|
2177
|
+
[TYPES.string]: [
|
2178
|
+
...setType(scope, leftName, TYPES.string),
|
2179
|
+
|
2180
|
+
[ Opcodes.loop, Blocktype.void ],
|
2181
|
+
|
2182
|
+
// setup new/out array
|
2183
|
+
...newOut,
|
2184
|
+
[ Opcodes.drop ],
|
2185
|
+
|
2186
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2187
|
+
|
2188
|
+
// load current string ind {arg}
|
2189
|
+
[ Opcodes.local_get, pointer ],
|
2190
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
2191
|
+
|
2192
|
+
// store to new string ind 0
|
2193
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2194
|
+
|
2195
|
+
// return new string (page)
|
2196
|
+
...number(newPointer),
|
2197
|
+
|
2198
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2199
|
+
|
2200
|
+
[ Opcodes.block, Blocktype.void ],
|
2201
|
+
[ Opcodes.block, Blocktype.void ],
|
2202
|
+
...generate(scope, decl.body),
|
2203
|
+
[ Opcodes.end ],
|
2204
|
+
|
2205
|
+
// increment iter pointer by valtype size
|
2206
|
+
[ Opcodes.local_get, pointer ],
|
2207
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
2208
|
+
[ Opcodes.i32_add ],
|
2209
|
+
[ Opcodes.local_set, pointer ],
|
2210
|
+
|
2211
|
+
// increment counter by 1
|
2212
|
+
[ Opcodes.local_get, counter ],
|
2213
|
+
...number(1, Valtype.i32),
|
2214
|
+
[ Opcodes.i32_add ],
|
2215
|
+
[ Opcodes.local_tee, counter ],
|
2216
|
+
|
2217
|
+
// loop if counter != length
|
2218
|
+
[ Opcodes.local_get, length ],
|
2219
|
+
[ Opcodes.i32_ne ],
|
2220
|
+
[ Opcodes.br_if, 1 ],
|
2221
|
+
|
2222
|
+
[ Opcodes.end ],
|
2223
|
+
[ Opcodes.end ]
|
2224
|
+
],
|
2225
|
+
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2226
|
+
}, Blocktype.void));
|
2227
|
+
|
2228
|
+
depth.pop();
|
2229
|
+
depth.pop();
|
2230
|
+
depth.pop();
|
2231
|
+
|
2232
|
+
return out;
|
2233
|
+
};
|
2234
|
+
|
1425
2235
|
const getNearestLoop = () => {
|
1426
2236
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1427
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
2237
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1428
2238
|
}
|
1429
2239
|
|
1430
2240
|
return -1;
|
@@ -1508,13 +2318,25 @@ const generateAssignPat = (scope, decl) => {
|
|
1508
2318
|
};
|
1509
2319
|
|
1510
2320
|
let pages = new Map();
|
1511
|
-
const allocPage = reason => {
|
1512
|
-
if (pages.has(reason)) return pages.get(reason);
|
2321
|
+
const allocPage = (reason, type) => {
|
2322
|
+
if (pages.has(reason)) return pages.get(reason).ind;
|
2323
|
+
|
2324
|
+
if (reason.startsWith('array:')) pages.hasArray = true;
|
2325
|
+
if (reason.startsWith('string:')) pages.hasString = true;
|
2326
|
+
|
2327
|
+
const ind = pages.size;
|
2328
|
+
pages.set(reason, { ind, type });
|
2329
|
+
|
2330
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2331
|
+
|
2332
|
+
return ind;
|
2333
|
+
};
|
1513
2334
|
|
1514
|
-
|
1515
|
-
pages.
|
2335
|
+
const freePage = reason => {
|
2336
|
+
const { ind } = pages.get(reason);
|
2337
|
+
pages.delete(reason);
|
1516
2338
|
|
1517
|
-
if (
|
2339
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1518
2340
|
|
1519
2341
|
return ind;
|
1520
2342
|
};
|
@@ -1528,7 +2350,7 @@ const itemTypeToValtype = {
|
|
1528
2350
|
i16: 'i32'
|
1529
2351
|
};
|
1530
2352
|
|
1531
|
-
const
|
2353
|
+
const StoreOps = {
|
1532
2354
|
i32: Opcodes.i32_store,
|
1533
2355
|
i64: Opcodes.i64_store,
|
1534
2356
|
f64: Opcodes.f64_store,
|
@@ -1537,13 +2359,32 @@ const storeOps = {
|
|
1537
2359
|
i16: Opcodes.i32_store16
|
1538
2360
|
};
|
1539
2361
|
|
2362
|
+
let data = [];
|
2363
|
+
|
2364
|
+
const compileBytes = (val, itemType, signed = true) => {
|
2365
|
+
// todo: this is a mess and needs confirming / ????
|
2366
|
+
switch (itemType) {
|
2367
|
+
case 'i8': return [ val % 256 ];
|
2368
|
+
case 'i16': return [ val % 256, Math.floor(val / 256) ];
|
2369
|
+
|
2370
|
+
case 'i32':
|
2371
|
+
case 'i64':
|
2372
|
+
return enforceFourBytes(signedLEB128(val));
|
2373
|
+
|
2374
|
+
case 'f64': return ieee754_binary64(val);
|
2375
|
+
}
|
2376
|
+
};
|
2377
|
+
|
1540
2378
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1541
2379
|
const out = [];
|
1542
2380
|
|
2381
|
+
let firstAssign = false;
|
1543
2382
|
if (!arrays.has(name) || name === '$undeclared') {
|
2383
|
+
firstAssign = true;
|
2384
|
+
|
1544
2385
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1545
2386
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1546
|
-
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}
|
2387
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
1547
2388
|
}
|
1548
2389
|
|
1549
2390
|
const pointer = arrays.get(name);
|
@@ -1551,8 +2392,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1551
2392
|
const useRawElements = !!decl.rawElements;
|
1552
2393
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1553
2394
|
|
2395
|
+
const valtype = itemTypeToValtype[itemType];
|
1554
2396
|
const length = elements.length;
|
1555
2397
|
|
2398
|
+
if (firstAssign && useRawElements) {
|
2399
|
+
let bytes = compileBytes(length, 'i32');
|
2400
|
+
|
2401
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2402
|
+
if (elements[i] == null) continue;
|
2403
|
+
|
2404
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2405
|
+
}
|
2406
|
+
|
2407
|
+
data.push({
|
2408
|
+
offset: pointer,
|
2409
|
+
bytes
|
2410
|
+
});
|
2411
|
+
|
2412
|
+
// local value as pointer
|
2413
|
+
out.push(...number(pointer));
|
2414
|
+
|
2415
|
+
return [ out, pointer ];
|
2416
|
+
}
|
2417
|
+
|
1556
2418
|
// store length as 0th array
|
1557
2419
|
out.push(
|
1558
2420
|
...number(0, Valtype.i32),
|
@@ -1560,8 +2422,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1560
2422
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1561
2423
|
);
|
1562
2424
|
|
1563
|
-
const storeOp =
|
1564
|
-
const valtype = itemTypeToValtype[itemType];
|
2425
|
+
const storeOp = StoreOps[itemType];
|
1565
2426
|
|
1566
2427
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1567
2428
|
if (elements[i] == null) continue;
|
@@ -1576,30 +2437,34 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1576
2437
|
// local value as pointer
|
1577
2438
|
out.push(...number(pointer));
|
1578
2439
|
|
1579
|
-
|
2440
|
+
return [ out, pointer ];
|
2441
|
+
};
|
1580
2442
|
|
1581
|
-
|
2443
|
+
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2444
|
+
const rawElements = new Array(str.length);
|
2445
|
+
for (let i = 0; i < str.length; i++) {
|
2446
|
+
rawElements[i] = str.charCodeAt(i);
|
2447
|
+
}
|
2448
|
+
|
2449
|
+
return makeArray(scope, {
|
2450
|
+
rawElements
|
2451
|
+
}, global, name, false, 'i16')[0];
|
1582
2452
|
};
|
1583
2453
|
|
1584
2454
|
let arrays = new Map();
|
1585
2455
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1586
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype);
|
2456
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1587
2457
|
};
|
1588
2458
|
|
1589
2459
|
export const generateMember = (scope, decl, _global, _name) => {
|
1590
|
-
const
|
2460
|
+
const name = decl.object.name;
|
2461
|
+
const pointer = arrays.get(name);
|
2462
|
+
|
2463
|
+
const aotPointer = pointer != null;
|
1591
2464
|
|
1592
2465
|
// hack: .length
|
1593
2466
|
if (decl.property.name === 'length') {
|
1594
2467
|
// 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
2468
|
return [
|
1604
2469
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1605
2470
|
...generate(scope, decl.object),
|
@@ -1611,18 +2476,17 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1611
2476
|
];
|
1612
2477
|
}
|
1613
2478
|
|
1614
|
-
//
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
const aotPointer = pointer != null;
|
2479
|
+
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2480
|
+
// hack: this is naughty and will break things!
|
2481
|
+
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2482
|
+
if (pages.hasString) {
|
2483
|
+
0, [ newOut, newPointer ] = makeArray(scope, {
|
2484
|
+
rawElements: new Array(1)
|
2485
|
+
}, _global, _name, true, 'i16');
|
2486
|
+
}
|
1623
2487
|
|
1624
|
-
|
1625
|
-
|
2488
|
+
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2489
|
+
[TYPES._array]: [
|
1626
2490
|
// get index as valtype
|
1627
2491
|
...generate(scope, decl.property),
|
1628
2492
|
|
@@ -1638,45 +2502,46 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1638
2502
|
]),
|
1639
2503
|
|
1640
2504
|
// read from memory
|
1641
|
-
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1642
|
-
];
|
1643
|
-
}
|
2505
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
1644
2506
|
|
1645
|
-
|
2507
|
+
...number(TYPES.number, Valtype.i32),
|
2508
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
2509
|
+
],
|
1646
2510
|
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
2511
|
+
[TYPES.string]: [
|
2512
|
+
// setup new/out array
|
2513
|
+
...newOut,
|
2514
|
+
[ Opcodes.drop ],
|
1651
2515
|
|
1652
|
-
|
1653
|
-
// setup new/out array
|
1654
|
-
...newOut,
|
1655
|
-
[ Opcodes.drop ],
|
2516
|
+
...number(0, Valtype.i32), // base 0 for store later
|
1656
2517
|
|
1657
|
-
|
2518
|
+
...generate(scope, decl.property),
|
1658
2519
|
|
1659
|
-
|
2520
|
+
Opcodes.i32_to_u,
|
2521
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
2522
|
+
[ Opcodes.i32_mul ],
|
1660
2523
|
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
2524
|
+
...(aotPointer ? [] : [
|
2525
|
+
...generate(scope, decl.object),
|
2526
|
+
Opcodes.i32_to_u,
|
2527
|
+
[ Opcodes.i32_add ]
|
2528
|
+
]),
|
1664
2529
|
|
1665
|
-
|
1666
|
-
|
1667
|
-
Opcodes.i32_to_u,
|
1668
|
-
[ Opcodes.i32_add ]
|
1669
|
-
]),
|
2530
|
+
// load current string ind {arg}
|
2531
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
1670
2532
|
|
1671
|
-
|
1672
|
-
|
2533
|
+
// store to new string ind 0
|
2534
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
1673
2535
|
|
1674
|
-
|
1675
|
-
|
2536
|
+
// return new string (page)
|
2537
|
+
...number(newPointer),
|
1676
2538
|
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
2539
|
+
...number(TYPES.string, Valtype.i32),
|
2540
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
2541
|
+
],
|
2542
|
+
|
2543
|
+
default: [ [ Opcodes.unreachable ] ]
|
2544
|
+
});
|
1680
2545
|
};
|
1681
2546
|
|
1682
2547
|
const randId = () => Math.random().toString(16).slice(0, -4);
|
@@ -1728,15 +2593,14 @@ const generateFunc = (scope, decl) => {
|
|
1728
2593
|
const innerScope = {
|
1729
2594
|
locals: {},
|
1730
2595
|
localInd: 0,
|
1731
|
-
|
1732
|
-
|
2596
|
+
// value, type
|
2597
|
+
returns: [ valtypeBinary, Valtype.i32 ],
|
1733
2598
|
throws: false,
|
1734
2599
|
name
|
1735
2600
|
};
|
1736
2601
|
|
1737
2602
|
for (let i = 0; i < params.length; i++) {
|
1738
|
-
|
1739
|
-
innerScope.locals[param] = { idx: innerScope.localInd++, type: valtypeBinary };
|
2603
|
+
allocVar(innerScope, params[i], false);
|
1740
2604
|
}
|
1741
2605
|
|
1742
2606
|
let body = objectHack(decl.body);
|
@@ -1751,11 +2615,9 @@ const generateFunc = (scope, decl) => {
|
|
1751
2615
|
const wasm = generate(innerScope, body);
|
1752
2616
|
const func = {
|
1753
2617
|
name,
|
1754
|
-
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
2618
|
+
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
1755
2619
|
returns: innerScope.returns,
|
1756
|
-
returnType: innerScope.returnType ?? TYPES.number,
|
1757
2620
|
locals: innerScope.locals,
|
1758
|
-
memory: innerScope.memory,
|
1759
2621
|
throws: innerScope.throws,
|
1760
2622
|
index: currentFuncIndex++
|
1761
2623
|
};
|
@@ -1768,8 +2630,13 @@ const generateFunc = (scope, decl) => {
|
|
1768
2630
|
}
|
1769
2631
|
}
|
1770
2632
|
|
1771
|
-
|
1772
|
-
|
2633
|
+
// add end return if not found
|
2634
|
+
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
2635
|
+
wasm.push(
|
2636
|
+
...number(0),
|
2637
|
+
...number(TYPES.undefined, Valtype.i32),
|
2638
|
+
[ Opcodes.return ]
|
2639
|
+
);
|
1773
2640
|
}
|
1774
2641
|
|
1775
2642
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1780,9 +2647,7 @@ const generateFunc = (scope, decl) => {
|
|
1780
2647
|
if (local.type === Valtype.v128) {
|
1781
2648
|
vecParams++;
|
1782
2649
|
|
1783
|
-
/*
|
1784
|
-
|
1785
|
-
wasm.unshift( // add v128 load for param
|
2650
|
+
/* wasm.unshift( // add v128 load for param
|
1786
2651
|
[ Opcodes.i32_const, 0 ],
|
1787
2652
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1788
2653
|
[ Opcodes.local_set, local.idx ]
|
@@ -1893,10 +2758,10 @@ const generateFunc = (scope, decl) => {
|
|
1893
2758
|
};
|
1894
2759
|
|
1895
2760
|
const generateCode = (scope, decl) => {
|
1896
|
-
|
2761
|
+
let out = [];
|
1897
2762
|
|
1898
2763
|
for (const x of decl.body) {
|
1899
|
-
out.
|
2764
|
+
out = out.concat(generate(scope, x));
|
1900
2765
|
}
|
1901
2766
|
|
1902
2767
|
return out;
|
@@ -1912,10 +2777,9 @@ const internalConstrs = {
|
|
1912
2777
|
|
1913
2778
|
// new Array(n)
|
1914
2779
|
|
1915
|
-
makeArray(scope, {
|
2780
|
+
const [ , pointer ] = makeArray(scope, {
|
1916
2781
|
rawElements: new Array(0)
|
1917
2782
|
}, global, name, true);
|
1918
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
1919
2783
|
|
1920
2784
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
1921
2785
|
|
@@ -1933,9 +2797,38 @@ const internalConstrs = {
|
|
1933
2797
|
];
|
1934
2798
|
},
|
1935
2799
|
type: TYPES._array
|
2800
|
+
},
|
2801
|
+
|
2802
|
+
__Array_of: {
|
2803
|
+
// this is not a constructor but best fits internal structure here
|
2804
|
+
generate: (scope, decl, global, name) => {
|
2805
|
+
// Array.of(i0, i1, ...)
|
2806
|
+
return generateArray(scope, {
|
2807
|
+
elements: decl.arguments
|
2808
|
+
}, global, name);
|
2809
|
+
},
|
2810
|
+
type: TYPES._array,
|
2811
|
+
notConstr: true
|
1936
2812
|
}
|
1937
2813
|
};
|
1938
2814
|
|
2815
|
+
// const _ = Array.prototype.push;
|
2816
|
+
// Array.prototype.push = function (a) {
|
2817
|
+
// const check = arr => {
|
2818
|
+
// for (const x of arr) {
|
2819
|
+
// if (x === undefined) {
|
2820
|
+
// console.trace(arr);
|
2821
|
+
// process.exit();
|
2822
|
+
// }
|
2823
|
+
// if (Array.isArray(x)) check(x);
|
2824
|
+
// }
|
2825
|
+
// };
|
2826
|
+
// if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
|
2827
|
+
// // if (Array.isArray(a)) check(a);
|
2828
|
+
|
2829
|
+
// return _.apply(this, arguments);
|
2830
|
+
// };
|
2831
|
+
|
1939
2832
|
export default program => {
|
1940
2833
|
globals = {};
|
1941
2834
|
globalInd = 0;
|
@@ -1944,9 +2837,9 @@ export default program => {
|
|
1944
2837
|
funcs = [];
|
1945
2838
|
funcIndex = {};
|
1946
2839
|
depth = [];
|
1947
|
-
typeStates = {};
|
1948
2840
|
arrays = new Map();
|
1949
2841
|
pages = new Map();
|
2842
|
+
data = [];
|
1950
2843
|
currentFuncIndex = importedFuncs.length;
|
1951
2844
|
|
1952
2845
|
globalThis.valtype = 'f64';
|
@@ -1996,18 +2889,20 @@ export default program => {
|
|
1996
2889
|
body: program.body
|
1997
2890
|
};
|
1998
2891
|
|
2892
|
+
if (process.argv.includes('-ast-log')) console.log(program.body.body);
|
2893
|
+
|
1999
2894
|
generateFunc(scope, program);
|
2000
2895
|
|
2001
2896
|
const main = funcs[funcs.length - 1];
|
2002
2897
|
main.export = true;
|
2003
|
-
main.returns = [ valtypeBinary ];
|
2898
|
+
main.returns = [ valtypeBinary, Valtype.i32 ];
|
2004
2899
|
|
2005
2900
|
const lastInst = main.wasm[main.wasm.length - 1] ?? [ Opcodes.end ];
|
2006
2901
|
if (lastInst[0] === Opcodes.drop) {
|
2007
2902
|
main.wasm.splice(main.wasm.length - 1, 1);
|
2008
2903
|
|
2009
2904
|
const finalStatement = program.body.body[program.body.body.length - 1];
|
2010
|
-
main.
|
2905
|
+
main.wasm.push(...getNodeType(main, finalStatement));
|
2011
2906
|
}
|
2012
2907
|
|
2013
2908
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
@@ -2023,5 +2918,5 @@ export default program => {
|
|
2023
2918
|
// if blank main func and other exports, remove it
|
2024
2919
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2025
2920
|
|
2026
|
-
return { funcs, globals, tags, exceptions, pages };
|
2921
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2027
2922
|
};
|