porffor 0.0.0-05f898f → 0.0.0-151f80e
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 +7 -6
- package/compiler/2c.js +353 -349
- package/compiler/codeGen.js +398 -101
- package/compiler/embedding.js +9 -5
- package/compiler/index.js +3 -3
- package/compiler/opt.js +14 -18
- package/compiler/prototype.js +168 -29
- package/compiler/sections.js +19 -4
- package/out.exe +0 -0
- package/package.json +1 -1
- package/r.js +39 -1
- package/rhemyn/compile.js +1 -1
- package/runner/index.js +5 -3
- package/runner/info.js +37 -2
- package/tmp.c +26 -23
package/compiler/codeGen.js
CHANGED
@@ -1,9 +1,9 @@
|
|
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
7
|
import parse from "./parse.js";
|
8
8
|
import * as Rhemyn from "../rhemyn/compile.js";
|
9
9
|
|
@@ -109,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
109
109
|
case 'WhileStatement':
|
110
110
|
return generateWhile(scope, decl);
|
111
111
|
|
112
|
-
|
113
|
-
return generateForOf(scope, decl);
|
112
|
+
case 'ForOfStatement':
|
113
|
+
return generateForOf(scope, decl);
|
114
114
|
|
115
115
|
case 'BreakStatement':
|
116
116
|
return generateBreak(scope, decl);
|
@@ -152,44 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
152
152
|
|
153
153
|
return [];
|
154
154
|
|
155
|
-
case 'TaggedTemplateExpression':
|
156
|
-
|
157
|
-
|
155
|
+
case 'TaggedTemplateExpression': {
|
156
|
+
const funcs = {
|
157
|
+
asm: str => {
|
158
|
+
let out = [];
|
158
159
|
|
159
|
-
|
160
|
-
|
160
|
+
for (const line of str.split('\n')) {
|
161
|
+
const asm = line.trim().split(';;')[0].split(' ');
|
162
|
+
if (asm[0] === '') continue; // blank
|
161
163
|
|
162
|
-
|
163
|
-
|
164
|
-
|
164
|
+
if (asm[0] === 'local') {
|
165
|
+
const [ name, idx, type ] = asm.slice(1);
|
166
|
+
scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
|
167
|
+
continue;
|
168
|
+
}
|
165
169
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
}
|
170
|
+
if (asm[0] === 'returns') {
|
171
|
+
scope.returns = asm.slice(1).map(x => Valtype[x]);
|
172
|
+
continue;
|
173
|
+
}
|
171
174
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
175
|
+
if (asm[0] === 'memory') {
|
176
|
+
allocPage('asm instrinsic');
|
177
|
+
// todo: add to store/load offset insts
|
178
|
+
continue;
|
179
|
+
}
|
176
180
|
|
177
|
-
|
178
|
-
|
179
|
-
// todo: add to store/load offset insts
|
180
|
-
continue;
|
181
|
-
}
|
181
|
+
let inst = Opcodes[asm[0].replace('.', '_')];
|
182
|
+
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
182
183
|
|
183
|
-
|
184
|
-
|
184
|
+
if (!Array.isArray(inst)) inst = [ inst ];
|
185
|
+
const immediates = asm.slice(1).map(x => parseInt(x));
|
185
186
|
|
186
|
-
|
187
|
-
|
187
|
+
out.push([ ...inst, ...immediates ]);
|
188
|
+
}
|
188
189
|
|
189
|
-
|
190
|
+
return out;
|
191
|
+
},
|
192
|
+
|
193
|
+
__internal_print_type: str => {
|
194
|
+
const type = getType(scope, str) - TYPES.number;
|
195
|
+
|
196
|
+
return [
|
197
|
+
...number(type),
|
198
|
+
[ Opcodes.call, importedFuncs.print ],
|
199
|
+
|
200
|
+
// newline
|
201
|
+
...number(10),
|
202
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
203
|
+
];
|
204
|
+
}
|
190
205
|
}
|
191
206
|
|
192
|
-
|
207
|
+
const name = decl.tag.name;
|
208
|
+
// hack for inline asm
|
209
|
+
if (!funcs[name]) return todo('tagged template expressions not implemented');
|
210
|
+
|
211
|
+
const str = decl.quasi.quasis[0].value.raw;
|
212
|
+
return funcs[name](str);
|
213
|
+
}
|
193
214
|
|
194
215
|
default:
|
195
216
|
return todo(`no generation for ${decl.type}!`);
|
@@ -305,6 +326,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
305
326
|
return idx;
|
306
327
|
};
|
307
328
|
|
329
|
+
const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
|
330
|
+
|
308
331
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
309
332
|
const checks = {
|
310
333
|
'||': falsy,
|
@@ -317,6 +340,32 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
317
340
|
// generic structure for {a} OP {b}
|
318
341
|
// -->
|
319
342
|
// _ = {a}; if (OP_CHECK) {b} else _
|
343
|
+
|
344
|
+
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
345
|
+
// (like if we are in an if condition - very common)
|
346
|
+
const leftIsInt = isIntOp(left[left.length - 1]);
|
347
|
+
const rightIsInt = isIntOp(right[right.length - 1]);
|
348
|
+
|
349
|
+
const canInt = leftIsInt && rightIsInt;
|
350
|
+
|
351
|
+
if (canInt) {
|
352
|
+
// remove int -> float conversions from left and right
|
353
|
+
left.pop();
|
354
|
+
right.pop();
|
355
|
+
|
356
|
+
return [
|
357
|
+
...left,
|
358
|
+
[ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
359
|
+
...checks[op](scope, [], leftType, true),
|
360
|
+
[ Opcodes.if, Valtype.i32 ],
|
361
|
+
...right,
|
362
|
+
[ Opcodes.else ],
|
363
|
+
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
364
|
+
[ Opcodes.end ],
|
365
|
+
Opcodes.i32_from
|
366
|
+
];
|
367
|
+
}
|
368
|
+
|
320
369
|
return [
|
321
370
|
...left,
|
322
371
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
@@ -340,6 +389,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
340
389
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
341
390
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
342
391
|
|
392
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
393
|
+
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
394
|
+
|
343
395
|
if (assign) {
|
344
396
|
const pointer = arrays.get(name ?? '$undeclared');
|
345
397
|
|
@@ -567,12 +619,12 @@ const compareStrings = (scope, left, right) => {
|
|
567
619
|
];
|
568
620
|
};
|
569
621
|
|
570
|
-
const truthy = (scope, wasm, type) => {
|
622
|
+
const truthy = (scope, wasm, type, int = false) => {
|
571
623
|
// arrays are always truthy
|
572
624
|
if (type === TYPES._array) return [
|
573
625
|
...wasm,
|
574
626
|
[ Opcodes.drop ],
|
575
|
-
...number(1)
|
627
|
+
...number(1, int ? Valtype.i32 : valtypeBinary)
|
576
628
|
];
|
577
629
|
|
578
630
|
if (type === TYPES.string) {
|
@@ -588,8 +640,8 @@ const truthy = (scope, wasm, type) => {
|
|
588
640
|
// if length != 0
|
589
641
|
/* [ Opcodes.i32_eqz ],
|
590
642
|
[ Opcodes.i32_eqz ], */
|
591
|
-
Opcodes.i32_from_u
|
592
|
-
]
|
643
|
+
...(int ? [] : [ Opcodes.i32_from_u ])
|
644
|
+
];
|
593
645
|
}
|
594
646
|
|
595
647
|
// if != 0
|
@@ -602,12 +654,12 @@ const truthy = (scope, wasm, type) => {
|
|
602
654
|
];
|
603
655
|
};
|
604
656
|
|
605
|
-
const falsy = (scope, wasm, type) => {
|
657
|
+
const falsy = (scope, wasm, type, int = false) => {
|
606
658
|
// arrays are always truthy
|
607
659
|
if (type === TYPES._array) return [
|
608
660
|
...wasm,
|
609
661
|
[ Opcodes.drop ],
|
610
|
-
...number(0)
|
662
|
+
...number(0, int ? Valtype.i32 : valtypeBinary)
|
611
663
|
];
|
612
664
|
|
613
665
|
if (type === TYPES.string) {
|
@@ -622,7 +674,7 @@ const falsy = (scope, wasm, type) => {
|
|
622
674
|
|
623
675
|
// if length == 0
|
624
676
|
[ Opcodes.i32_eqz ],
|
625
|
-
Opcodes.i32_from_u
|
677
|
+
...(int ? [] : [ Opcodes.i32_from_u ])
|
626
678
|
]
|
627
679
|
}
|
628
680
|
|
@@ -630,31 +682,29 @@ const falsy = (scope, wasm, type) => {
|
|
630
682
|
return [
|
631
683
|
...wasm,
|
632
684
|
|
633
|
-
...Opcodes.eqz,
|
634
|
-
Opcodes.i32_from_u
|
685
|
+
...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
|
635
686
|
];
|
636
687
|
};
|
637
688
|
|
638
|
-
const nullish = (scope, wasm, type) => {
|
689
|
+
const nullish = (scope, wasm, type, int = false) => {
|
639
690
|
// undefined
|
640
691
|
if (type === TYPES.undefined) return [
|
641
692
|
...wasm,
|
642
693
|
[ Opcodes.drop ],
|
643
|
-
...number(1)
|
694
|
+
...number(1, int ? Valtype.i32 : valtypeBinary)
|
644
695
|
];
|
645
696
|
|
646
697
|
// null (if object and = "0")
|
647
698
|
if (type === TYPES.object) return [
|
648
699
|
...wasm,
|
649
|
-
...Opcodes.eqz,
|
650
|
-
Opcodes.i32_from_u
|
700
|
+
...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
|
651
701
|
];
|
652
702
|
|
653
703
|
// not
|
654
704
|
return [
|
655
705
|
...wasm,
|
656
706
|
[ Opcodes.drop ],
|
657
|
-
...number(0)
|
707
|
+
...number(0, int ? Valtype.i32 : valtypeBinary)
|
658
708
|
];
|
659
709
|
};
|
660
710
|
|
@@ -665,29 +715,44 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
665
715
|
|
666
716
|
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
667
717
|
|
668
|
-
|
669
|
-
|
718
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
719
|
+
|
720
|
+
if (leftType && rightType && (
|
721
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
722
|
+
((op === '===' || op === '!==') && leftType !== rightType) ||
|
723
|
+
|
724
|
+
// if equality op and an operand is undefined, return false
|
725
|
+
(eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
|
726
|
+
)) {
|
727
|
+
// undefined == null
|
728
|
+
if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
|
729
|
+
...(leftType === TYPES.object ? left : right),
|
730
|
+
...Opcodes.eqz,
|
731
|
+
...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
|
732
|
+
];
|
733
|
+
|
670
734
|
return [
|
671
735
|
...left,
|
672
|
-
...right,
|
673
|
-
|
674
|
-
// drop values
|
675
736
|
[ Opcodes.drop ],
|
737
|
+
|
738
|
+
...right,
|
676
739
|
[ Opcodes.drop ],
|
677
740
|
|
678
|
-
// return
|
679
|
-
...number(op === '===' ?
|
741
|
+
// return true (!=/!==) or false (else)
|
742
|
+
...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
|
680
743
|
];
|
681
744
|
}
|
682
745
|
|
746
|
+
// todo: niche null hell with 0
|
747
|
+
|
683
748
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
684
749
|
if (op === '+') {
|
685
750
|
// string concat (a + b)
|
686
751
|
return concatStrings(scope, left, right, _global, _name, assign);
|
687
752
|
}
|
688
753
|
|
689
|
-
//
|
690
|
-
if (!
|
754
|
+
// not an equality op, NaN
|
755
|
+
if (!eqOp) return number(NaN);
|
691
756
|
|
692
757
|
// else leave bool ops
|
693
758
|
// todo: convert string to number if string and number/bool
|
@@ -732,17 +797,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
732
797
|
];
|
733
798
|
};
|
734
799
|
|
735
|
-
let binaryExpDepth = 0;
|
736
800
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
737
|
-
|
738
|
-
|
739
|
-
const out = [
|
740
|
-
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
741
|
-
];
|
801
|
+
const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
|
742
802
|
|
743
803
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
744
804
|
|
745
|
-
binaryExpDepth--;
|
746
805
|
return out;
|
747
806
|
};
|
748
807
|
|
@@ -959,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
959
1018
|
case 'bigint': return number(TYPES.bigint);
|
960
1019
|
}
|
961
1020
|
|
1021
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
1022
|
+
let wellFormed = aotWFA ? true : undefined;
|
1023
|
+
|
962
1024
|
const str = decl.value;
|
963
1025
|
const rawElements = new Array(str.length);
|
1026
|
+
let j = 0;
|
964
1027
|
for (let i = 0; i < str.length; i++) {
|
965
1028
|
rawElements[i] = str.charCodeAt(i);
|
1029
|
+
|
1030
|
+
if (wellFormed) {
|
1031
|
+
// check if surrogate
|
1032
|
+
if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
|
1033
|
+
// unpaired trailing surrogate
|
1034
|
+
if (str.charCodeAt(j) >= 0xDC00) {
|
1035
|
+
wellFormed = false;
|
1036
|
+
}
|
1037
|
+
|
1038
|
+
// unpaired leading surrogate
|
1039
|
+
// if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
|
1040
|
+
if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
|
1041
|
+
wellFormed = false;
|
1042
|
+
}
|
1043
|
+
}
|
1044
|
+
|
1045
|
+
j++;
|
1046
|
+
}
|
966
1047
|
}
|
967
1048
|
|
1049
|
+
// console.log(wellFormed, str);
|
1050
|
+
|
1051
|
+
if (aotWFA) addVarMeta(name, { wellFormed });
|
1052
|
+
|
968
1053
|
return makeArray(scope, {
|
969
1054
|
rawElements
|
970
1055
|
}, global, name, false, 'i16')[0];
|
@@ -999,6 +1084,8 @@ const countLeftover = wasm => {
|
|
999
1084
|
} else count--;
|
1000
1085
|
if (func) count += func.returns.length;
|
1001
1086
|
} else count--;
|
1087
|
+
|
1088
|
+
// console.log(count, decompile([ inst ]).slice(0, -1));
|
1002
1089
|
}
|
1003
1090
|
|
1004
1091
|
return count;
|
@@ -1181,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1181
1268
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
|
1182
1269
|
|
1183
1270
|
let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1271
|
+
let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1184
1272
|
|
1185
1273
|
// use local for cached i32 length as commonly used
|
1186
1274
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1187
1275
|
|
1276
|
+
let lengthI32CacheUsed = false;
|
1277
|
+
|
1278
|
+
const protoOut = protoFunc(pointer, {
|
1279
|
+
getCachedI32: () => {
|
1280
|
+
lengthI32CacheUsed = true;
|
1281
|
+
return [ [ Opcodes.local_get, lengthLocal ] ]
|
1282
|
+
},
|
1283
|
+
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1284
|
+
get: () => arrayUtil.getLength(pointer),
|
1285
|
+
getI32: () => arrayUtil.getLengthI32(pointer),
|
1286
|
+
set: value => arrayUtil.setLength(pointer, value),
|
1287
|
+
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1288
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1289
|
+
return makeArray(scope, {
|
1290
|
+
rawElements: new Array(length)
|
1291
|
+
}, _global, _name, true, itemType);
|
1292
|
+
}, varMetadata.get(baseName));
|
1293
|
+
|
1188
1294
|
return [
|
1189
1295
|
...out,
|
1190
1296
|
|
1191
|
-
...
|
1192
|
-
|
1297
|
+
...(!lengthI32CacheUsed ? [] : [
|
1298
|
+
...arrayUtil.getLengthI32(pointer),
|
1299
|
+
[ Opcodes.local_set, lengthLocal ],
|
1300
|
+
]),
|
1193
1301
|
|
1194
1302
|
[ Opcodes.block, valtypeBinary ],
|
1195
|
-
...
|
1196
|
-
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
1197
|
-
get: arrayUtil.getLength(pointer),
|
1198
|
-
getI32: arrayUtil.getLengthI32(pointer),
|
1199
|
-
set: value => arrayUtil.setLength(pointer, value),
|
1200
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1201
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1202
|
-
return makeArray(scope, {
|
1203
|
-
rawElements: new Array(length)
|
1204
|
-
}, _global, _name, true, itemType);
|
1205
|
-
}),
|
1303
|
+
...protoOut,
|
1206
1304
|
[ Opcodes.end ]
|
1207
1305
|
];
|
1208
1306
|
}
|
@@ -1262,7 +1360,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1262
1360
|
if (func && func.throws) scope.throws = true;
|
1263
1361
|
|
1264
1362
|
for (const arg of args) {
|
1265
|
-
out.
|
1363
|
+
out = out.concat(generate(scope, arg));
|
1266
1364
|
}
|
1267
1365
|
|
1268
1366
|
out.push([ Opcodes.call, idx ]);
|
@@ -1273,7 +1371,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1273
1371
|
const generateNew = (scope, decl, _global, _name) => {
|
1274
1372
|
// hack: basically treat this as a normal call for builtins for now
|
1275
1373
|
const name = mapName(decl.callee.name);
|
1276
|
-
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1374
|
+
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1277
1375
|
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1278
1376
|
|
1279
1377
|
return generateCall(scope, decl, _global, _name);
|
@@ -1291,12 +1389,12 @@ const unhackName = name => {
|
|
1291
1389
|
};
|
1292
1390
|
|
1293
1391
|
const generateVar = (scope, decl) => {
|
1294
|
-
|
1392
|
+
let out = [];
|
1295
1393
|
|
1296
1394
|
const topLevel = scope.name === 'main';
|
1297
1395
|
|
1298
1396
|
// global variable if in top scope (main) and var ..., or if wanted
|
1299
|
-
const global = decl.kind === 'var';
|
1397
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1300
1398
|
const target = global ? globals : scope.locals;
|
1301
1399
|
|
1302
1400
|
for (const x of decl.declarations) {
|
@@ -1333,7 +1431,7 @@ const generateVar = (scope, decl) => {
|
|
1333
1431
|
|
1334
1432
|
// x.init ??= DEFAULT_VALUE;
|
1335
1433
|
if (x.init) {
|
1336
|
-
out.
|
1434
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1337
1435
|
|
1338
1436
|
// if our value is the result of a function, infer the type from that func's return value
|
1339
1437
|
if (out[out.length - 1][0] === Opcodes.call) {
|
@@ -1402,13 +1500,60 @@ const generateAssign = (scope, decl) => {
|
|
1402
1500
|
];
|
1403
1501
|
}
|
1404
1502
|
|
1503
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1504
|
+
|
1505
|
+
// arr[i] | str[i]
|
1506
|
+
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1507
|
+
const name = decl.left.object.name;
|
1508
|
+
const pointer = arrays.get(name);
|
1509
|
+
|
1510
|
+
const aotPointer = pointer != null;
|
1511
|
+
|
1512
|
+
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1513
|
+
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1514
|
+
|
1515
|
+
const parentType = getNodeType(scope, decl.left.object);
|
1516
|
+
|
1517
|
+
return [
|
1518
|
+
...(aotPointer ? [] : [
|
1519
|
+
...generate(scope, decl.left.object),
|
1520
|
+
Opcodes.i32_to_u
|
1521
|
+
]),
|
1522
|
+
|
1523
|
+
// get index as valtype
|
1524
|
+
...generate(scope, decl.left.property),
|
1525
|
+
|
1526
|
+
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
1527
|
+
Opcodes.i32_to_u,
|
1528
|
+
...number(ValtypeSize[valtype], Valtype.i32),
|
1529
|
+
[ Opcodes.i32_mul ],
|
1530
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1531
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1532
|
+
|
1533
|
+
...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
|
1534
|
+
[ Opcodes.local_get, pointerTmp ],
|
1535
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1536
|
+
], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
|
1537
|
+
[ Opcodes.local_tee, newValueTmp ],
|
1538
|
+
|
1539
|
+
...(parentType === TYPES._array ? [
|
1540
|
+
[ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1541
|
+
] : [
|
1542
|
+
Opcodes.i32_to_u,
|
1543
|
+
[ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1544
|
+
]),
|
1545
|
+
|
1546
|
+
[ Opcodes.local_get, newValueTmp ]
|
1547
|
+
];
|
1548
|
+
}
|
1549
|
+
|
1405
1550
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1406
1551
|
|
1407
1552
|
if (local === undefined) {
|
1408
|
-
// todo: this should be a
|
1553
|
+
// todo: this should be a sloppy mode only thing
|
1409
1554
|
|
1410
1555
|
// only allow = for this
|
1411
|
-
if (
|
1556
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1412
1557
|
|
1413
1558
|
if (builtinVars[name]) {
|
1414
1559
|
// just return rhs (eg `NaN = 2`)
|
@@ -1417,13 +1562,15 @@ const generateAssign = (scope, decl) => {
|
|
1417
1562
|
|
1418
1563
|
// set global and return (eg a = 2)
|
1419
1564
|
return [
|
1420
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1565
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1421
1566
|
[ Opcodes.global_get, globals[name].idx ]
|
1422
1567
|
];
|
1423
1568
|
}
|
1424
1569
|
|
1425
|
-
|
1426
|
-
|
1570
|
+
typeStates[name] = getNodeType(scope, decl.right);
|
1571
|
+
|
1572
|
+
if (op === '=') {
|
1573
|
+
// typeStates[name] = getNodeType(scope, decl.right);
|
1427
1574
|
|
1428
1575
|
return [
|
1429
1576
|
...generate(scope, decl.right, isGlobal, name),
|
@@ -1432,7 +1579,6 @@ const generateAssign = (scope, decl) => {
|
|
1432
1579
|
];
|
1433
1580
|
}
|
1434
1581
|
|
1435
|
-
const op = decl.operator.slice(0, -1);
|
1436
1582
|
if (op === '||' || op === '&&' || op === '??') {
|
1437
1583
|
// todo: is this needed?
|
1438
1584
|
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
@@ -1567,7 +1713,7 @@ const generateUpdate = (scope, decl) => {
|
|
1567
1713
|
};
|
1568
1714
|
|
1569
1715
|
const generateIf = (scope, decl) => {
|
1570
|
-
const out = truthy(scope, generate(scope, decl.test), decl.test);
|
1716
|
+
const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
|
1571
1717
|
|
1572
1718
|
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1573
1719
|
depth.push('if');
|
@@ -1660,18 +1806,106 @@ const generateWhile = (scope, decl) => {
|
|
1660
1806
|
const generateForOf = (scope, decl) => {
|
1661
1807
|
const out = [];
|
1662
1808
|
|
1809
|
+
const rightType = getNodeType(scope, decl.right);
|
1810
|
+
const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
|
1811
|
+
|
1812
|
+
// todo: for of inside for of might fuck up?
|
1813
|
+
const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
|
1814
|
+
const length = localTmp(scope, 'forof_length', Valtype.i32);
|
1815
|
+
const counter = localTmp(scope, 'forof_counter', Valtype.i32);
|
1816
|
+
|
1817
|
+
out.push(
|
1818
|
+
// set pointer as right
|
1819
|
+
...generate(scope, decl.right),
|
1820
|
+
Opcodes.i32_to_u,
|
1821
|
+
[ Opcodes.local_set, pointer ],
|
1822
|
+
|
1823
|
+
// get length
|
1824
|
+
[ Opcodes.local_get, pointer ],
|
1825
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
1826
|
+
[ Opcodes.local_set, length ]
|
1827
|
+
);
|
1828
|
+
|
1663
1829
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
1664
|
-
depth.push('
|
1830
|
+
depth.push('forof');
|
1665
1831
|
|
1666
|
-
|
1667
|
-
|
1668
|
-
depth.push('if');
|
1832
|
+
// setup local for left
|
1833
|
+
generate(scope, decl.left);
|
1669
1834
|
|
1670
|
-
|
1835
|
+
const leftName = decl.left.declarations[0].id.name;
|
1671
1836
|
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1837
|
+
// set type for local
|
1838
|
+
typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
|
1839
|
+
|
1840
|
+
const [ local, isGlobal ] = lookupName(scope, leftName);
|
1841
|
+
|
1842
|
+
if (rightType === TYPES._array) { // array
|
1843
|
+
out.push(
|
1844
|
+
[ Opcodes.local_get, pointer ],
|
1845
|
+
[ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
|
1846
|
+
);
|
1847
|
+
} else { // string
|
1848
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1849
|
+
rawElements: new Array(1)
|
1850
|
+
}, isGlobal, leftName, true, 'i16');
|
1851
|
+
|
1852
|
+
out.push(
|
1853
|
+
// setup new/out array
|
1854
|
+
...newOut,
|
1855
|
+
[ Opcodes.drop ],
|
1856
|
+
|
1857
|
+
...number(0, Valtype.i32), // base 0 for store after
|
1858
|
+
|
1859
|
+
// load current string ind {arg}
|
1860
|
+
[ Opcodes.local_get, pointer ],
|
1861
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
1862
|
+
|
1863
|
+
// store to new string ind 0
|
1864
|
+
[ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
1865
|
+
|
1866
|
+
// return new string (page)
|
1867
|
+
...number(newPointer)
|
1868
|
+
);
|
1869
|
+
}
|
1870
|
+
|
1871
|
+
// set left value
|
1872
|
+
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
|
1873
|
+
|
1874
|
+
out.push(
|
1875
|
+
[ Opcodes.block, Blocktype.void ],
|
1876
|
+
[ Opcodes.block, Blocktype.void ]
|
1877
|
+
);
|
1878
|
+
depth.push('block');
|
1879
|
+
depth.push('block');
|
1880
|
+
|
1881
|
+
out.push(
|
1882
|
+
...generate(scope, decl.body),
|
1883
|
+
[ Opcodes.end ]
|
1884
|
+
);
|
1885
|
+
depth.pop();
|
1886
|
+
|
1887
|
+
out.push(
|
1888
|
+
// increment iter pointer by valtype size
|
1889
|
+
[ Opcodes.local_get, pointer ],
|
1890
|
+
...number(valtypeSize, Valtype.i32),
|
1891
|
+
[ Opcodes.i32_add ],
|
1892
|
+
[ Opcodes.local_set, pointer ],
|
1893
|
+
|
1894
|
+
// increment counter by 1
|
1895
|
+
[ Opcodes.local_get, counter ],
|
1896
|
+
...number(1, Valtype.i32),
|
1897
|
+
[ Opcodes.i32_add ],
|
1898
|
+
[ Opcodes.local_tee, counter ],
|
1899
|
+
|
1900
|
+
// loop if counter != length
|
1901
|
+
[ Opcodes.local_get, length ],
|
1902
|
+
[ Opcodes.i32_ne ],
|
1903
|
+
[ Opcodes.br_if, 1 ],
|
1904
|
+
|
1905
|
+
[ Opcodes.end ], [ Opcodes.end ]
|
1906
|
+
);
|
1907
|
+
depth.pop();
|
1908
|
+
depth.pop();
|
1675
1909
|
|
1676
1910
|
return out;
|
1677
1911
|
};
|
@@ -1791,7 +2025,7 @@ const itemTypeToValtype = {
|
|
1791
2025
|
i16: 'i32'
|
1792
2026
|
};
|
1793
2027
|
|
1794
|
-
const
|
2028
|
+
const StoreOps = {
|
1795
2029
|
i32: Opcodes.i32_store,
|
1796
2030
|
i64: Opcodes.i64_store,
|
1797
2031
|
f64: Opcodes.f64_store,
|
@@ -1800,10 +2034,28 @@ const storeOps = {
|
|
1800
2034
|
i16: Opcodes.i32_store16
|
1801
2035
|
};
|
1802
2036
|
|
2037
|
+
let data = [];
|
2038
|
+
|
2039
|
+
const compileBytes = (val, itemType, signed = true) => {
|
2040
|
+
switch (itemType) {
|
2041
|
+
case 'i8': return [ val % 256 ];
|
2042
|
+
case 'i16': return [ val % 256, Math.floor(val / 256) ];
|
2043
|
+
|
2044
|
+
case 'i32':
|
2045
|
+
case 'i64':
|
2046
|
+
return enforceFourBytes(signedLEB128(val));
|
2047
|
+
|
2048
|
+
case 'f64': return ieee754_binary64(val);
|
2049
|
+
}
|
2050
|
+
};
|
2051
|
+
|
1803
2052
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1804
2053
|
const out = [];
|
1805
2054
|
|
2055
|
+
let firstAssign = false;
|
1806
2056
|
if (!arrays.has(name) || name === '$undeclared') {
|
2057
|
+
firstAssign = true;
|
2058
|
+
|
1807
2059
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1808
2060
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1809
2061
|
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
@@ -1814,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1814
2066
|
const useRawElements = !!decl.rawElements;
|
1815
2067
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1816
2068
|
|
2069
|
+
const valtype = itemTypeToValtype[itemType];
|
1817
2070
|
const length = elements.length;
|
1818
2071
|
|
2072
|
+
if (firstAssign && useRawElements) {
|
2073
|
+
let bytes = compileBytes(length, 'i32');
|
2074
|
+
|
2075
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2076
|
+
if (elements[i] == null) continue;
|
2077
|
+
|
2078
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2079
|
+
}
|
2080
|
+
|
2081
|
+
data.push({
|
2082
|
+
offset: pointer,
|
2083
|
+
bytes
|
2084
|
+
});
|
2085
|
+
|
2086
|
+
// local value as pointer
|
2087
|
+
out.push(...number(pointer));
|
2088
|
+
|
2089
|
+
return [ out, pointer ];
|
2090
|
+
}
|
2091
|
+
|
1819
2092
|
// store length as 0th array
|
1820
2093
|
out.push(
|
1821
2094
|
...number(0, Valtype.i32),
|
@@ -1823,8 +2096,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1823
2096
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
|
1824
2097
|
);
|
1825
2098
|
|
1826
|
-
const storeOp =
|
1827
|
-
const valtype = itemTypeToValtype[itemType];
|
2099
|
+
const storeOp = StoreOps[itemType];
|
1828
2100
|
|
1829
2101
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1830
2102
|
if (elements[i] == null) continue;
|
@@ -1847,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
|
|
1847
2119
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1848
2120
|
};
|
1849
2121
|
|
2122
|
+
let varMetadata = new Map();
|
2123
|
+
const addVarMeta = (_name, obj) => {
|
2124
|
+
const name = _name ?? '$undeclared';
|
2125
|
+
if (!varMetadata.has(name)) varMetadata.set(name, {});
|
2126
|
+
|
2127
|
+
const meta = varMetadata.get(name);
|
2128
|
+
for (const k in obj) {
|
2129
|
+
meta[k] = obj[k];
|
2130
|
+
}
|
2131
|
+
};
|
2132
|
+
|
1850
2133
|
export const generateMember = (scope, decl, _global, _name) => {
|
1851
2134
|
const type = getNodeType(scope, decl.object);
|
1852
2135
|
|
@@ -2148,10 +2431,10 @@ const generateFunc = (scope, decl) => {
|
|
2148
2431
|
};
|
2149
2432
|
|
2150
2433
|
const generateCode = (scope, decl) => {
|
2151
|
-
|
2434
|
+
let out = [];
|
2152
2435
|
|
2153
2436
|
for (const x of decl.body) {
|
2154
|
-
out.
|
2437
|
+
out = out.concat(generate(scope, x));
|
2155
2438
|
}
|
2156
2439
|
|
2157
2440
|
return out;
|
@@ -2187,6 +2470,18 @@ const internalConstrs = {
|
|
2187
2470
|
];
|
2188
2471
|
},
|
2189
2472
|
type: TYPES._array
|
2473
|
+
},
|
2474
|
+
|
2475
|
+
__Array_of: {
|
2476
|
+
// this is not a constructor but best fits internal structure here
|
2477
|
+
generate: (scope, decl, global, name) => {
|
2478
|
+
// Array.of(i0, i1, ...)
|
2479
|
+
return generateArray(scope, {
|
2480
|
+
elements: decl.arguments
|
2481
|
+
}, global, name);
|
2482
|
+
},
|
2483
|
+
type: TYPES._array,
|
2484
|
+
notConstr: true
|
2190
2485
|
}
|
2191
2486
|
};
|
2192
2487
|
|
@@ -2200,7 +2495,9 @@ export default program => {
|
|
2200
2495
|
depth = [];
|
2201
2496
|
typeStates = {};
|
2202
2497
|
arrays = new Map();
|
2498
|
+
varMetadata = new Map();
|
2203
2499
|
pages = new Map();
|
2500
|
+
data = [];
|
2204
2501
|
currentFuncIndex = importedFuncs.length;
|
2205
2502
|
|
2206
2503
|
globalThis.valtype = 'f64';
|
@@ -2277,5 +2574,5 @@ export default program => {
|
|
2277
2574
|
// if blank main func and other exports, remove it
|
2278
2575
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2279
2576
|
|
2280
|
-
return { funcs, globals, tags, exceptions, pages };
|
2577
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2281
2578
|
};
|