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