porffor 0.2.0-50b82f8 → 0.2.0-5ac7ea0
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 +82 -46
- package/asur/README.md +2 -0
- package/asur/index.js +978 -0
- package/compiler/2c.js +316 -71
- package/compiler/builtins/base64.ts +153 -0
- package/compiler/builtins/crypto.ts +132 -0
- package/compiler/builtins/porffor.d.ts +26 -0
- package/compiler/builtins.js +590 -255
- package/compiler/codeGen.js +520 -184
- package/compiler/decompile.js +3 -3
- package/compiler/generated_builtins.js +25 -0
- package/compiler/index.js +22 -22
- package/compiler/log.js +4 -1
- package/compiler/opt.js +52 -27
- package/compiler/parse.js +4 -2
- package/compiler/precompile.js +129 -0
- package/compiler/prefs.js +26 -0
- package/compiler/prototype.js +177 -21
- package/compiler/sections.js +8 -7
- package/compiler/wasmSpec.js +20 -6
- package/compiler/wrap.js +112 -11
- package/package.json +1 -1
- package/porf +2 -0
- package/rhemyn/compile.js +2 -1
- package/runner/index.js +26 -3
- package/runner/profiler.js +83 -0
- package/compiler/builtins/base64.js +0 -92
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
package/compiler/codeGen.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } 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";
|
@@ -7,6 +7,7 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
10
11
|
|
11
12
|
let globals = {};
|
12
13
|
let globalInd = 0;
|
@@ -55,7 +56,7 @@ const todo = msg => {
|
|
55
56
|
};
|
56
57
|
|
57
58
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
59
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
60
|
switch (decl.type) {
|
60
61
|
case 'BinaryExpression':
|
61
62
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -68,7 +69,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
69
|
|
69
70
|
case 'ArrowFunctionExpression':
|
70
71
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
72
|
+
const func = generateFunc(scope, decl);
|
73
|
+
|
74
|
+
if (decl.type.endsWith('Expression')) {
|
75
|
+
return number(func.index);
|
76
|
+
}
|
77
|
+
|
72
78
|
return [];
|
73
79
|
|
74
80
|
case 'BlockStatement':
|
@@ -81,7 +87,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
87
|
return generateExp(scope, decl);
|
82
88
|
|
83
89
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
90
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
91
|
|
86
92
|
case 'NewExpression':
|
87
93
|
return generateNew(scope, decl, global, name);
|
@@ -99,7 +105,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
99
105
|
return generateUnary(scope, decl);
|
100
106
|
|
101
107
|
case 'UpdateExpression':
|
102
|
-
return generateUpdate(scope, decl);
|
108
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
103
109
|
|
104
110
|
case 'IfStatement':
|
105
111
|
return generateIf(scope, decl);
|
@@ -155,7 +161,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
155
161
|
|
156
162
|
case 'TaggedTemplateExpression': {
|
157
163
|
const funcs = {
|
158
|
-
|
164
|
+
__Porffor_wasm: str => {
|
159
165
|
let out = [];
|
160
166
|
|
161
167
|
for (const line of str.split('\n')) {
|
@@ -163,8 +169,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
163
169
|
if (asm[0] === '') continue; // blank
|
164
170
|
|
165
171
|
if (asm[0] === 'local') {
|
166
|
-
const [ name,
|
167
|
-
scope.locals[name] = { idx:
|
172
|
+
const [ name, type ] = asm.slice(1);
|
173
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
168
174
|
continue;
|
169
175
|
}
|
170
176
|
|
@@ -174,7 +180,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
174
180
|
}
|
175
181
|
|
176
182
|
if (asm[0] === 'memory') {
|
177
|
-
allocPage('asm instrinsic');
|
183
|
+
allocPage(scope, 'asm instrinsic');
|
178
184
|
// todo: add to store/load offset insts
|
179
185
|
continue;
|
180
186
|
}
|
@@ -183,7 +189,11 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
183
189
|
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
184
190
|
|
185
191
|
if (!Array.isArray(inst)) inst = [ inst ];
|
186
|
-
const immediates = asm.slice(1).map(x =>
|
192
|
+
const immediates = asm.slice(1).map(x => {
|
193
|
+
const int = parseInt(x);
|
194
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
195
|
+
return int;
|
196
|
+
});
|
187
197
|
|
188
198
|
out.push([ ...inst, ...immediates ]);
|
189
199
|
}
|
@@ -191,31 +201,40 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
191
201
|
return out;
|
192
202
|
},
|
193
203
|
|
194
|
-
|
195
|
-
|
204
|
+
__Porffor_bs: str => [
|
205
|
+
...makeString(scope, str, undefined, undefined, true),
|
196
206
|
|
197
|
-
|
198
|
-
|
199
|
-
|
207
|
+
...number(TYPES._bytestring, Valtype.i32),
|
208
|
+
setLastType(scope)
|
209
|
+
],
|
210
|
+
__Porffor_s: str => [
|
211
|
+
...makeString(scope, str, undefined, undefined, false),
|
200
212
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
}
|
206
|
-
}
|
213
|
+
...number(TYPES.string, Valtype.i32),
|
214
|
+
setLastType(scope)
|
215
|
+
],
|
216
|
+
};
|
207
217
|
|
208
218
|
const name = decl.tag.name;
|
209
219
|
// hack for inline asm
|
210
220
|
if (!funcs[name]) return todo('tagged template expressions not implemented');
|
211
221
|
|
212
|
-
const
|
222
|
+
const { quasis, expressions } = decl.quasi;
|
223
|
+
let str = quasis[0].value.raw;
|
224
|
+
|
225
|
+
for (let i = 0; i < expressions.length; i++) {
|
226
|
+
const e = expressions[i];
|
227
|
+
str += lookupName(scope, e.name)[0].idx;
|
228
|
+
str += quasis[i + 1].value.raw;
|
229
|
+
}
|
230
|
+
|
213
231
|
return funcs[name](str);
|
214
232
|
}
|
215
233
|
|
216
234
|
default:
|
217
|
-
|
218
|
-
|
235
|
+
// ignore typescript nodes
|
236
|
+
if (decl.type.startsWith('TS') ||
|
237
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
219
238
|
return [];
|
220
239
|
}
|
221
240
|
|
@@ -269,25 +288,28 @@ const generateIdent = (scope, decl) => {
|
|
269
288
|
const name = mapName(rawName);
|
270
289
|
let local = scope.locals[rawName];
|
271
290
|
|
272
|
-
if (builtinVars
|
291
|
+
if (Object.hasOwn(builtinVars, name)) {
|
273
292
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
274
|
-
|
293
|
+
|
294
|
+
let wasm = builtinVars[name];
|
295
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
296
|
+
return wasm;
|
275
297
|
}
|
276
298
|
|
277
|
-
if (builtinFuncs
|
299
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
278
300
|
// todo: return an actual something
|
279
301
|
return number(1);
|
280
302
|
}
|
281
303
|
|
282
|
-
if (local === undefined) {
|
304
|
+
if (local?.idx === undefined) {
|
283
305
|
// no local var with name
|
284
|
-
if (
|
285
|
-
if (funcIndex
|
306
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
307
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
286
308
|
|
287
|
-
if (globals
|
309
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
288
310
|
}
|
289
311
|
|
290
|
-
if (local === undefined && rawName.startsWith('__')) {
|
312
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
291
313
|
// return undefined if unknown key in already known var
|
292
314
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
293
315
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -296,7 +318,7 @@ const generateIdent = (scope, decl) => {
|
|
296
318
|
if (!parentLookup[1]) return number(UNDEFINED);
|
297
319
|
}
|
298
320
|
|
299
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
321
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
300
322
|
|
301
323
|
return [ [ Opcodes.local_get, local.idx ] ];
|
302
324
|
};
|
@@ -404,9 +426,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
404
426
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
405
427
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
406
428
|
|
407
|
-
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
408
|
-
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
409
|
-
|
410
429
|
if (assign) {
|
411
430
|
const pointer = arrays.get(name ?? '$undeclared');
|
412
431
|
|
@@ -644,11 +663,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
644
663
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
645
664
|
];
|
646
665
|
|
647
|
-
const
|
666
|
+
const useTmp = knownType(scope, type) == null;
|
667
|
+
const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
648
668
|
|
649
669
|
const def = [
|
650
670
|
// if value != 0
|
651
|
-
[ Opcodes.local_get, tmp ],
|
671
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
652
672
|
|
653
673
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
654
674
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -660,7 +680,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
660
680
|
|
661
681
|
return [
|
662
682
|
...wasm,
|
663
|
-
[ Opcodes.local_set, tmp ],
|
683
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
664
684
|
|
665
685
|
...typeSwitch(scope, type, {
|
666
686
|
// [TYPES.number]: def,
|
@@ -669,7 +689,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
669
689
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
670
690
|
],
|
671
691
|
[TYPES.string]: [
|
672
|
-
[ Opcodes.local_get, tmp ],
|
692
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
673
693
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
674
694
|
|
675
695
|
// get length
|
@@ -680,16 +700,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
680
700
|
[ Opcodes.i32_eqz ], */
|
681
701
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
702
|
],
|
703
|
+
[TYPES._bytestring]: [ // duplicate of string
|
704
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
705
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
706
|
+
|
707
|
+
// get length
|
708
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
709
|
+
|
710
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
711
|
+
],
|
683
712
|
default: def
|
684
713
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
714
|
];
|
686
715
|
};
|
687
716
|
|
688
717
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
689
|
-
const
|
718
|
+
const useTmp = knownType(scope, type) == null;
|
719
|
+
const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
720
|
+
|
690
721
|
return [
|
691
722
|
...wasm,
|
692
|
-
[ Opcodes.local_set, tmp ],
|
723
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
693
724
|
|
694
725
|
...typeSwitch(scope, type, {
|
695
726
|
[TYPES._array]: [
|
@@ -697,7 +728,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
697
728
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
698
729
|
],
|
699
730
|
[TYPES.string]: [
|
700
|
-
[ Opcodes.local_get, tmp ],
|
731
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
732
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
733
|
+
|
734
|
+
// get length
|
735
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
736
|
+
|
737
|
+
// if length == 0
|
738
|
+
[ Opcodes.i32_eqz ],
|
739
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
740
|
+
],
|
741
|
+
[TYPES._bytestring]: [ // duplicate of string
|
742
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
701
743
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
702
744
|
|
703
745
|
// get length
|
@@ -709,7 +751,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
709
751
|
],
|
710
752
|
default: [
|
711
753
|
// if value == 0
|
712
|
-
[ Opcodes.local_get, tmp ],
|
754
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
713
755
|
|
714
756
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
715
757
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -719,10 +761,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
719
761
|
};
|
720
762
|
|
721
763
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
722
|
-
const
|
764
|
+
const useTmp = knownType(scope, type) == null;
|
765
|
+
const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
766
|
+
|
723
767
|
return [
|
724
768
|
...wasm,
|
725
|
-
[ Opcodes.local_set, tmp ],
|
769
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
726
770
|
|
727
771
|
...typeSwitch(scope, type, {
|
728
772
|
[TYPES.undefined]: [
|
@@ -731,7 +775,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
731
775
|
],
|
732
776
|
[TYPES.object]: [
|
733
777
|
// object, null if == 0
|
734
|
-
[ Opcodes.local_get, tmp ],
|
778
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
735
779
|
|
736
780
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
737
781
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -760,11 +804,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
760
804
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
761
805
|
}
|
762
806
|
|
807
|
+
const knownLeft = knownType(scope, leftType);
|
808
|
+
const knownRight = knownType(scope, rightType);
|
809
|
+
|
763
810
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
764
811
|
const strictOp = op === '===' || op === '!==';
|
765
812
|
|
766
813
|
const startOut = [], endOut = [];
|
767
|
-
const
|
814
|
+
const finalize = out => startOut.concat(out, endOut);
|
768
815
|
|
769
816
|
// if strict (in)equal check types match
|
770
817
|
if (strictOp) {
|
@@ -809,31 +856,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
809
856
|
// todo: if equality op and an operand is undefined, return false
|
810
857
|
// todo: niche null hell with 0
|
811
858
|
|
812
|
-
//
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
859
|
+
// todo: this should be dynamic but for now only static
|
860
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
861
|
+
if (op === '+') {
|
862
|
+
// string concat (a + b)
|
863
|
+
return concatStrings(scope, left, right, _global, _name, assign);
|
864
|
+
}
|
865
|
+
|
866
|
+
// not an equality op, NaN
|
867
|
+
if (!eqOp) return number(NaN);
|
868
|
+
|
869
|
+
// else leave bool ops
|
870
|
+
// todo: convert string to number if string and number/bool
|
871
|
+
// todo: string (>|>=|<|<=) string
|
872
|
+
|
873
|
+
// string comparison
|
874
|
+
if (op === '===' || op === '==') {
|
875
|
+
return compareStrings(scope, left, right);
|
876
|
+
}
|
877
|
+
|
878
|
+
if (op === '!==' || op === '!=') {
|
879
|
+
return [
|
880
|
+
...compareStrings(scope, left, right),
|
881
|
+
[ Opcodes.i32_eqz ]
|
882
|
+
];
|
883
|
+
}
|
884
|
+
}
|
837
885
|
|
838
886
|
let ops = operatorOpcode[valtype][op];
|
839
887
|
|
@@ -843,7 +891,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
843
891
|
includeBuiltin(scope, builtinName);
|
844
892
|
const idx = funcIndex[builtinName];
|
845
893
|
|
846
|
-
return
|
894
|
+
return finalize([
|
847
895
|
...left,
|
848
896
|
...right,
|
849
897
|
[ Opcodes.call, idx ]
|
@@ -858,9 +906,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
858
906
|
let tmpLeft, tmpRight;
|
859
907
|
// if equal op, check if strings for compareStrings
|
860
908
|
if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
|
861
|
-
const knownLeft = knownType(scope, leftType);
|
862
|
-
const knownRight = knownType(scope, rightType);
|
863
|
-
|
864
909
|
// todo: intelligent partial skip later
|
865
910
|
// if neither known are string, stop this madness
|
866
911
|
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
@@ -900,7 +945,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
900
945
|
[ Opcodes.i32_or ],
|
901
946
|
[ Opcodes.if, Blocktype.void ],
|
902
947
|
...number(0, Valtype.i32),
|
903
|
-
[ Opcodes.br,
|
948
|
+
[ Opcodes.br, 2 ],
|
904
949
|
[ Opcodes.end ],
|
905
950
|
|
906
951
|
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
@@ -918,7 +963,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
918
963
|
// }
|
919
964
|
})();
|
920
965
|
|
921
|
-
return
|
966
|
+
return finalize([
|
922
967
|
...left,
|
923
968
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
924
969
|
...right,
|
@@ -935,7 +980,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
935
980
|
return out;
|
936
981
|
};
|
937
982
|
|
938
|
-
const
|
983
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
984
|
+
return func({ name, params, locals, returns, localInd }, {
|
985
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
986
|
+
builtin: name => {
|
987
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
988
|
+
if (idx === undefined && builtinFuncs[name]) {
|
989
|
+
includeBuiltin(null, name);
|
990
|
+
idx = funcIndex[name];
|
991
|
+
}
|
992
|
+
|
993
|
+
return idx;
|
994
|
+
}
|
995
|
+
});
|
996
|
+
};
|
997
|
+
|
998
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
939
999
|
const existing = funcs.find(x => x.name === name);
|
940
1000
|
if (existing) return existing;
|
941
1001
|
|
@@ -947,6 +1007,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
947
1007
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
948
1008
|
}
|
949
1009
|
|
1010
|
+
for (const x of _data) {
|
1011
|
+
const copy = { ...x };
|
1012
|
+
copy.offset += pages.size * pageSize;
|
1013
|
+
data.push(copy);
|
1014
|
+
}
|
1015
|
+
|
1016
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1017
|
+
|
950
1018
|
let baseGlobalIdx, i = 0;
|
951
1019
|
for (const type of globalTypes) {
|
952
1020
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1027,7 +1095,8 @@ const TYPES = {
|
|
1027
1095
|
|
1028
1096
|
// these are not "typeof" types but tracked internally
|
1029
1097
|
_array: 0x10,
|
1030
|
-
_regexp: 0x11
|
1098
|
+
_regexp: 0x11,
|
1099
|
+
_bytestring: 0x12
|
1031
1100
|
};
|
1032
1101
|
|
1033
1102
|
const TYPE_NAMES = {
|
@@ -1041,13 +1110,17 @@ const TYPE_NAMES = {
|
|
1041
1110
|
[TYPES.bigint]: 'BigInt',
|
1042
1111
|
|
1043
1112
|
[TYPES._array]: 'Array',
|
1044
|
-
[TYPES._regexp]: 'RegExp'
|
1113
|
+
[TYPES._regexp]: 'RegExp',
|
1114
|
+
[TYPES._bytestring]: 'ByteString'
|
1045
1115
|
};
|
1046
1116
|
|
1047
1117
|
const getType = (scope, _name) => {
|
1048
1118
|
const name = mapName(_name);
|
1049
1119
|
|
1120
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1050
1121
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1122
|
+
|
1123
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1051
1124
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1052
1125
|
|
1053
1126
|
let type = TYPES.undefined;
|
@@ -1094,6 +1167,8 @@ const getNodeType = (scope, node) => {
|
|
1094
1167
|
if (node.type === 'Literal') {
|
1095
1168
|
if (node.regex) return TYPES._regexp;
|
1096
1169
|
|
1170
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1171
|
+
|
1097
1172
|
return TYPES[typeof node.value];
|
1098
1173
|
}
|
1099
1174
|
|
@@ -1107,6 +1182,15 @@ const getNodeType = (scope, node) => {
|
|
1107
1182
|
|
1108
1183
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
1184
|
const name = node.callee.name;
|
1185
|
+
if (!name) {
|
1186
|
+
// iife
|
1187
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1188
|
+
|
1189
|
+
// presume
|
1190
|
+
// todo: warn here?
|
1191
|
+
return TYPES.number;
|
1192
|
+
}
|
1193
|
+
|
1110
1194
|
const func = funcs.find(x => x.name === name);
|
1111
1195
|
|
1112
1196
|
if (func) {
|
@@ -1114,7 +1198,7 @@ const getNodeType = (scope, node) => {
|
|
1114
1198
|
if (func.returnType) return func.returnType;
|
1115
1199
|
}
|
1116
1200
|
|
1117
|
-
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1201
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1118
1202
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1119
1203
|
|
1120
1204
|
// check if this is a prototype function
|
@@ -1125,10 +1209,15 @@ const getNodeType = (scope, node) => {
|
|
1125
1209
|
const spl = name.slice(2).split('_');
|
1126
1210
|
|
1127
1211
|
const func = spl[spl.length - 1];
|
1128
|
-
const protoFuncs = Object.
|
1212
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1129
1213
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
1214
|
}
|
1131
1215
|
|
1216
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1217
|
+
// todo: return undefined for non-returning ops
|
1218
|
+
return TYPES.number;
|
1219
|
+
}
|
1220
|
+
|
1132
1221
|
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1133
1222
|
|
1134
1223
|
// presume
|
@@ -1177,6 +1266,14 @@ const getNodeType = (scope, node) => {
|
|
1177
1266
|
|
1178
1267
|
if (node.type === 'BinaryExpression') {
|
1179
1268
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1269
|
+
if (node.operator !== '+') return TYPES.number;
|
1270
|
+
|
1271
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1272
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1273
|
+
|
1274
|
+
// todo: this should be dynamic but for now only static
|
1275
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1276
|
+
|
1180
1277
|
return TYPES.number;
|
1181
1278
|
|
1182
1279
|
// todo: string concat types
|
@@ -1201,7 +1298,7 @@ const getNodeType = (scope, node) => {
|
|
1201
1298
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1299
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1300
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1301
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1205
1302
|
|
1206
1303
|
return TYPES.number;
|
1207
1304
|
}
|
@@ -1210,7 +1307,13 @@ const getNodeType = (scope, node) => {
|
|
1210
1307
|
// hack: if something.length, number type
|
1211
1308
|
if (node.property.name === 'length') return TYPES.number;
|
1212
1309
|
|
1213
|
-
//
|
1310
|
+
// ts hack
|
1311
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1312
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1313
|
+
|
1314
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1315
|
+
|
1316
|
+
// presume
|
1214
1317
|
return TYPES.number;
|
1215
1318
|
}
|
1216
1319
|
|
@@ -1230,8 +1333,8 @@ const getNodeType = (scope, node) => {
|
|
1230
1333
|
const generateLiteral = (scope, decl, global, name) => {
|
1231
1334
|
if (decl.value === null) return number(NULL);
|
1232
1335
|
|
1336
|
+
// hack: just return 1 for regex literals
|
1233
1337
|
if (decl.regex) {
|
1234
|
-
scope.regex[name] = decl.regex;
|
1235
1338
|
return number(1);
|
1236
1339
|
}
|
1237
1340
|
|
@@ -1244,16 +1347,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1244
1347
|
return number(decl.value ? 1 : 0);
|
1245
1348
|
|
1246
1349
|
case 'string':
|
1247
|
-
|
1248
|
-
const rawElements = new Array(str.length);
|
1249
|
-
let j = 0;
|
1250
|
-
for (let i = 0; i < str.length; i++) {
|
1251
|
-
rawElements[i] = str.charCodeAt(i);
|
1252
|
-
}
|
1253
|
-
|
1254
|
-
return makeArray(scope, {
|
1255
|
-
rawElements
|
1256
|
-
}, global, name, false, 'i16')[0];
|
1350
|
+
return makeString(scope, decl.value, global, name);
|
1257
1351
|
|
1258
1352
|
default:
|
1259
1353
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1273,10 +1367,10 @@ const countLeftover = wasm => {
|
|
1273
1367
|
if (inst[0] === Opcodes.end) depth--;
|
1274
1368
|
|
1275
1369
|
if (depth === 0)
|
1276
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1277
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1370
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1371
|
+
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.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1278
1372
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1279
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1373
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1280
1374
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1281
1375
|
else if (inst[0] === Opcodes.return) count = 0;
|
1282
1376
|
else if (inst[0] === Opcodes.call) {
|
@@ -1302,7 +1396,7 @@ const disposeLeftover = wasm => {
|
|
1302
1396
|
const generateExp = (scope, decl) => {
|
1303
1397
|
const expression = decl.expression;
|
1304
1398
|
|
1305
|
-
const out = generate(scope, expression);
|
1399
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1306
1400
|
disposeLeftover(out);
|
1307
1401
|
|
1308
1402
|
return out;
|
@@ -1360,7 +1454,7 @@ const RTArrayUtil = {
|
|
1360
1454
|
]
|
1361
1455
|
};
|
1362
1456
|
|
1363
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1457
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1364
1458
|
/* const callee = decl.callee;
|
1365
1459
|
const args = decl.arguments;
|
1366
1460
|
|
@@ -1426,8 +1520,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1426
1520
|
// literal.func()
|
1427
1521
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1428
1522
|
// megahack for /regex/.func()
|
1429
|
-
|
1430
|
-
|
1523
|
+
const funcName = decl.callee.property.name;
|
1524
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1431
1525
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1432
1526
|
|
1433
1527
|
funcIndex[func.name] = func.index;
|
@@ -1469,8 +1563,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1469
1563
|
|
1470
1564
|
if (protoName) {
|
1471
1565
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1472
|
-
|
1473
|
-
if (f) acc[x] = f;
|
1566
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1474
1567
|
return acc;
|
1475
1568
|
}, {});
|
1476
1569
|
|
@@ -1479,10 +1572,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1479
1572
|
// use local for cached i32 length as commonly used
|
1480
1573
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1481
1574
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1482
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1483
1575
|
|
1484
1576
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1485
1577
|
|
1578
|
+
const rawPointer = [
|
1579
|
+
...generate(scope, target),
|
1580
|
+
Opcodes.i32_to_u
|
1581
|
+
];
|
1582
|
+
|
1583
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1584
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1585
|
+
|
1586
|
+
let allOptUnused = true;
|
1486
1587
|
let lengthI32CacheUsed = false;
|
1487
1588
|
const protoBC = {};
|
1488
1589
|
for (const x in protoCands) {
|
@@ -1502,6 +1603,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1502
1603
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1503
1604
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1504
1605
|
|
1606
|
+
let optUnused = false;
|
1505
1607
|
const protoOut = protoFunc(getPointer, {
|
1506
1608
|
getCachedI32: () => {
|
1507
1609
|
lengthI32CacheUsed = true;
|
@@ -1516,10 +1618,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1516
1618
|
return makeArray(scope, {
|
1517
1619
|
rawElements: new Array(length)
|
1518
1620
|
}, _global, _name, true, itemType);
|
1621
|
+
}, () => {
|
1622
|
+
optUnused = true;
|
1623
|
+
return unusedValue;
|
1519
1624
|
});
|
1520
1625
|
|
1626
|
+
if (!optUnused) allOptUnused = false;
|
1627
|
+
|
1521
1628
|
protoBC[x] = [
|
1522
|
-
[ Opcodes.block, valtypeBinary ],
|
1629
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1523
1630
|
...protoOut,
|
1524
1631
|
|
1525
1632
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1528,11 +1635,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1528
1635
|
];
|
1529
1636
|
}
|
1530
1637
|
|
1531
|
-
|
1532
|
-
...generate(scope, target),
|
1638
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1533
1639
|
|
1534
|
-
|
1535
|
-
|
1640
|
+
return [
|
1641
|
+
...(usePointerCache ? [
|
1642
|
+
...rawPointer,
|
1643
|
+
[ Opcodes.local_set, pointerLocal ],
|
1644
|
+
] : []),
|
1536
1645
|
|
1537
1646
|
...(!lengthI32CacheUsed ? [] : [
|
1538
1647
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1544,7 +1653,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1544
1653
|
|
1545
1654
|
// TODO: error better
|
1546
1655
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1547
|
-
}, valtypeBinary),
|
1656
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1548
1657
|
];
|
1549
1658
|
}
|
1550
1659
|
}
|
@@ -1583,6 +1692,32 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1583
1692
|
idx = -1;
|
1584
1693
|
}
|
1585
1694
|
|
1695
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1696
|
+
const wasmOps = {
|
1697
|
+
// pointer, align, offset
|
1698
|
+
i32_load8_u: { imms: 2, args: 1 },
|
1699
|
+
// pointer, value, align, offset
|
1700
|
+
i32_store8: { imms: 2, args: 2 },
|
1701
|
+
};
|
1702
|
+
|
1703
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1704
|
+
|
1705
|
+
if (wasmOps[opName]) {
|
1706
|
+
const op = wasmOps[opName];
|
1707
|
+
|
1708
|
+
const argOut = [];
|
1709
|
+
for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
|
1710
|
+
|
1711
|
+
// literals only
|
1712
|
+
const imms = decl.arguments.slice(op.args).map(x => x.value);
|
1713
|
+
|
1714
|
+
return [
|
1715
|
+
...argOut,
|
1716
|
+
[ Opcodes[opName], ...imms ]
|
1717
|
+
];
|
1718
|
+
}
|
1719
|
+
}
|
1720
|
+
|
1586
1721
|
if (idx === undefined) {
|
1587
1722
|
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
|
1588
1723
|
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
@@ -1591,7 +1726,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1591
1726
|
const func = funcs.find(x => x.index === idx);
|
1592
1727
|
|
1593
1728
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1594
|
-
const
|
1729
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1730
|
+
const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
|
1731
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1595
1732
|
|
1596
1733
|
let args = decl.arguments;
|
1597
1734
|
if (func && args.length < paramCount) {
|
@@ -1607,14 +1744,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1607
1744
|
if (func && func.throws) scope.throws = true;
|
1608
1745
|
|
1609
1746
|
let out = [];
|
1610
|
-
for (
|
1747
|
+
for (let i = 0; i < args.length; i++) {
|
1748
|
+
const arg = args[i];
|
1611
1749
|
out = out.concat(generate(scope, arg));
|
1612
|
-
|
1750
|
+
|
1751
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1752
|
+
out.push(Opcodes.i32_to);
|
1753
|
+
}
|
1754
|
+
|
1755
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1613
1756
|
}
|
1614
1757
|
|
1615
1758
|
out.push([ Opcodes.call, idx ]);
|
1616
1759
|
|
1617
|
-
if (!
|
1760
|
+
if (!typedReturns) {
|
1618
1761
|
// let type;
|
1619
1762
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1620
1763
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1626,6 +1769,10 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1626
1769
|
// );
|
1627
1770
|
} else out.push(setLastType(scope));
|
1628
1771
|
|
1772
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1773
|
+
out.push(Opcodes.i32_from);
|
1774
|
+
}
|
1775
|
+
|
1629
1776
|
return out;
|
1630
1777
|
};
|
1631
1778
|
|
@@ -1665,14 +1812,102 @@ const knownType = (scope, type) => {
|
|
1665
1812
|
return null;
|
1666
1813
|
};
|
1667
1814
|
|
1815
|
+
const brTable = (input, bc, returns) => {
|
1816
|
+
const out = [];
|
1817
|
+
const keys = Object.keys(bc);
|
1818
|
+
const count = keys.length;
|
1819
|
+
|
1820
|
+
if (count === 1) {
|
1821
|
+
// return [
|
1822
|
+
// ...input,
|
1823
|
+
// ...bc[keys[0]]
|
1824
|
+
// ];
|
1825
|
+
return bc[keys[0]];
|
1826
|
+
}
|
1827
|
+
|
1828
|
+
if (count === 2) {
|
1829
|
+
// just use if else
|
1830
|
+
const other = keys.find(x => x !== 'default');
|
1831
|
+
return [
|
1832
|
+
...input,
|
1833
|
+
...number(other, Valtype.i32),
|
1834
|
+
[ Opcodes.i32_eq ],
|
1835
|
+
[ Opcodes.if, returns ],
|
1836
|
+
...bc[other],
|
1837
|
+
[ Opcodes.else ],
|
1838
|
+
...bc.default,
|
1839
|
+
[ Opcodes.end ]
|
1840
|
+
];
|
1841
|
+
}
|
1842
|
+
|
1843
|
+
for (let i = 0; i < count; i++) {
|
1844
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1845
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1846
|
+
}
|
1847
|
+
|
1848
|
+
const nums = keys.filter(x => +x);
|
1849
|
+
const offset = Math.min(...nums);
|
1850
|
+
const max = Math.max(...nums);
|
1851
|
+
|
1852
|
+
const table = [];
|
1853
|
+
let br = 1;
|
1854
|
+
|
1855
|
+
for (let i = offset; i <= max; i++) {
|
1856
|
+
// if branch for this num, go to that block
|
1857
|
+
if (bc[i]) {
|
1858
|
+
table.push(br);
|
1859
|
+
br++;
|
1860
|
+
continue;
|
1861
|
+
}
|
1862
|
+
|
1863
|
+
// else default
|
1864
|
+
table.push(0);
|
1865
|
+
}
|
1866
|
+
|
1867
|
+
out.push(
|
1868
|
+
[ Opcodes.block, Blocktype.void ],
|
1869
|
+
...input,
|
1870
|
+
...(offset > 0 ? [
|
1871
|
+
...number(offset, Valtype.i32),
|
1872
|
+
[ Opcodes.i32_sub ]
|
1873
|
+
] : []),
|
1874
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1875
|
+
);
|
1876
|
+
|
1877
|
+
// if you can guess why we sort the wrong way and then reverse
|
1878
|
+
// (instead of just sorting the correct way)
|
1879
|
+
// dm me and if you are correct and the first person
|
1880
|
+
// I will somehow shout you out or something
|
1881
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1882
|
+
|
1883
|
+
br = count - 1;
|
1884
|
+
for (const x of orderedBc) {
|
1885
|
+
out.push(
|
1886
|
+
[ Opcodes.end ],
|
1887
|
+
...bc[x],
|
1888
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1889
|
+
);
|
1890
|
+
br--;
|
1891
|
+
}
|
1892
|
+
|
1893
|
+
return [
|
1894
|
+
...out,
|
1895
|
+
[ Opcodes.end, 'br table end' ]
|
1896
|
+
];
|
1897
|
+
};
|
1898
|
+
|
1668
1899
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1900
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
1901
|
+
|
1669
1902
|
const known = knownType(scope, type);
|
1670
1903
|
if (known != null) {
|
1671
1904
|
return bc[known] ?? bc.default;
|
1672
1905
|
}
|
1673
1906
|
|
1674
|
-
|
1907
|
+
if (Prefs.typeswitchUseBrtable)
|
1908
|
+
return brTable(type, bc, returns);
|
1675
1909
|
|
1910
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1676
1911
|
const out = [
|
1677
1912
|
...type,
|
1678
1913
|
[ Opcodes.local_set, tmp ],
|
@@ -1704,7 +1939,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1704
1939
|
return out;
|
1705
1940
|
};
|
1706
1941
|
|
1707
|
-
const allocVar = (scope, name, global = false) => {
|
1942
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1708
1943
|
const target = global ? globals : scope.locals;
|
1709
1944
|
|
1710
1945
|
// already declared
|
@@ -1718,8 +1953,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1718
1953
|
let idx = global ? globalInd++ : scope.localInd++;
|
1719
1954
|
target[name] = { idx, type: valtypeBinary };
|
1720
1955
|
|
1721
|
-
|
1722
|
-
|
1956
|
+
if (type) {
|
1957
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
1958
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
1959
|
+
}
|
1723
1960
|
|
1724
1961
|
return idx;
|
1725
1962
|
};
|
@@ -1739,6 +1976,7 @@ const typeAnnoToPorfType = x => {
|
|
1739
1976
|
|
1740
1977
|
switch (x) {
|
1741
1978
|
case 'i32':
|
1979
|
+
case 'i64':
|
1742
1980
|
return TYPES.number;
|
1743
1981
|
}
|
1744
1982
|
|
@@ -1762,6 +2000,8 @@ const extractTypeAnnotation = decl => {
|
|
1762
2000
|
const typeName = type;
|
1763
2001
|
type = typeAnnoToPorfType(type);
|
1764
2002
|
|
2003
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2004
|
+
|
1765
2005
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1766
2006
|
|
1767
2007
|
return { type, typeName, elementType };
|
@@ -1778,6 +2018,8 @@ const generateVar = (scope, decl) => {
|
|
1778
2018
|
for (const x of decl.declarations) {
|
1779
2019
|
const name = mapName(x.id.name);
|
1780
2020
|
|
2021
|
+
if (!name) return todo('destructuring is not supported yet');
|
2022
|
+
|
1781
2023
|
if (x.init && isFuncType(x.init.type)) {
|
1782
2024
|
// hack for let a = function () { ... }
|
1783
2025
|
x.init.id = { name };
|
@@ -1793,7 +2035,12 @@ const generateVar = (scope, decl) => {
|
|
1793
2035
|
continue; // always ignore
|
1794
2036
|
}
|
1795
2037
|
|
1796
|
-
let idx = allocVar(scope, name, global);
|
2038
|
+
let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
|
2039
|
+
|
2040
|
+
if (typedInput && x.id.typeAnnotation) {
|
2041
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
2042
|
+
}
|
2043
|
+
|
1797
2044
|
if (x.init) {
|
1798
2045
|
out = out.concat(generate(scope, x.init, global, name));
|
1799
2046
|
|
@@ -1803,16 +2050,13 @@ const generateVar = (scope, decl) => {
|
|
1803
2050
|
|
1804
2051
|
// hack: this follows spec properly but is mostly unneeded 😅
|
1805
2052
|
// out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
|
1806
|
-
|
1807
|
-
if (typedInput && x.id.typeAnnotation) {
|
1808
|
-
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1809
|
-
}
|
1810
2053
|
}
|
1811
2054
|
|
1812
2055
|
return out;
|
1813
2056
|
};
|
1814
2057
|
|
1815
|
-
|
2058
|
+
// todo: optimize this func for valueUnused
|
2059
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1816
2060
|
const { type, name } = decl.left;
|
1817
2061
|
|
1818
2062
|
if (type === 'ObjectPattern') {
|
@@ -1915,6 +2159,8 @@ const generateAssign = (scope, decl) => {
|
|
1915
2159
|
];
|
1916
2160
|
}
|
1917
2161
|
|
2162
|
+
if (!name) return todo('destructuring is not supported yet');
|
2163
|
+
|
1918
2164
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1919
2165
|
|
1920
2166
|
if (local === undefined) {
|
@@ -1961,9 +2207,7 @@ const generateAssign = (scope, decl) => {
|
|
1961
2207
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1962
2208
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1963
2209
|
|
1964
|
-
getLastType(scope)
|
1965
|
-
// hack: type is idx+1
|
1966
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2210
|
+
...setType(scope, name, getLastType(scope))
|
1967
2211
|
];
|
1968
2212
|
}
|
1969
2213
|
|
@@ -1974,9 +2218,7 @@ const generateAssign = (scope, decl) => {
|
|
1974
2218
|
|
1975
2219
|
// todo: string concat types
|
1976
2220
|
|
1977
|
-
|
1978
|
-
...number(TYPES.number, Valtype.i32),
|
1979
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2221
|
+
...setType(scope, name, TYPES.number)
|
1980
2222
|
];
|
1981
2223
|
};
|
1982
2224
|
|
@@ -2053,6 +2295,8 @@ const generateUnary = (scope, decl) => {
|
|
2053
2295
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2054
2296
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2055
2297
|
|
2298
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2299
|
+
|
2056
2300
|
// object and internal types
|
2057
2301
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2058
2302
|
});
|
@@ -2062,7 +2306,7 @@ const generateUnary = (scope, decl) => {
|
|
2062
2306
|
}
|
2063
2307
|
};
|
2064
2308
|
|
2065
|
-
const generateUpdate = (scope, decl) => {
|
2309
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2066
2310
|
const { name } = decl.argument;
|
2067
2311
|
|
2068
2312
|
const [ local, isGlobal ] = lookupName(scope, name);
|
@@ -2075,7 +2319,7 @@ const generateUpdate = (scope, decl) => {
|
|
2075
2319
|
const out = [];
|
2076
2320
|
|
2077
2321
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2078
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2322
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2079
2323
|
|
2080
2324
|
switch (decl.operator) {
|
2081
2325
|
case '++':
|
@@ -2088,7 +2332,7 @@ const generateUpdate = (scope, decl) => {
|
|
2088
2332
|
}
|
2089
2333
|
|
2090
2334
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2091
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2335
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2092
2336
|
|
2093
2337
|
return out;
|
2094
2338
|
};
|
@@ -2151,15 +2395,17 @@ const generateFor = (scope, decl) => {
|
|
2151
2395
|
const out = [];
|
2152
2396
|
|
2153
2397
|
if (decl.init) {
|
2154
|
-
out.push(...generate(scope, decl.init));
|
2398
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2155
2399
|
disposeLeftover(out);
|
2156
2400
|
}
|
2157
2401
|
|
2158
2402
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2159
2403
|
depth.push('for');
|
2160
2404
|
|
2161
|
-
out.push(...generate(scope, decl.test));
|
2162
|
-
|
2405
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2406
|
+
else out.push(...number(1, Valtype.i32));
|
2407
|
+
|
2408
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2163
2409
|
depth.push('if');
|
2164
2410
|
|
2165
2411
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2167,8 +2413,7 @@ const generateFor = (scope, decl) => {
|
|
2167
2413
|
out.push(...generate(scope, decl.body));
|
2168
2414
|
out.push([ Opcodes.end ]);
|
2169
2415
|
|
2170
|
-
out.push(...generate(scope, decl.update));
|
2171
|
-
depth.pop();
|
2416
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2172
2417
|
|
2173
2418
|
out.push([ Opcodes.br, 1 ]);
|
2174
2419
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2225,7 +2470,13 @@ const generateForOf = (scope, decl) => {
|
|
2225
2470
|
// setup local for left
|
2226
2471
|
generate(scope, decl.left);
|
2227
2472
|
|
2228
|
-
|
2473
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2474
|
+
if (!leftName && decl.left.name) {
|
2475
|
+
leftName = decl.left.name;
|
2476
|
+
|
2477
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2478
|
+
}
|
2479
|
+
|
2229
2480
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2230
2481
|
|
2231
2482
|
depth.push('block');
|
@@ -2234,13 +2485,14 @@ const generateForOf = (scope, decl) => {
|
|
2234
2485
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2235
2486
|
// hack: this is naughty and will break things!
|
2236
2487
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2237
|
-
if (pages.
|
2488
|
+
if (pages.hasAnyString) {
|
2238
2489
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2239
2490
|
rawElements: new Array(1)
|
2240
2491
|
}, isGlobal, leftName, true, 'i16');
|
2241
2492
|
}
|
2242
2493
|
|
2243
2494
|
// set type for local
|
2495
|
+
// todo: optimize away counter and use end pointer
|
2244
2496
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2245
2497
|
[TYPES._array]: [
|
2246
2498
|
...setType(scope, leftName, TYPES.number),
|
@@ -2365,7 +2617,7 @@ const generateThrow = (scope, decl) => {
|
|
2365
2617
|
// hack: throw new X("...") -> throw "..."
|
2366
2618
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2367
2619
|
constructor = decl.argument.callee.name;
|
2368
|
-
message = decl.argument.arguments[0]
|
2620
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2369
2621
|
}
|
2370
2622
|
|
2371
2623
|
if (tags.length === 0) tags.push({
|
@@ -2377,6 +2629,9 @@ const generateThrow = (scope, decl) => {
|
|
2377
2629
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2378
2630
|
let tagIdx = tags[0].idx;
|
2379
2631
|
|
2632
|
+
scope.exceptions ??= [];
|
2633
|
+
scope.exceptions.push(exceptId);
|
2634
|
+
|
2380
2635
|
// todo: write a description of how this works lol
|
2381
2636
|
|
2382
2637
|
return [
|
@@ -2421,25 +2676,31 @@ const generateAssignPat = (scope, decl) => {
|
|
2421
2676
|
};
|
2422
2677
|
|
2423
2678
|
let pages = new Map();
|
2424
|
-
const allocPage = (reason, type) => {
|
2679
|
+
const allocPage = (scope, reason, type) => {
|
2425
2680
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2426
2681
|
|
2427
2682
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2428
2683
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2684
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2685
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2429
2686
|
|
2430
2687
|
const ind = pages.size;
|
2431
2688
|
pages.set(reason, { ind, type });
|
2432
2689
|
|
2433
|
-
|
2690
|
+
scope.pages ??= new Map();
|
2691
|
+
scope.pages.set(reason, { ind, type });
|
2692
|
+
|
2693
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2434
2694
|
|
2435
2695
|
return ind;
|
2436
2696
|
};
|
2437
2697
|
|
2698
|
+
// todo: add scope.pages
|
2438
2699
|
const freePage = reason => {
|
2439
2700
|
const { ind } = pages.get(reason);
|
2440
2701
|
pages.delete(reason);
|
2441
2702
|
|
2442
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2703
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2443
2704
|
|
2444
2705
|
return ind;
|
2445
2706
|
};
|
@@ -2459,25 +2720,34 @@ const StoreOps = {
|
|
2459
2720
|
f64: Opcodes.f64_store,
|
2460
2721
|
|
2461
2722
|
// expects i32 input!
|
2462
|
-
|
2723
|
+
i8: Opcodes.i32_store8,
|
2724
|
+
i16: Opcodes.i32_store16,
|
2463
2725
|
};
|
2464
2726
|
|
2465
2727
|
let data = [];
|
2466
2728
|
|
2467
|
-
const compileBytes = (val, itemType
|
2729
|
+
const compileBytes = (val, itemType) => {
|
2468
2730
|
// todo: this is a mess and needs confirming / ????
|
2469
2731
|
switch (itemType) {
|
2470
2732
|
case 'i8': return [ val % 256 ];
|
2471
|
-
case 'i16': return [ val % 256,
|
2472
|
-
|
2473
|
-
case 'i32':
|
2474
|
-
|
2475
|
-
return enforceFourBytes(signedLEB128(val));
|
2733
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2734
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2735
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
2736
|
+
// todo: i64
|
2476
2737
|
|
2477
2738
|
case 'f64': return ieee754_binary64(val);
|
2478
2739
|
}
|
2479
2740
|
};
|
2480
2741
|
|
2742
|
+
const getAllocType = itemType => {
|
2743
|
+
switch (itemType) {
|
2744
|
+
case 'i8': return 'bytestring';
|
2745
|
+
case 'i16': return 'string';
|
2746
|
+
|
2747
|
+
default: return 'array';
|
2748
|
+
}
|
2749
|
+
};
|
2750
|
+
|
2481
2751
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2482
2752
|
const out = [];
|
2483
2753
|
|
@@ -2487,7 +2757,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2487
2757
|
|
2488
2758
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2489
2759
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2490
|
-
arrays.set(name, allocPage(`${itemType
|
2760
|
+
arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2491
2761
|
}
|
2492
2762
|
|
2493
2763
|
const pointer = arrays.get(name);
|
@@ -2498,19 +2768,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2498
2768
|
const valtype = itemTypeToValtype[itemType];
|
2499
2769
|
const length = elements.length;
|
2500
2770
|
|
2501
|
-
if (firstAssign && useRawElements) {
|
2502
|
-
|
2771
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
2772
|
+
// if length is 0 memory/data will just be 0000... anyway
|
2773
|
+
if (length !== 0) {
|
2774
|
+
let bytes = compileBytes(length, 'i32');
|
2503
2775
|
|
2504
|
-
|
2505
|
-
|
2776
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2777
|
+
if (elements[i] == null) continue;
|
2506
2778
|
|
2507
|
-
|
2508
|
-
|
2779
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2780
|
+
}
|
2509
2781
|
|
2510
|
-
|
2511
|
-
|
2512
|
-
|
2513
|
-
|
2782
|
+
const ind = data.push({
|
2783
|
+
offset: pointer,
|
2784
|
+
bytes
|
2785
|
+
}) - 1;
|
2786
|
+
|
2787
|
+
scope.data ??= [];
|
2788
|
+
scope.data.push(ind);
|
2789
|
+
}
|
2514
2790
|
|
2515
2791
|
// local value as pointer
|
2516
2792
|
out.push(...number(pointer));
|
@@ -2533,7 +2809,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2533
2809
|
out.push(
|
2534
2810
|
...number(0, Valtype.i32),
|
2535
2811
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2536
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2812
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2537
2813
|
);
|
2538
2814
|
}
|
2539
2815
|
|
@@ -2543,15 +2819,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2543
2819
|
return [ out, pointer ];
|
2544
2820
|
};
|
2545
2821
|
|
2546
|
-
const
|
2822
|
+
const byteStringable = str => {
|
2823
|
+
if (!Prefs.bytestring) return false;
|
2824
|
+
|
2825
|
+
for (let i = 0; i < str.length; i++) {
|
2826
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2827
|
+
}
|
2828
|
+
|
2829
|
+
return true;
|
2830
|
+
};
|
2831
|
+
|
2832
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2547
2833
|
const rawElements = new Array(str.length);
|
2834
|
+
let byteStringable = Prefs.bytestring;
|
2548
2835
|
for (let i = 0; i < str.length; i++) {
|
2549
|
-
|
2836
|
+
const c = str.charCodeAt(i);
|
2837
|
+
rawElements[i] = c;
|
2838
|
+
|
2839
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2550
2840
|
}
|
2551
2841
|
|
2842
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2843
|
+
|
2552
2844
|
return makeArray(scope, {
|
2553
2845
|
rawElements
|
2554
|
-
}, global, name, false, 'i16')[0];
|
2846
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2555
2847
|
};
|
2556
2848
|
|
2557
2849
|
let arrays = new Map();
|
@@ -2579,10 +2871,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2579
2871
|
];
|
2580
2872
|
}
|
2581
2873
|
|
2874
|
+
const object = generate(scope, decl.object);
|
2875
|
+
const property = generate(scope, decl.property);
|
2876
|
+
|
2582
2877
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2583
2878
|
// hack: this is naughty and will break things!
|
2584
|
-
let newOut = number(0,
|
2585
|
-
if (pages.
|
2879
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2880
|
+
if (pages.hasAnyString) {
|
2586
2881
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2587
2882
|
rawElements: new Array(1)
|
2588
2883
|
}, _global, _name, true, 'i16');
|
@@ -2591,7 +2886,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2591
2886
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2592
2887
|
[TYPES._array]: [
|
2593
2888
|
// get index as valtype
|
2594
|
-
...
|
2889
|
+
...property,
|
2595
2890
|
|
2596
2891
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2597
2892
|
Opcodes.i32_to_u,
|
@@ -2599,7 +2894,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2599
2894
|
[ Opcodes.i32_mul ],
|
2600
2895
|
|
2601
2896
|
...(aotPointer ? [] : [
|
2602
|
-
...
|
2897
|
+
...object,
|
2603
2898
|
Opcodes.i32_to_u,
|
2604
2899
|
[ Opcodes.i32_add ]
|
2605
2900
|
]),
|
@@ -2618,14 +2913,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2618
2913
|
|
2619
2914
|
...number(0, Valtype.i32), // base 0 for store later
|
2620
2915
|
|
2621
|
-
...
|
2622
|
-
|
2916
|
+
...property,
|
2623
2917
|
Opcodes.i32_to_u,
|
2918
|
+
|
2624
2919
|
...number(ValtypeSize.i16, Valtype.i32),
|
2625
2920
|
[ Opcodes.i32_mul ],
|
2626
2921
|
|
2627
2922
|
...(aotPointer ? [] : [
|
2628
|
-
...
|
2923
|
+
...object,
|
2629
2924
|
Opcodes.i32_to_u,
|
2630
2925
|
[ Opcodes.i32_add ]
|
2631
2926
|
]),
|
@@ -2642,8 +2937,36 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2642
2937
|
...number(TYPES.string, Valtype.i32),
|
2643
2938
|
setLastType(scope)
|
2644
2939
|
],
|
2940
|
+
[TYPES._bytestring]: [
|
2941
|
+
// setup new/out array
|
2942
|
+
...newOut,
|
2943
|
+
[ Opcodes.drop ],
|
2944
|
+
|
2945
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2946
|
+
|
2947
|
+
...property,
|
2948
|
+
Opcodes.i32_to_u,
|
2949
|
+
|
2950
|
+
...(aotPointer ? [] : [
|
2951
|
+
...object,
|
2952
|
+
Opcodes.i32_to_u,
|
2953
|
+
[ Opcodes.i32_add ]
|
2954
|
+
]),
|
2955
|
+
|
2956
|
+
// load current string ind {arg}
|
2957
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2958
|
+
|
2959
|
+
// store to new string ind 0
|
2960
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2961
|
+
|
2962
|
+
// return new string (page)
|
2963
|
+
...number(newPointer),
|
2964
|
+
|
2965
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2966
|
+
setLastType(scope)
|
2967
|
+
],
|
2645
2968
|
|
2646
|
-
default:
|
2969
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2647
2970
|
});
|
2648
2971
|
};
|
2649
2972
|
|
@@ -2660,13 +2983,16 @@ const objectHack = node => {
|
|
2660
2983
|
// if object is not identifier or another member exp, give up
|
2661
2984
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2662
2985
|
|
2663
|
-
if (!objectName) objectName = objectHack(node.object)
|
2986
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2664
2987
|
|
2665
2988
|
// if .length, give up (hack within a hack!)
|
2666
2989
|
if (node.property.name === 'length') return node;
|
2667
2990
|
|
2991
|
+
// no object name, give up
|
2992
|
+
if (!objectName) return node;
|
2993
|
+
|
2668
2994
|
const name = '__' + objectName + '_' + node.property.name;
|
2669
|
-
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2995
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2670
2996
|
|
2671
2997
|
return {
|
2672
2998
|
type: 'Identifier',
|
@@ -2723,13 +3049,13 @@ const generateFunc = (scope, decl) => {
|
|
2723
3049
|
const func = {
|
2724
3050
|
name,
|
2725
3051
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2726
|
-
|
2727
|
-
|
2728
|
-
throws: innerScope.throws,
|
2729
|
-
index: currentFuncIndex++
|
3052
|
+
index: currentFuncIndex++,
|
3053
|
+
...innerScope
|
2730
3054
|
};
|
2731
3055
|
funcIndex[name] = func.index;
|
2732
3056
|
|
3057
|
+
if (name === 'main') func.gotLastType = true;
|
3058
|
+
|
2733
3059
|
// quick hack fixes
|
2734
3060
|
for (const inst of wasm) {
|
2735
3061
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2764,6 +3090,16 @@ const generateCode = (scope, decl) => {
|
|
2764
3090
|
};
|
2765
3091
|
|
2766
3092
|
const internalConstrs = {
|
3093
|
+
Boolean: {
|
3094
|
+
generate: (scope, decl) => {
|
3095
|
+
if (decl.arguments.length === 0) return number(0);
|
3096
|
+
|
3097
|
+
// should generate/run all args
|
3098
|
+
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3099
|
+
},
|
3100
|
+
type: TYPES.boolean
|
3101
|
+
},
|
3102
|
+
|
2767
3103
|
Array: {
|
2768
3104
|
generate: (scope, decl, global, name) => {
|
2769
3105
|
// new Array(i0, i1, ...)
|
@@ -2885,7 +3221,7 @@ export default program => {
|
|
2885
3221
|
body: program.body
|
2886
3222
|
};
|
2887
3223
|
|
2888
|
-
if (
|
3224
|
+
if (Prefs.astLog) console.log(program.body.body);
|
2889
3225
|
|
2890
3226
|
generateFunc(scope, program);
|
2891
3227
|
|