porffor 0.2.0-09999e8 → 0.2.0-181627c
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 +81 -47
- package/compiler/2c.js +316 -71
- package/compiler/builtins/base64.ts +108 -0
- package/compiler/builtins/porffor.d.ts +15 -0
- package/compiler/builtins.js +254 -60
- package/compiler/codeGen.js +430 -156
- package/compiler/decompile.js +3 -3
- package/compiler/generated_builtins.js +3 -0
- package/compiler/index.js +22 -22
- package/compiler/opt.js +52 -27
- package/compiler/parse.js +4 -2
- package/compiler/precompile.js +89 -0
- package/compiler/prefs.js +22 -0
- package/compiler/prototype.js +176 -20
- package/compiler/sections.js +8 -7
- package/compiler/wasmSpec.js +6 -2
- package/compiler/wrap.js +103 -9
- package/demo.js +3 -0
- package/demo.ts +1 -0
- package/filesize.cmd +2 -0
- package/hello +0 -0
- package/package.json +1 -1
- package/porf +2 -0
- package/rhemyn/compile.js +2 -1
- package/runner/index.js +20 -3
- package/tmp.c +152 -0
- package/compiler/builtins/base64.js +0 -92
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);
|
@@ -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,7 @@ 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 => parseInt(x));
|
192
|
+
const immediates = asm.slice(1).map(x => parseInt(x) || scope.locals[x]?.idx);
|
187
193
|
|
188
194
|
out.push([ ...inst, ...immediates ]);
|
189
195
|
}
|
@@ -191,31 +197,41 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
191
197
|
return out;
|
192
198
|
},
|
193
199
|
|
194
|
-
|
195
|
-
|
200
|
+
__Porffor_bs: str => [
|
201
|
+
...makeString(scope, str, undefined, undefined, true),
|
196
202
|
|
197
|
-
|
198
|
-
|
199
|
-
|
203
|
+
...number(TYPES._bytestring, Valtype.i32),
|
204
|
+
setLastType(scope)
|
205
|
+
],
|
206
|
+
__Porffor_s: str => [
|
207
|
+
...makeString(scope, str, undefined, undefined, false),
|
200
208
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
}
|
206
|
-
}
|
209
|
+
...number(TYPES.string, Valtype.i32),
|
210
|
+
setLastType(scope)
|
211
|
+
],
|
212
|
+
};
|
207
213
|
|
208
214
|
const name = decl.tag.name;
|
209
215
|
// hack for inline asm
|
210
216
|
if (!funcs[name]) return todo('tagged template expressions not implemented');
|
211
217
|
|
212
|
-
const
|
218
|
+
const { quasis, expressions } = decl.quasi;
|
219
|
+
let str = quasis[0].value.raw;
|
220
|
+
|
221
|
+
for (let i = 0; i < expressions.length; i++) {
|
222
|
+
const e = expressions[i];
|
223
|
+
str += lookupName(scope, e.name)[0];
|
224
|
+
|
225
|
+
str += quasis[i + 1].value.raw;
|
226
|
+
}
|
227
|
+
|
213
228
|
return funcs[name](str);
|
214
229
|
}
|
215
230
|
|
216
231
|
default:
|
217
|
-
|
218
|
-
|
232
|
+
// ignore typescript nodes
|
233
|
+
if (decl.type.startsWith('TS') ||
|
234
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
219
235
|
return [];
|
220
236
|
}
|
221
237
|
|
@@ -269,25 +285,25 @@ const generateIdent = (scope, decl) => {
|
|
269
285
|
const name = mapName(rawName);
|
270
286
|
let local = scope.locals[rawName];
|
271
287
|
|
272
|
-
if (builtinVars
|
288
|
+
if (Object.hasOwn(builtinVars, name)) {
|
273
289
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
274
290
|
return builtinVars[name];
|
275
291
|
}
|
276
292
|
|
277
|
-
if (builtinFuncs
|
293
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
278
294
|
// todo: return an actual something
|
279
295
|
return number(1);
|
280
296
|
}
|
281
297
|
|
282
|
-
if (local === undefined) {
|
298
|
+
if (local?.idx === undefined) {
|
283
299
|
// no local var with name
|
284
|
-
if (
|
285
|
-
if (funcIndex
|
300
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
301
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
286
302
|
|
287
|
-
if (globals
|
303
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
288
304
|
}
|
289
305
|
|
290
|
-
if (local === undefined && rawName.startsWith('__')) {
|
306
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
291
307
|
// return undefined if unknown key in already known var
|
292
308
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
293
309
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -296,7 +312,7 @@ const generateIdent = (scope, decl) => {
|
|
296
312
|
if (!parentLookup[1]) return number(UNDEFINED);
|
297
313
|
}
|
298
314
|
|
299
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
315
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
300
316
|
|
301
317
|
return [ [ Opcodes.local_get, local.idx ] ];
|
302
318
|
};
|
@@ -404,9 +420,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
404
420
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
405
421
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
406
422
|
|
407
|
-
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
408
|
-
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
409
|
-
|
410
423
|
if (assign) {
|
411
424
|
const pointer = arrays.get(name ?? '$undeclared');
|
412
425
|
|
@@ -680,6 +693,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
680
693
|
[ Opcodes.i32_eqz ], */
|
681
694
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
695
|
],
|
696
|
+
[TYPES._bytestring]: [ // duplicate of string
|
697
|
+
[ Opcodes.local_get, tmp ],
|
698
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
699
|
+
|
700
|
+
// get length
|
701
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
702
|
+
|
703
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
704
|
+
],
|
683
705
|
default: def
|
684
706
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
707
|
];
|
@@ -707,6 +729,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
707
729
|
[ Opcodes.i32_eqz ],
|
708
730
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
709
731
|
],
|
732
|
+
[TYPES._bytestring]: [ // duplicate of string
|
733
|
+
[ Opcodes.local_get, tmp ],
|
734
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
735
|
+
|
736
|
+
// get length
|
737
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
738
|
+
|
739
|
+
// if length == 0
|
740
|
+
[ Opcodes.i32_eqz ],
|
741
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
742
|
+
],
|
710
743
|
default: [
|
711
744
|
// if value == 0
|
712
745
|
[ Opcodes.local_get, tmp ],
|
@@ -760,11 +793,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
760
793
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
761
794
|
}
|
762
795
|
|
796
|
+
const knownLeft = knownType(scope, leftType);
|
797
|
+
const knownRight = knownType(scope, rightType);
|
798
|
+
|
763
799
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
764
800
|
const strictOp = op === '===' || op === '!==';
|
765
801
|
|
766
802
|
const startOut = [], endOut = [];
|
767
|
-
const
|
803
|
+
const finalize = out => startOut.concat(out, endOut);
|
768
804
|
|
769
805
|
// if strict (in)equal check types match
|
770
806
|
if (strictOp) {
|
@@ -809,31 +845,32 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
809
845
|
// todo: if equality op and an operand is undefined, return false
|
810
846
|
// todo: niche null hell with 0
|
811
847
|
|
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
|
-
|
848
|
+
// todo: this should be dynamic but for now only static
|
849
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
850
|
+
if (op === '+') {
|
851
|
+
// string concat (a + b)
|
852
|
+
return concatStrings(scope, left, right, _global, _name, assign);
|
853
|
+
}
|
854
|
+
|
855
|
+
// not an equality op, NaN
|
856
|
+
if (!eqOp) return number(NaN);
|
857
|
+
|
858
|
+
// else leave bool ops
|
859
|
+
// todo: convert string to number if string and number/bool
|
860
|
+
// todo: string (>|>=|<|<=) string
|
861
|
+
|
862
|
+
// string comparison
|
863
|
+
if (op === '===' || op === '==') {
|
864
|
+
return compareStrings(scope, left, right);
|
865
|
+
}
|
866
|
+
|
867
|
+
if (op === '!==' || op === '!=') {
|
868
|
+
return [
|
869
|
+
...compareStrings(scope, left, right),
|
870
|
+
[ Opcodes.i32_eqz ]
|
871
|
+
];
|
872
|
+
}
|
873
|
+
}
|
837
874
|
|
838
875
|
let ops = operatorOpcode[valtype][op];
|
839
876
|
|
@@ -843,7 +880,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
843
880
|
includeBuiltin(scope, builtinName);
|
844
881
|
const idx = funcIndex[builtinName];
|
845
882
|
|
846
|
-
return
|
883
|
+
return finalize([
|
847
884
|
...left,
|
848
885
|
...right,
|
849
886
|
[ Opcodes.call, idx ]
|
@@ -858,9 +895,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
858
895
|
let tmpLeft, tmpRight;
|
859
896
|
// if equal op, check if strings for compareStrings
|
860
897
|
if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
|
861
|
-
const knownLeft = knownType(scope, leftType);
|
862
|
-
const knownRight = knownType(scope, rightType);
|
863
|
-
|
864
898
|
// todo: intelligent partial skip later
|
865
899
|
// if neither known are string, stop this madness
|
866
900
|
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
@@ -900,7 +934,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
900
934
|
[ Opcodes.i32_or ],
|
901
935
|
[ Opcodes.if, Blocktype.void ],
|
902
936
|
...number(0, Valtype.i32),
|
903
|
-
[ Opcodes.br,
|
937
|
+
[ Opcodes.br, 2 ],
|
904
938
|
[ Opcodes.end ],
|
905
939
|
|
906
940
|
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
@@ -918,7 +952,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
918
952
|
// }
|
919
953
|
})();
|
920
954
|
|
921
|
-
return
|
955
|
+
return finalize([
|
922
956
|
...left,
|
923
957
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
924
958
|
...right,
|
@@ -947,6 +981,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
947
981
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
948
982
|
}
|
949
983
|
|
984
|
+
if (typeof wasm === 'function') {
|
985
|
+
const scope = {
|
986
|
+
name,
|
987
|
+
params,
|
988
|
+
locals,
|
989
|
+
returns,
|
990
|
+
localInd: allLocals.length,
|
991
|
+
};
|
992
|
+
|
993
|
+
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
994
|
+
}
|
995
|
+
|
950
996
|
let baseGlobalIdx, i = 0;
|
951
997
|
for (const type of globalTypes) {
|
952
998
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1027,7 +1073,8 @@ const TYPES = {
|
|
1027
1073
|
|
1028
1074
|
// these are not "typeof" types but tracked internally
|
1029
1075
|
_array: 0x10,
|
1030
|
-
_regexp: 0x11
|
1076
|
+
_regexp: 0x11,
|
1077
|
+
_bytestring: 0x12
|
1031
1078
|
};
|
1032
1079
|
|
1033
1080
|
const TYPE_NAMES = {
|
@@ -1041,13 +1088,17 @@ const TYPE_NAMES = {
|
|
1041
1088
|
[TYPES.bigint]: 'BigInt',
|
1042
1089
|
|
1043
1090
|
[TYPES._array]: 'Array',
|
1044
|
-
[TYPES._regexp]: 'RegExp'
|
1091
|
+
[TYPES._regexp]: 'RegExp',
|
1092
|
+
[TYPES._bytestring]: 'ByteString'
|
1045
1093
|
};
|
1046
1094
|
|
1047
1095
|
const getType = (scope, _name) => {
|
1048
1096
|
const name = mapName(_name);
|
1049
1097
|
|
1098
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1050
1099
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1100
|
+
|
1101
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1051
1102
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1052
1103
|
|
1053
1104
|
let type = TYPES.undefined;
|
@@ -1094,6 +1145,8 @@ const getNodeType = (scope, node) => {
|
|
1094
1145
|
if (node.type === 'Literal') {
|
1095
1146
|
if (node.regex) return TYPES._regexp;
|
1096
1147
|
|
1148
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1149
|
+
|
1097
1150
|
return TYPES[typeof node.value];
|
1098
1151
|
}
|
1099
1152
|
|
@@ -1107,6 +1160,15 @@ const getNodeType = (scope, node) => {
|
|
1107
1160
|
|
1108
1161
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
1162
|
const name = node.callee.name;
|
1163
|
+
if (!name) {
|
1164
|
+
// iife
|
1165
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1166
|
+
|
1167
|
+
// presume
|
1168
|
+
// todo: warn here?
|
1169
|
+
return TYPES.number;
|
1170
|
+
}
|
1171
|
+
|
1110
1172
|
const func = funcs.find(x => x.name === name);
|
1111
1173
|
|
1112
1174
|
if (func) {
|
@@ -1125,7 +1187,7 @@ const getNodeType = (scope, node) => {
|
|
1125
1187
|
const spl = name.slice(2).split('_');
|
1126
1188
|
|
1127
1189
|
const func = spl[spl.length - 1];
|
1128
|
-
const protoFuncs = Object.
|
1190
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1129
1191
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
1192
|
}
|
1131
1193
|
|
@@ -1177,6 +1239,14 @@ const getNodeType = (scope, node) => {
|
|
1177
1239
|
|
1178
1240
|
if (node.type === 'BinaryExpression') {
|
1179
1241
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1242
|
+
if (node.operator !== '+') return TYPES.number;
|
1243
|
+
|
1244
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1245
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1246
|
+
|
1247
|
+
// todo: this should be dynamic but for now only static
|
1248
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1249
|
+
|
1180
1250
|
return TYPES.number;
|
1181
1251
|
|
1182
1252
|
// todo: string concat types
|
@@ -1201,7 +1271,7 @@ const getNodeType = (scope, node) => {
|
|
1201
1271
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1272
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1273
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1274
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1205
1275
|
|
1206
1276
|
return TYPES.number;
|
1207
1277
|
}
|
@@ -1210,7 +1280,13 @@ const getNodeType = (scope, node) => {
|
|
1210
1280
|
// hack: if something.length, number type
|
1211
1281
|
if (node.property.name === 'length') return TYPES.number;
|
1212
1282
|
|
1213
|
-
//
|
1283
|
+
// ts hack
|
1284
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1285
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1286
|
+
|
1287
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1288
|
+
|
1289
|
+
// presume
|
1214
1290
|
return TYPES.number;
|
1215
1291
|
}
|
1216
1292
|
|
@@ -1230,8 +1306,8 @@ const getNodeType = (scope, node) => {
|
|
1230
1306
|
const generateLiteral = (scope, decl, global, name) => {
|
1231
1307
|
if (decl.value === null) return number(NULL);
|
1232
1308
|
|
1309
|
+
// hack: just return 1 for regex literals
|
1233
1310
|
if (decl.regex) {
|
1234
|
-
scope.regex[name] = decl.regex;
|
1235
1311
|
return number(1);
|
1236
1312
|
}
|
1237
1313
|
|
@@ -1244,16 +1320,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1244
1320
|
return number(decl.value ? 1 : 0);
|
1245
1321
|
|
1246
1322
|
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];
|
1323
|
+
return makeString(scope, decl.value, global, name);
|
1257
1324
|
|
1258
1325
|
default:
|
1259
1326
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1274,9 +1341,9 @@ const countLeftover = wasm => {
|
|
1274
1341
|
|
1275
1342
|
if (depth === 0)
|
1276
1343
|
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)) {}
|
1344
|
+
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
1345
|
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;
|
1346
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1280
1347
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1281
1348
|
else if (inst[0] === Opcodes.return) count = 0;
|
1282
1349
|
else if (inst[0] === Opcodes.call) {
|
@@ -1302,7 +1369,7 @@ const disposeLeftover = wasm => {
|
|
1302
1369
|
const generateExp = (scope, decl) => {
|
1303
1370
|
const expression = decl.expression;
|
1304
1371
|
|
1305
|
-
const out = generate(scope, expression);
|
1372
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1306
1373
|
disposeLeftover(out);
|
1307
1374
|
|
1308
1375
|
return out;
|
@@ -1360,7 +1427,7 @@ const RTArrayUtil = {
|
|
1360
1427
|
]
|
1361
1428
|
};
|
1362
1429
|
|
1363
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1430
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1364
1431
|
/* const callee = decl.callee;
|
1365
1432
|
const args = decl.arguments;
|
1366
1433
|
|
@@ -1426,8 +1493,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1426
1493
|
// literal.func()
|
1427
1494
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1428
1495
|
// megahack for /regex/.func()
|
1429
|
-
|
1430
|
-
|
1496
|
+
const funcName = decl.callee.property.name;
|
1497
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1431
1498
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1432
1499
|
|
1433
1500
|
funcIndex[func.name] = func.index;
|
@@ -1469,8 +1536,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1469
1536
|
|
1470
1537
|
if (protoName) {
|
1471
1538
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1472
|
-
|
1473
|
-
if (f) acc[x] = f;
|
1539
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1474
1540
|
return acc;
|
1475
1541
|
}, {});
|
1476
1542
|
|
@@ -1479,10 +1545,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1479
1545
|
// use local for cached i32 length as commonly used
|
1480
1546
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1481
1547
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1482
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1483
1548
|
|
1484
1549
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1485
1550
|
|
1551
|
+
const rawPointer = [
|
1552
|
+
...generate(scope, target),
|
1553
|
+
Opcodes.i32_to_u
|
1554
|
+
];
|
1555
|
+
|
1556
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1557
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1558
|
+
|
1559
|
+
let allOptUnused = true;
|
1486
1560
|
let lengthI32CacheUsed = false;
|
1487
1561
|
const protoBC = {};
|
1488
1562
|
for (const x in protoCands) {
|
@@ -1502,6 +1576,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1502
1576
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1503
1577
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1504
1578
|
|
1579
|
+
let optUnused = false;
|
1505
1580
|
const protoOut = protoFunc(getPointer, {
|
1506
1581
|
getCachedI32: () => {
|
1507
1582
|
lengthI32CacheUsed = true;
|
@@ -1516,10 +1591,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1516
1591
|
return makeArray(scope, {
|
1517
1592
|
rawElements: new Array(length)
|
1518
1593
|
}, _global, _name, true, itemType);
|
1594
|
+
}, () => {
|
1595
|
+
optUnused = true;
|
1596
|
+
return unusedValue;
|
1519
1597
|
});
|
1520
1598
|
|
1599
|
+
if (!optUnused) allOptUnused = false;
|
1600
|
+
|
1521
1601
|
protoBC[x] = [
|
1522
|
-
[ Opcodes.block, valtypeBinary ],
|
1602
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1523
1603
|
...protoOut,
|
1524
1604
|
|
1525
1605
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1528,11 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1528
1608
|
];
|
1529
1609
|
}
|
1530
1610
|
|
1531
|
-
|
1532
|
-
...generate(scope, target),
|
1611
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1533
1612
|
|
1534
|
-
|
1535
|
-
|
1613
|
+
return [
|
1614
|
+
...(usePointerCache ? [
|
1615
|
+
...rawPointer,
|
1616
|
+
[ Opcodes.local_set, pointerLocal ],
|
1617
|
+
] : []),
|
1536
1618
|
|
1537
1619
|
...(!lengthI32CacheUsed ? [] : [
|
1538
1620
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1544,7 +1626,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1544
1626
|
|
1545
1627
|
// TODO: error better
|
1546
1628
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1547
|
-
}, valtypeBinary),
|
1629
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1548
1630
|
];
|
1549
1631
|
}
|
1550
1632
|
}
|
@@ -1591,7 +1673,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1591
1673
|
const func = funcs.find(x => x.index === idx);
|
1592
1674
|
|
1593
1675
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1594
|
-
const
|
1676
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1677
|
+
const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
|
1678
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1595
1679
|
|
1596
1680
|
let args = decl.arguments;
|
1597
1681
|
if (func && args.length < paramCount) {
|
@@ -1609,12 +1693,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1609
1693
|
let out = [];
|
1610
1694
|
for (const arg of args) {
|
1611
1695
|
out = out.concat(generate(scope, arg));
|
1612
|
-
if (
|
1696
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1613
1697
|
}
|
1614
1698
|
|
1615
1699
|
out.push([ Opcodes.call, idx ]);
|
1616
1700
|
|
1617
|
-
if (!
|
1701
|
+
if (!typedReturns) {
|
1618
1702
|
// let type;
|
1619
1703
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1620
1704
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1665,14 +1749,102 @@ const knownType = (scope, type) => {
|
|
1665
1749
|
return null;
|
1666
1750
|
};
|
1667
1751
|
|
1752
|
+
const brTable = (input, bc, returns) => {
|
1753
|
+
const out = [];
|
1754
|
+
const keys = Object.keys(bc);
|
1755
|
+
const count = keys.length;
|
1756
|
+
|
1757
|
+
if (count === 1) {
|
1758
|
+
// return [
|
1759
|
+
// ...input,
|
1760
|
+
// ...bc[keys[0]]
|
1761
|
+
// ];
|
1762
|
+
return bc[keys[0]];
|
1763
|
+
}
|
1764
|
+
|
1765
|
+
if (count === 2) {
|
1766
|
+
// just use if else
|
1767
|
+
const other = keys.find(x => x !== 'default');
|
1768
|
+
return [
|
1769
|
+
...input,
|
1770
|
+
...number(other, Valtype.i32),
|
1771
|
+
[ Opcodes.i32_eq ],
|
1772
|
+
[ Opcodes.if, returns ],
|
1773
|
+
...bc[other],
|
1774
|
+
[ Opcodes.else ],
|
1775
|
+
...bc.default,
|
1776
|
+
[ Opcodes.end ]
|
1777
|
+
];
|
1778
|
+
}
|
1779
|
+
|
1780
|
+
for (let i = 0; i < count; i++) {
|
1781
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1782
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1783
|
+
}
|
1784
|
+
|
1785
|
+
const nums = keys.filter(x => +x);
|
1786
|
+
const offset = Math.min(...nums);
|
1787
|
+
const max = Math.max(...nums);
|
1788
|
+
|
1789
|
+
const table = [];
|
1790
|
+
let br = 1;
|
1791
|
+
|
1792
|
+
for (let i = offset; i <= max; i++) {
|
1793
|
+
// if branch for this num, go to that block
|
1794
|
+
if (bc[i]) {
|
1795
|
+
table.push(br);
|
1796
|
+
br++;
|
1797
|
+
continue;
|
1798
|
+
}
|
1799
|
+
|
1800
|
+
// else default
|
1801
|
+
table.push(0);
|
1802
|
+
}
|
1803
|
+
|
1804
|
+
out.push(
|
1805
|
+
[ Opcodes.block, Blocktype.void ],
|
1806
|
+
...input,
|
1807
|
+
...(offset > 0 ? [
|
1808
|
+
...number(offset, Valtype.i32),
|
1809
|
+
[ Opcodes.i32_sub ]
|
1810
|
+
] : []),
|
1811
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1812
|
+
);
|
1813
|
+
|
1814
|
+
// if you can guess why we sort the wrong way and then reverse
|
1815
|
+
// (instead of just sorting the correct way)
|
1816
|
+
// dm me and if you are correct and the first person
|
1817
|
+
// I will somehow shout you out or something
|
1818
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1819
|
+
|
1820
|
+
br = count - 1;
|
1821
|
+
for (const x of orderedBc) {
|
1822
|
+
out.push(
|
1823
|
+
[ Opcodes.end ],
|
1824
|
+
...bc[x],
|
1825
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1826
|
+
);
|
1827
|
+
br--;
|
1828
|
+
}
|
1829
|
+
|
1830
|
+
return [
|
1831
|
+
...out,
|
1832
|
+
[ Opcodes.end, 'br table end' ]
|
1833
|
+
];
|
1834
|
+
};
|
1835
|
+
|
1668
1836
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1837
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
1838
|
+
|
1669
1839
|
const known = knownType(scope, type);
|
1670
1840
|
if (known != null) {
|
1671
1841
|
return bc[known] ?? bc.default;
|
1672
1842
|
}
|
1673
1843
|
|
1674
|
-
|
1844
|
+
if (Prefs.typeswitchUseBrtable)
|
1845
|
+
return brTable(type, bc, returns);
|
1675
1846
|
|
1847
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1676
1848
|
const out = [
|
1677
1849
|
...type,
|
1678
1850
|
[ Opcodes.local_set, tmp ],
|
@@ -1704,7 +1876,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1704
1876
|
return out;
|
1705
1877
|
};
|
1706
1878
|
|
1707
|
-
const allocVar = (scope, name, global = false) => {
|
1879
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1708
1880
|
const target = global ? globals : scope.locals;
|
1709
1881
|
|
1710
1882
|
// already declared
|
@@ -1718,8 +1890,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1718
1890
|
let idx = global ? globalInd++ : scope.localInd++;
|
1719
1891
|
target[name] = { idx, type: valtypeBinary };
|
1720
1892
|
|
1721
|
-
|
1722
|
-
|
1893
|
+
if (type) {
|
1894
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
1895
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
1896
|
+
}
|
1723
1897
|
|
1724
1898
|
return idx;
|
1725
1899
|
};
|
@@ -1759,11 +1933,14 @@ const extractTypeAnnotation = decl => {
|
|
1759
1933
|
elementType = extractTypeAnnotation(a.elementType).type;
|
1760
1934
|
}
|
1761
1935
|
|
1936
|
+
const typeName = type;
|
1762
1937
|
type = typeAnnoToPorfType(type);
|
1763
1938
|
|
1939
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
1940
|
+
|
1764
1941
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1765
1942
|
|
1766
|
-
return { type, elementType };
|
1943
|
+
return { type, typeName, elementType };
|
1767
1944
|
};
|
1768
1945
|
|
1769
1946
|
const generateVar = (scope, decl) => {
|
@@ -1777,6 +1954,8 @@ const generateVar = (scope, decl) => {
|
|
1777
1954
|
for (const x of decl.declarations) {
|
1778
1955
|
const name = mapName(x.id.name);
|
1779
1956
|
|
1957
|
+
if (!name) return todo('destructuring is not supported yet');
|
1958
|
+
|
1780
1959
|
if (x.init && isFuncType(x.init.type)) {
|
1781
1960
|
// hack for let a = function () { ... }
|
1782
1961
|
x.init.id = { name };
|
@@ -1792,7 +1971,12 @@ const generateVar = (scope, decl) => {
|
|
1792
1971
|
continue; // always ignore
|
1793
1972
|
}
|
1794
1973
|
|
1795
|
-
let idx = allocVar(scope, name, global);
|
1974
|
+
let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
|
1975
|
+
|
1976
|
+
if (typedInput && x.id.typeAnnotation) {
|
1977
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1978
|
+
}
|
1979
|
+
|
1796
1980
|
if (x.init) {
|
1797
1981
|
out = out.concat(generate(scope, x.init, global, name));
|
1798
1982
|
|
@@ -1802,10 +1986,6 @@ const generateVar = (scope, decl) => {
|
|
1802
1986
|
|
1803
1987
|
// hack: this follows spec properly but is mostly unneeded 😅
|
1804
1988
|
// out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
|
1805
|
-
|
1806
|
-
if (typedInput && x.id.typeAnnotation) {
|
1807
|
-
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1808
|
-
}
|
1809
1989
|
}
|
1810
1990
|
|
1811
1991
|
return out;
|
@@ -1914,6 +2094,8 @@ const generateAssign = (scope, decl) => {
|
|
1914
2094
|
];
|
1915
2095
|
}
|
1916
2096
|
|
2097
|
+
if (!name) return todo('destructuring is not supported yet');
|
2098
|
+
|
1917
2099
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1918
2100
|
|
1919
2101
|
if (local === undefined) {
|
@@ -2052,6 +2234,8 @@ const generateUnary = (scope, decl) => {
|
|
2052
2234
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2053
2235
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2054
2236
|
|
2237
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2238
|
+
|
2055
2239
|
// object and internal types
|
2056
2240
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2057
2241
|
});
|
@@ -2157,8 +2341,10 @@ const generateFor = (scope, decl) => {
|
|
2157
2341
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2158
2342
|
depth.push('for');
|
2159
2343
|
|
2160
|
-
out.push(...generate(scope, decl.test));
|
2161
|
-
|
2344
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2345
|
+
else out.push(...number(1, Valtype.i32));
|
2346
|
+
|
2347
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2162
2348
|
depth.push('if');
|
2163
2349
|
|
2164
2350
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2166,8 +2352,7 @@ const generateFor = (scope, decl) => {
|
|
2166
2352
|
out.push(...generate(scope, decl.body));
|
2167
2353
|
out.push([ Opcodes.end ]);
|
2168
2354
|
|
2169
|
-
out.push(...generate(scope, decl.update));
|
2170
|
-
depth.pop();
|
2355
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2171
2356
|
|
2172
2357
|
out.push([ Opcodes.br, 1 ]);
|
2173
2358
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2224,7 +2409,13 @@ const generateForOf = (scope, decl) => {
|
|
2224
2409
|
// setup local for left
|
2225
2410
|
generate(scope, decl.left);
|
2226
2411
|
|
2227
|
-
|
2412
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2413
|
+
if (!leftName && decl.left.name) {
|
2414
|
+
leftName = decl.left.name;
|
2415
|
+
|
2416
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2417
|
+
}
|
2418
|
+
|
2228
2419
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2229
2420
|
|
2230
2421
|
depth.push('block');
|
@@ -2233,13 +2424,14 @@ const generateForOf = (scope, decl) => {
|
|
2233
2424
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2234
2425
|
// hack: this is naughty and will break things!
|
2235
2426
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2236
|
-
if (pages.
|
2427
|
+
if (pages.hasAnyString) {
|
2237
2428
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2238
2429
|
rawElements: new Array(1)
|
2239
2430
|
}, isGlobal, leftName, true, 'i16');
|
2240
2431
|
}
|
2241
2432
|
|
2242
2433
|
// set type for local
|
2434
|
+
// todo: optimize away counter and use end pointer
|
2243
2435
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2244
2436
|
[TYPES._array]: [
|
2245
2437
|
...setType(scope, leftName, TYPES.number),
|
@@ -2364,7 +2556,7 @@ const generateThrow = (scope, decl) => {
|
|
2364
2556
|
// hack: throw new X("...") -> throw "..."
|
2365
2557
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2366
2558
|
constructor = decl.argument.callee.name;
|
2367
|
-
message = decl.argument.arguments[0]
|
2559
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2368
2560
|
}
|
2369
2561
|
|
2370
2562
|
if (tags.length === 0) tags.push({
|
@@ -2376,6 +2568,9 @@ const generateThrow = (scope, decl) => {
|
|
2376
2568
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2377
2569
|
let tagIdx = tags[0].idx;
|
2378
2570
|
|
2571
|
+
scope.exceptions ??= [];
|
2572
|
+
scope.exceptions.push(exceptId);
|
2573
|
+
|
2379
2574
|
// todo: write a description of how this works lol
|
2380
2575
|
|
2381
2576
|
return [
|
@@ -2420,25 +2615,31 @@ const generateAssignPat = (scope, decl) => {
|
|
2420
2615
|
};
|
2421
2616
|
|
2422
2617
|
let pages = new Map();
|
2423
|
-
const allocPage = (reason, type) => {
|
2618
|
+
const allocPage = (scope, reason, type) => {
|
2424
2619
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2425
2620
|
|
2426
2621
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2427
2622
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2623
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2624
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2428
2625
|
|
2429
2626
|
const ind = pages.size;
|
2430
2627
|
pages.set(reason, { ind, type });
|
2431
2628
|
|
2432
|
-
|
2629
|
+
scope.pages ??= new Map();
|
2630
|
+
scope.pages.set(reason, { ind, type });
|
2631
|
+
|
2632
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2433
2633
|
|
2434
2634
|
return ind;
|
2435
2635
|
};
|
2436
2636
|
|
2637
|
+
// todo: add scope.pages
|
2437
2638
|
const freePage = reason => {
|
2438
2639
|
const { ind } = pages.get(reason);
|
2439
2640
|
pages.delete(reason);
|
2440
2641
|
|
2441
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2642
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2442
2643
|
|
2443
2644
|
return ind;
|
2444
2645
|
};
|
@@ -2458,25 +2659,34 @@ const StoreOps = {
|
|
2458
2659
|
f64: Opcodes.f64_store,
|
2459
2660
|
|
2460
2661
|
// expects i32 input!
|
2461
|
-
|
2662
|
+
i8: Opcodes.i32_store8,
|
2663
|
+
i16: Opcodes.i32_store16,
|
2462
2664
|
};
|
2463
2665
|
|
2464
2666
|
let data = [];
|
2465
2667
|
|
2466
|
-
const compileBytes = (val, itemType
|
2668
|
+
const compileBytes = (val, itemType) => {
|
2467
2669
|
// todo: this is a mess and needs confirming / ????
|
2468
2670
|
switch (itemType) {
|
2469
2671
|
case 'i8': return [ val % 256 ];
|
2470
|
-
case 'i16': return [ val % 256,
|
2471
|
-
|
2472
|
-
case 'i32':
|
2473
|
-
|
2474
|
-
return enforceFourBytes(signedLEB128(val));
|
2672
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2673
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2674
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
2675
|
+
// todo: i64
|
2475
2676
|
|
2476
2677
|
case 'f64': return ieee754_binary64(val);
|
2477
2678
|
}
|
2478
2679
|
};
|
2479
2680
|
|
2681
|
+
const getAllocType = itemType => {
|
2682
|
+
switch (itemType) {
|
2683
|
+
case 'i8': return 'bytestring';
|
2684
|
+
case 'i16': return 'string';
|
2685
|
+
|
2686
|
+
default: return 'array';
|
2687
|
+
}
|
2688
|
+
};
|
2689
|
+
|
2480
2690
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2481
2691
|
const out = [];
|
2482
2692
|
|
@@ -2486,7 +2696,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2486
2696
|
|
2487
2697
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2488
2698
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2489
|
-
arrays.set(name, allocPage(`${itemType
|
2699
|
+
arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2490
2700
|
}
|
2491
2701
|
|
2492
2702
|
const pointer = arrays.get(name);
|
@@ -2498,18 +2708,24 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2498
2708
|
const length = elements.length;
|
2499
2709
|
|
2500
2710
|
if (firstAssign && useRawElements) {
|
2501
|
-
|
2711
|
+
// if length is 0 memory/data will just be 0000... anyway
|
2712
|
+
if (length !== 0) {
|
2713
|
+
let bytes = compileBytes(length, 'i32');
|
2502
2714
|
|
2503
|
-
|
2504
|
-
|
2715
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2716
|
+
if (elements[i] == null) continue;
|
2505
2717
|
|
2506
|
-
|
2507
|
-
|
2718
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2719
|
+
}
|
2508
2720
|
|
2509
|
-
|
2510
|
-
|
2511
|
-
|
2512
|
-
|
2721
|
+
const ind = data.push({
|
2722
|
+
offset: pointer,
|
2723
|
+
bytes
|
2724
|
+
}) - 1;
|
2725
|
+
|
2726
|
+
scope.data ??= [];
|
2727
|
+
scope.data.push(ind);
|
2728
|
+
}
|
2513
2729
|
|
2514
2730
|
// local value as pointer
|
2515
2731
|
out.push(...number(pointer));
|
@@ -2532,7 +2748,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2532
2748
|
out.push(
|
2533
2749
|
...number(0, Valtype.i32),
|
2534
2750
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2535
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2751
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2536
2752
|
);
|
2537
2753
|
}
|
2538
2754
|
|
@@ -2542,15 +2758,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2542
2758
|
return [ out, pointer ];
|
2543
2759
|
};
|
2544
2760
|
|
2545
|
-
const
|
2761
|
+
const byteStringable = str => {
|
2762
|
+
if (!Prefs.bytestring) return false;
|
2763
|
+
|
2764
|
+
for (let i = 0; i < str.length; i++) {
|
2765
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2766
|
+
}
|
2767
|
+
|
2768
|
+
return true;
|
2769
|
+
};
|
2770
|
+
|
2771
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2546
2772
|
const rawElements = new Array(str.length);
|
2773
|
+
let byteStringable = Prefs.bytestring;
|
2547
2774
|
for (let i = 0; i < str.length; i++) {
|
2548
|
-
|
2775
|
+
const c = str.charCodeAt(i);
|
2776
|
+
rawElements[i] = c;
|
2777
|
+
|
2778
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2549
2779
|
}
|
2550
2780
|
|
2781
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2782
|
+
|
2551
2783
|
return makeArray(scope, {
|
2552
2784
|
rawElements
|
2553
|
-
}, global, name, false, 'i16')[0];
|
2785
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2554
2786
|
};
|
2555
2787
|
|
2556
2788
|
let arrays = new Map();
|
@@ -2578,10 +2810,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2578
2810
|
];
|
2579
2811
|
}
|
2580
2812
|
|
2813
|
+
const object = generate(scope, decl.object);
|
2814
|
+
const property = generate(scope, decl.property);
|
2815
|
+
|
2581
2816
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2582
2817
|
// hack: this is naughty and will break things!
|
2583
|
-
let newOut = number(0,
|
2584
|
-
if (pages.
|
2818
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2819
|
+
if (pages.hasAnyString) {
|
2585
2820
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2586
2821
|
rawElements: new Array(1)
|
2587
2822
|
}, _global, _name, true, 'i16');
|
@@ -2590,7 +2825,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2590
2825
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2591
2826
|
[TYPES._array]: [
|
2592
2827
|
// get index as valtype
|
2593
|
-
...
|
2828
|
+
...property,
|
2594
2829
|
|
2595
2830
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2596
2831
|
Opcodes.i32_to_u,
|
@@ -2598,7 +2833,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2598
2833
|
[ Opcodes.i32_mul ],
|
2599
2834
|
|
2600
2835
|
...(aotPointer ? [] : [
|
2601
|
-
...
|
2836
|
+
...object,
|
2602
2837
|
Opcodes.i32_to_u,
|
2603
2838
|
[ Opcodes.i32_add ]
|
2604
2839
|
]),
|
@@ -2617,14 +2852,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2617
2852
|
|
2618
2853
|
...number(0, Valtype.i32), // base 0 for store later
|
2619
2854
|
|
2620
|
-
...
|
2621
|
-
|
2855
|
+
...property,
|
2622
2856
|
Opcodes.i32_to_u,
|
2857
|
+
|
2623
2858
|
...number(ValtypeSize.i16, Valtype.i32),
|
2624
2859
|
[ Opcodes.i32_mul ],
|
2625
2860
|
|
2626
2861
|
...(aotPointer ? [] : [
|
2627
|
-
...
|
2862
|
+
...object,
|
2628
2863
|
Opcodes.i32_to_u,
|
2629
2864
|
[ Opcodes.i32_add ]
|
2630
2865
|
]),
|
@@ -2641,8 +2876,36 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2641
2876
|
...number(TYPES.string, Valtype.i32),
|
2642
2877
|
setLastType(scope)
|
2643
2878
|
],
|
2879
|
+
[TYPES._bytestring]: [
|
2880
|
+
// setup new/out array
|
2881
|
+
...newOut,
|
2882
|
+
[ Opcodes.drop ],
|
2883
|
+
|
2884
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2644
2885
|
|
2645
|
-
|
2886
|
+
...property,
|
2887
|
+
Opcodes.i32_to_u,
|
2888
|
+
|
2889
|
+
...(aotPointer ? [] : [
|
2890
|
+
...object,
|
2891
|
+
Opcodes.i32_to_u,
|
2892
|
+
[ Opcodes.i32_add ]
|
2893
|
+
]),
|
2894
|
+
|
2895
|
+
// load current string ind {arg}
|
2896
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2897
|
+
|
2898
|
+
// store to new string ind 0
|
2899
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2900
|
+
|
2901
|
+
// return new string (page)
|
2902
|
+
...number(newPointer),
|
2903
|
+
|
2904
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2905
|
+
setLastType(scope)
|
2906
|
+
],
|
2907
|
+
|
2908
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2646
2909
|
});
|
2647
2910
|
};
|
2648
2911
|
|
@@ -2659,13 +2922,16 @@ const objectHack = node => {
|
|
2659
2922
|
// if object is not identifier or another member exp, give up
|
2660
2923
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2661
2924
|
|
2662
|
-
if (!objectName) objectName = objectHack(node.object)
|
2925
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2663
2926
|
|
2664
2927
|
// if .length, give up (hack within a hack!)
|
2665
2928
|
if (node.property.name === 'length') return node;
|
2666
2929
|
|
2930
|
+
// no object name, give up
|
2931
|
+
if (!objectName) return node;
|
2932
|
+
|
2667
2933
|
const name = '__' + objectName + '_' + node.property.name;
|
2668
|
-
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2934
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2669
2935
|
|
2670
2936
|
return {
|
2671
2937
|
type: 'Identifier',
|
@@ -2722,10 +2988,8 @@ const generateFunc = (scope, decl) => {
|
|
2722
2988
|
const func = {
|
2723
2989
|
name,
|
2724
2990
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2725
|
-
|
2726
|
-
|
2727
|
-
throws: innerScope.throws,
|
2728
|
-
index: currentFuncIndex++
|
2991
|
+
index: currentFuncIndex++,
|
2992
|
+
...innerScope
|
2729
2993
|
};
|
2730
2994
|
funcIndex[name] = func.index;
|
2731
2995
|
|
@@ -2763,6 +3027,16 @@ const generateCode = (scope, decl) => {
|
|
2763
3027
|
};
|
2764
3028
|
|
2765
3029
|
const internalConstrs = {
|
3030
|
+
Boolean: {
|
3031
|
+
generate: (scope, decl) => {
|
3032
|
+
if (decl.arguments.length === 0) return number(0);
|
3033
|
+
|
3034
|
+
// should generate/run all args
|
3035
|
+
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3036
|
+
},
|
3037
|
+
type: TYPES.boolean
|
3038
|
+
},
|
3039
|
+
|
2766
3040
|
Array: {
|
2767
3041
|
generate: (scope, decl, global, name) => {
|
2768
3042
|
// new Array(i0, i1, ...)
|
@@ -2884,7 +3158,7 @@ export default program => {
|
|
2884
3158
|
body: program.body
|
2885
3159
|
};
|
2886
3160
|
|
2887
|
-
if (
|
3161
|
+
if (Prefs.astLog) console.log(program.body.body);
|
2888
3162
|
|
2889
3163
|
generateFunc(scope, program);
|
2890
3164
|
|