porffor 0.2.0-5e33105 → 0.2.0-623cdf0
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 +9 -6
- package/compiler/2c.js +316 -71
- package/compiler/builtins.js +177 -25
- package/compiler/codeGen.js +235 -100
- package/compiler/decompile.js +3 -3
- package/compiler/index.js +14 -3
- package/compiler/opt.js +1 -0
- package/compiler/prototype.js +171 -16
- package/compiler/wasmSpec.js +3 -0
- package/compiler/wrap.js +103 -10
- package/filesize.cmd +2 -0
- package/package.json +1 -1
- package/runner/index.js +15 -2
- package/node_trace.1.log +0 -1
package/compiler/codeGen.js
CHANGED
@@ -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_asm: str => {
|
159
164
|
let out = [];
|
160
165
|
|
161
166
|
for (const line of str.split('\n')) {
|
@@ -191,19 +196,19 @@ 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
|
@@ -269,25 +274,25 @@ const generateIdent = (scope, decl) => {
|
|
269
274
|
const name = mapName(rawName);
|
270
275
|
let local = scope.locals[rawName];
|
271
276
|
|
272
|
-
if (builtinVars
|
277
|
+
if (Object.hasOwn(builtinVars, name)) {
|
273
278
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
274
279
|
return builtinVars[name];
|
275
280
|
}
|
276
281
|
|
277
|
-
if (builtinFuncs
|
282
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
278
283
|
// todo: return an actual something
|
279
284
|
return number(1);
|
280
285
|
}
|
281
286
|
|
282
|
-
if (local === undefined) {
|
287
|
+
if (local?.idx === undefined) {
|
283
288
|
// no local var with name
|
284
|
-
if (
|
285
|
-
if (funcIndex
|
289
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
290
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
286
291
|
|
287
|
-
if (globals
|
292
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
288
293
|
}
|
289
294
|
|
290
|
-
if (local === undefined && rawName.startsWith('__')) {
|
295
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
291
296
|
// return undefined if unknown key in already known var
|
292
297
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
293
298
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -296,7 +301,7 @@ const generateIdent = (scope, decl) => {
|
|
296
301
|
if (!parentLookup[1]) return number(UNDEFINED);
|
297
302
|
}
|
298
303
|
|
299
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
304
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
300
305
|
|
301
306
|
return [ [ Opcodes.local_get, local.idx ] ];
|
302
307
|
};
|
@@ -680,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
680
685
|
[ Opcodes.i32_eqz ], */
|
681
686
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
687
|
],
|
688
|
+
[TYPES._bytestring]: [ // duplicate of string
|
689
|
+
[ Opcodes.local_get, tmp ],
|
690
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
|
+
|
692
|
+
// get length
|
693
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
694
|
+
|
695
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
696
|
+
],
|
683
697
|
default: def
|
684
698
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
699
|
];
|
@@ -707,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
707
721
|
[ Opcodes.i32_eqz ],
|
708
722
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
709
723
|
],
|
724
|
+
[TYPES._bytestring]: [ // duplicate of string
|
725
|
+
[ Opcodes.local_get, tmp ],
|
726
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
|
+
|
728
|
+
// get length
|
729
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
730
|
+
|
731
|
+
// if length == 0
|
732
|
+
[ Opcodes.i32_eqz ],
|
733
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
734
|
+
],
|
710
735
|
default: [
|
711
736
|
// if value == 0
|
712
737
|
[ Opcodes.local_get, tmp ],
|
@@ -900,7 +925,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
900
925
|
[ Opcodes.i32_or ],
|
901
926
|
[ Opcodes.if, Blocktype.void ],
|
902
927
|
...number(0, Valtype.i32),
|
903
|
-
[ Opcodes.br,
|
928
|
+
[ Opcodes.br, 2 ],
|
904
929
|
[ Opcodes.end ],
|
905
930
|
|
906
931
|
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
@@ -947,6 +972,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
947
972
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
948
973
|
}
|
949
974
|
|
975
|
+
if (typeof wasm === 'function') {
|
976
|
+
const scope = {
|
977
|
+
name,
|
978
|
+
params,
|
979
|
+
locals,
|
980
|
+
returns,
|
981
|
+
localInd: allLocals.length,
|
982
|
+
};
|
983
|
+
|
984
|
+
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
985
|
+
}
|
986
|
+
|
950
987
|
let baseGlobalIdx, i = 0;
|
951
988
|
for (const type of globalTypes) {
|
952
989
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1027,7 +1064,8 @@ const TYPES = {
|
|
1027
1064
|
|
1028
1065
|
// these are not "typeof" types but tracked internally
|
1029
1066
|
_array: 0x10,
|
1030
|
-
_regexp: 0x11
|
1067
|
+
_regexp: 0x11,
|
1068
|
+
_bytestring: 0x12
|
1031
1069
|
};
|
1032
1070
|
|
1033
1071
|
const TYPE_NAMES = {
|
@@ -1041,7 +1079,8 @@ const TYPE_NAMES = {
|
|
1041
1079
|
[TYPES.bigint]: 'BigInt',
|
1042
1080
|
|
1043
1081
|
[TYPES._array]: 'Array',
|
1044
|
-
[TYPES._regexp]: 'RegExp'
|
1082
|
+
[TYPES._regexp]: 'RegExp',
|
1083
|
+
[TYPES._bytestring]: 'ByteString'
|
1045
1084
|
};
|
1046
1085
|
|
1047
1086
|
const getType = (scope, _name) => {
|
@@ -1094,6 +1133,8 @@ const getNodeType = (scope, node) => {
|
|
1094
1133
|
if (node.type === 'Literal') {
|
1095
1134
|
if (node.regex) return TYPES._regexp;
|
1096
1135
|
|
1136
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1137
|
+
|
1097
1138
|
return TYPES[typeof node.value];
|
1098
1139
|
}
|
1099
1140
|
|
@@ -1107,6 +1148,15 @@ const getNodeType = (scope, node) => {
|
|
1107
1148
|
|
1108
1149
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
1150
|
const name = node.callee.name;
|
1151
|
+
if (!name) {
|
1152
|
+
// iife
|
1153
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1154
|
+
|
1155
|
+
// presume
|
1156
|
+
// todo: warn here?
|
1157
|
+
return TYPES.number;
|
1158
|
+
}
|
1159
|
+
|
1110
1160
|
const func = funcs.find(x => x.name === name);
|
1111
1161
|
|
1112
1162
|
if (func) {
|
@@ -1125,7 +1175,7 @@ const getNodeType = (scope, node) => {
|
|
1125
1175
|
const spl = name.slice(2).split('_');
|
1126
1176
|
|
1127
1177
|
const func = spl[spl.length - 1];
|
1128
|
-
const protoFuncs = Object.
|
1178
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1129
1179
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
1180
|
}
|
1131
1181
|
|
@@ -1201,7 +1251,7 @@ const getNodeType = (scope, node) => {
|
|
1201
1251
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1252
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1253
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1254
|
+
if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
|
1205
1255
|
|
1206
1256
|
return TYPES.number;
|
1207
1257
|
}
|
@@ -1210,7 +1260,13 @@ const getNodeType = (scope, node) => {
|
|
1210
1260
|
// hack: if something.length, number type
|
1211
1261
|
if (node.property.name === 'length') return TYPES.number;
|
1212
1262
|
|
1213
|
-
//
|
1263
|
+
// ts hack
|
1264
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1265
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1266
|
+
|
1267
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1268
|
+
|
1269
|
+
// presume
|
1214
1270
|
return TYPES.number;
|
1215
1271
|
}
|
1216
1272
|
|
@@ -1227,28 +1283,11 @@ const getNodeType = (scope, node) => {
|
|
1227
1283
|
return ret;
|
1228
1284
|
};
|
1229
1285
|
|
1230
|
-
const toString = (scope, wasm, type) => {
|
1231
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1232
|
-
return [
|
1233
|
-
...wasm,
|
1234
|
-
[ Opcodes.local_set, tmp ],
|
1235
|
-
|
1236
|
-
...typeSwitch(scope, type, {
|
1237
|
-
[TYPES.string]: [
|
1238
|
-
[ Opcodes.local_get, tmp ]
|
1239
|
-
],
|
1240
|
-
[TYPES.undefined]: [
|
1241
|
-
// [ Opcodes.]
|
1242
|
-
]
|
1243
|
-
})
|
1244
|
-
]
|
1245
|
-
};
|
1246
|
-
|
1247
1286
|
const generateLiteral = (scope, decl, global, name) => {
|
1248
1287
|
if (decl.value === null) return number(NULL);
|
1249
1288
|
|
1289
|
+
// hack: just return 1 for regex literals
|
1250
1290
|
if (decl.regex) {
|
1251
|
-
scope.regex[name] = decl.regex;
|
1252
1291
|
return number(1);
|
1253
1292
|
}
|
1254
1293
|
|
@@ -1261,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1261
1300
|
return number(decl.value ? 1 : 0);
|
1262
1301
|
|
1263
1302
|
case 'string':
|
1264
|
-
|
1265
|
-
const rawElements = new Array(str.length);
|
1266
|
-
let j = 0;
|
1267
|
-
for (let i = 0; i < str.length; i++) {
|
1268
|
-
rawElements[i] = str.charCodeAt(i);
|
1269
|
-
}
|
1270
|
-
|
1271
|
-
return makeArray(scope, {
|
1272
|
-
rawElements
|
1273
|
-
}, global, name, false, 'i16')[0];
|
1303
|
+
return makeString(scope, decl.value, global, name);
|
1274
1304
|
|
1275
1305
|
default:
|
1276
1306
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1291,9 +1321,9 @@ const countLeftover = wasm => {
|
|
1291
1321
|
|
1292
1322
|
if (depth === 0)
|
1293
1323
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1294
|
-
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)) {}
|
1324
|
+
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)) {}
|
1295
1325
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1296
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1326
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1297
1327
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1298
1328
|
else if (inst[0] === Opcodes.return) count = 0;
|
1299
1329
|
else if (inst[0] === Opcodes.call) {
|
@@ -1319,7 +1349,7 @@ const disposeLeftover = wasm => {
|
|
1319
1349
|
const generateExp = (scope, decl) => {
|
1320
1350
|
const expression = decl.expression;
|
1321
1351
|
|
1322
|
-
const out = generate(scope, expression);
|
1352
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1323
1353
|
disposeLeftover(out);
|
1324
1354
|
|
1325
1355
|
return out;
|
@@ -1377,7 +1407,7 @@ const RTArrayUtil = {
|
|
1377
1407
|
]
|
1378
1408
|
};
|
1379
1409
|
|
1380
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1410
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1381
1411
|
/* const callee = decl.callee;
|
1382
1412
|
const args = decl.arguments;
|
1383
1413
|
|
@@ -1443,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1443
1473
|
// literal.func()
|
1444
1474
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1445
1475
|
// megahack for /regex/.func()
|
1446
|
-
|
1447
|
-
|
1476
|
+
const funcName = decl.callee.property.name;
|
1477
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1448
1478
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1449
1479
|
|
1450
1480
|
funcIndex[func.name] = func.index;
|
@@ -1486,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1486
1516
|
|
1487
1517
|
if (protoName) {
|
1488
1518
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1489
|
-
|
1490
|
-
if (f) acc[x] = f;
|
1519
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1491
1520
|
return acc;
|
1492
1521
|
}, {});
|
1493
1522
|
|
@@ -1496,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1496
1525
|
// use local for cached i32 length as commonly used
|
1497
1526
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1498
1527
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1499
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1500
1528
|
|
1501
1529
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1502
1530
|
|
1531
|
+
const rawPointer = [
|
1532
|
+
...generate(scope, target),
|
1533
|
+
Opcodes.i32_to_u
|
1534
|
+
];
|
1535
|
+
|
1536
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1537
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1538
|
+
|
1539
|
+
let allOptUnused = true;
|
1503
1540
|
let lengthI32CacheUsed = false;
|
1504
1541
|
const protoBC = {};
|
1505
1542
|
for (const x in protoCands) {
|
@@ -1519,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1519
1556
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1520
1557
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1521
1558
|
|
1559
|
+
let optUnused = false;
|
1522
1560
|
const protoOut = protoFunc(getPointer, {
|
1523
1561
|
getCachedI32: () => {
|
1524
1562
|
lengthI32CacheUsed = true;
|
@@ -1533,10 +1571,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1533
1571
|
return makeArray(scope, {
|
1534
1572
|
rawElements: new Array(length)
|
1535
1573
|
}, _global, _name, true, itemType);
|
1574
|
+
}, () => {
|
1575
|
+
optUnused = true;
|
1576
|
+
return unusedValue;
|
1536
1577
|
});
|
1537
1578
|
|
1579
|
+
if (!optUnused) allOptUnused = false;
|
1580
|
+
|
1538
1581
|
protoBC[x] = [
|
1539
|
-
[ Opcodes.block, valtypeBinary ],
|
1582
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1540
1583
|
...protoOut,
|
1541
1584
|
|
1542
1585
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1545,11 +1588,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1545
1588
|
];
|
1546
1589
|
}
|
1547
1590
|
|
1548
|
-
|
1549
|
-
...generate(scope, target),
|
1591
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1550
1592
|
|
1551
|
-
|
1552
|
-
|
1593
|
+
return [
|
1594
|
+
...(usePointerCache ? [
|
1595
|
+
...rawPointer,
|
1596
|
+
[ Opcodes.local_set, pointerLocal ],
|
1597
|
+
] : []),
|
1553
1598
|
|
1554
1599
|
...(!lengthI32CacheUsed ? [] : [
|
1555
1600
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1561,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1561
1606
|
|
1562
1607
|
// TODO: error better
|
1563
1608
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1564
|
-
}, valtypeBinary),
|
1609
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1565
1610
|
];
|
1566
1611
|
}
|
1567
1612
|
}
|
@@ -1608,7 +1653,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1608
1653
|
const func = funcs.find(x => x.index === idx);
|
1609
1654
|
|
1610
1655
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1611
|
-
const
|
1656
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1657
|
+
const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
|
1658
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1612
1659
|
|
1613
1660
|
let args = decl.arguments;
|
1614
1661
|
if (func && args.length < paramCount) {
|
@@ -1626,12 +1673,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1626
1673
|
let out = [];
|
1627
1674
|
for (const arg of args) {
|
1628
1675
|
out = out.concat(generate(scope, arg));
|
1629
|
-
if (
|
1676
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1630
1677
|
}
|
1631
1678
|
|
1632
1679
|
out.push([ Opcodes.call, idx ]);
|
1633
1680
|
|
1634
|
-
if (!
|
1681
|
+
if (!typedReturn) {
|
1635
1682
|
// let type;
|
1636
1683
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1637
1684
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1767,6 +1814,8 @@ const brTable = (input, bc, returns) => {
|
|
1767
1814
|
};
|
1768
1815
|
|
1769
1816
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1817
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1818
|
+
|
1770
1819
|
const known = knownType(scope, type);
|
1771
1820
|
if (known != null) {
|
1772
1821
|
return bc[known] ?? bc.default;
|
@@ -1865,6 +1914,8 @@ const extractTypeAnnotation = decl => {
|
|
1865
1914
|
const typeName = type;
|
1866
1915
|
type = typeAnnoToPorfType(type);
|
1867
1916
|
|
1917
|
+
if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
|
1918
|
+
|
1868
1919
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1869
1920
|
|
1870
1921
|
return { type, typeName, elementType };
|
@@ -1881,6 +1932,8 @@ const generateVar = (scope, decl) => {
|
|
1881
1932
|
for (const x of decl.declarations) {
|
1882
1933
|
const name = mapName(x.id.name);
|
1883
1934
|
|
1935
|
+
if (!name) return todo('destructuring is not supported yet');
|
1936
|
+
|
1884
1937
|
if (x.init && isFuncType(x.init.type)) {
|
1885
1938
|
// hack for let a = function () { ... }
|
1886
1939
|
x.init.id = { name };
|
@@ -2019,6 +2072,8 @@ const generateAssign = (scope, decl) => {
|
|
2019
2072
|
];
|
2020
2073
|
}
|
2021
2074
|
|
2075
|
+
if (!name) return todo('destructuring is not supported yet');
|
2076
|
+
|
2022
2077
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2023
2078
|
|
2024
2079
|
if (local === undefined) {
|
@@ -2157,6 +2212,8 @@ const generateUnary = (scope, decl) => {
|
|
2157
2212
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2158
2213
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2159
2214
|
|
2215
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2216
|
+
|
2160
2217
|
// object and internal types
|
2161
2218
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2162
2219
|
});
|
@@ -2262,8 +2319,10 @@ const generateFor = (scope, decl) => {
|
|
2262
2319
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2263
2320
|
depth.push('for');
|
2264
2321
|
|
2265
|
-
out.push(...generate(scope, decl.test));
|
2266
|
-
|
2322
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2323
|
+
else out.push(...number(1, Valtype.i32));
|
2324
|
+
|
2325
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2267
2326
|
depth.push('if');
|
2268
2327
|
|
2269
2328
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2271,8 +2330,7 @@ const generateFor = (scope, decl) => {
|
|
2271
2330
|
out.push(...generate(scope, decl.body));
|
2272
2331
|
out.push([ Opcodes.end ]);
|
2273
2332
|
|
2274
|
-
out.push(...generate(scope, decl.update));
|
2275
|
-
depth.pop();
|
2333
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2276
2334
|
|
2277
2335
|
out.push([ Opcodes.br, 1 ]);
|
2278
2336
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2329,7 +2387,13 @@ const generateForOf = (scope, decl) => {
|
|
2329
2387
|
// setup local for left
|
2330
2388
|
generate(scope, decl.left);
|
2331
2389
|
|
2332
|
-
|
2390
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2391
|
+
if (!leftName && decl.left.name) {
|
2392
|
+
leftName = decl.left.name;
|
2393
|
+
|
2394
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2395
|
+
}
|
2396
|
+
|
2333
2397
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2334
2398
|
|
2335
2399
|
depth.push('block');
|
@@ -2338,13 +2402,14 @@ const generateForOf = (scope, decl) => {
|
|
2338
2402
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2339
2403
|
// hack: this is naughty and will break things!
|
2340
2404
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2341
|
-
if (pages.
|
2405
|
+
if (pages.hasAnyString) {
|
2342
2406
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2343
2407
|
rawElements: new Array(1)
|
2344
2408
|
}, isGlobal, leftName, true, 'i16');
|
2345
2409
|
}
|
2346
2410
|
|
2347
2411
|
// set type for local
|
2412
|
+
// todo: optimize away counter and use end pointer
|
2348
2413
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2349
2414
|
[TYPES._array]: [
|
2350
2415
|
...setType(scope, leftName, TYPES.number),
|
@@ -2469,7 +2534,7 @@ const generateThrow = (scope, decl) => {
|
|
2469
2534
|
// hack: throw new X("...") -> throw "..."
|
2470
2535
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2471
2536
|
constructor = decl.argument.callee.name;
|
2472
|
-
message = decl.argument.arguments[0]
|
2537
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2473
2538
|
}
|
2474
2539
|
|
2475
2540
|
if (tags.length === 0) tags.push({
|
@@ -2530,6 +2595,8 @@ const allocPage = (reason, type) => {
|
|
2530
2595
|
|
2531
2596
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2532
2597
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2598
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2599
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2533
2600
|
|
2534
2601
|
const ind = pages.size;
|
2535
2602
|
pages.set(reason, { ind, type });
|
@@ -2563,7 +2630,8 @@ const StoreOps = {
|
|
2563
2630
|
f64: Opcodes.f64_store,
|
2564
2631
|
|
2565
2632
|
// expects i32 input!
|
2566
|
-
|
2633
|
+
i8: Opcodes.i32_store8,
|
2634
|
+
i16: Opcodes.i32_store16,
|
2567
2635
|
};
|
2568
2636
|
|
2569
2637
|
let data = [];
|
@@ -2582,6 +2650,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2582
2650
|
}
|
2583
2651
|
};
|
2584
2652
|
|
2653
|
+
const getAllocType = itemType => {
|
2654
|
+
switch (itemType) {
|
2655
|
+
case 'i8': return 'bytestring';
|
2656
|
+
case 'i16': return 'string';
|
2657
|
+
|
2658
|
+
default: return 'array';
|
2659
|
+
}
|
2660
|
+
};
|
2661
|
+
|
2585
2662
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2586
2663
|
const out = [];
|
2587
2664
|
|
@@ -2591,7 +2668,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2591
2668
|
|
2592
2669
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2593
2670
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2594
|
-
arrays.set(name, allocPage(`${itemType
|
2671
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2595
2672
|
}
|
2596
2673
|
|
2597
2674
|
const pointer = arrays.get(name);
|
@@ -2637,7 +2714,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2637
2714
|
out.push(
|
2638
2715
|
...number(0, Valtype.i32),
|
2639
2716
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2640
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2717
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2641
2718
|
);
|
2642
2719
|
}
|
2643
2720
|
|
@@ -2647,15 +2724,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2647
2724
|
return [ out, pointer ];
|
2648
2725
|
};
|
2649
2726
|
|
2650
|
-
const
|
2727
|
+
const byteStringable = str => {
|
2728
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2729
|
+
|
2730
|
+
for (let i = 0; i < str.length; i++) {
|
2731
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2732
|
+
}
|
2733
|
+
|
2734
|
+
return true;
|
2735
|
+
};
|
2736
|
+
|
2737
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2651
2738
|
const rawElements = new Array(str.length);
|
2739
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2652
2740
|
for (let i = 0; i < str.length; i++) {
|
2653
|
-
|
2741
|
+
const c = str.charCodeAt(i);
|
2742
|
+
rawElements[i] = c;
|
2743
|
+
|
2744
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2654
2745
|
}
|
2655
2746
|
|
2747
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2748
|
+
|
2656
2749
|
return makeArray(scope, {
|
2657
2750
|
rawElements
|
2658
|
-
}, global, name, false, 'i16')[0];
|
2751
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2659
2752
|
};
|
2660
2753
|
|
2661
2754
|
let arrays = new Map();
|
@@ -2683,10 +2776,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2683
2776
|
];
|
2684
2777
|
}
|
2685
2778
|
|
2779
|
+
const object = generate(scope, decl.object);
|
2780
|
+
const property = generate(scope, decl.property);
|
2781
|
+
|
2686
2782
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2687
2783
|
// hack: this is naughty and will break things!
|
2688
2784
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2689
|
-
if (pages.
|
2785
|
+
if (pages.hasAnyString) {
|
2690
2786
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2691
2787
|
rawElements: new Array(1)
|
2692
2788
|
}, _global, _name, true, 'i16');
|
@@ -2695,7 +2791,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2695
2791
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2696
2792
|
[TYPES._array]: [
|
2697
2793
|
// get index as valtype
|
2698
|
-
...
|
2794
|
+
...property,
|
2699
2795
|
|
2700
2796
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2701
2797
|
Opcodes.i32_to_u,
|
@@ -2703,7 +2799,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2703
2799
|
[ Opcodes.i32_mul ],
|
2704
2800
|
|
2705
2801
|
...(aotPointer ? [] : [
|
2706
|
-
...
|
2802
|
+
...object,
|
2707
2803
|
Opcodes.i32_to_u,
|
2708
2804
|
[ Opcodes.i32_add ]
|
2709
2805
|
]),
|
@@ -2722,14 +2818,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2722
2818
|
|
2723
2819
|
...number(0, Valtype.i32), // base 0 for store later
|
2724
2820
|
|
2725
|
-
...
|
2726
|
-
|
2821
|
+
...property,
|
2727
2822
|
Opcodes.i32_to_u,
|
2823
|
+
|
2728
2824
|
...number(ValtypeSize.i16, Valtype.i32),
|
2729
2825
|
[ Opcodes.i32_mul ],
|
2730
2826
|
|
2731
2827
|
...(aotPointer ? [] : [
|
2732
|
-
...
|
2828
|
+
...object,
|
2733
2829
|
Opcodes.i32_to_u,
|
2734
2830
|
[ Opcodes.i32_add ]
|
2735
2831
|
]),
|
@@ -2746,8 +2842,36 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2746
2842
|
...number(TYPES.string, Valtype.i32),
|
2747
2843
|
setLastType(scope)
|
2748
2844
|
],
|
2845
|
+
[TYPES._bytestring]: [
|
2846
|
+
// setup new/out array
|
2847
|
+
...newOut,
|
2848
|
+
[ Opcodes.drop ],
|
2849
|
+
|
2850
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2851
|
+
|
2852
|
+
...property,
|
2853
|
+
Opcodes.i32_to_u,
|
2854
|
+
|
2855
|
+
...(aotPointer ? [] : [
|
2856
|
+
...object,
|
2857
|
+
Opcodes.i32_to_u,
|
2858
|
+
[ Opcodes.i32_add ]
|
2859
|
+
]),
|
2860
|
+
|
2861
|
+
// load current string ind {arg}
|
2862
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2863
|
+
|
2864
|
+
// store to new string ind 0
|
2865
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2866
|
+
|
2867
|
+
// return new string (page)
|
2868
|
+
...number(newPointer),
|
2869
|
+
|
2870
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2871
|
+
setLastType(scope)
|
2872
|
+
],
|
2749
2873
|
|
2750
|
-
default:
|
2874
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2751
2875
|
});
|
2752
2876
|
};
|
2753
2877
|
|
@@ -2764,11 +2888,14 @@ const objectHack = node => {
|
|
2764
2888
|
// if object is not identifier or another member exp, give up
|
2765
2889
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2766
2890
|
|
2767
|
-
if (!objectName) objectName = objectHack(node.object)
|
2891
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2768
2892
|
|
2769
2893
|
// if .length, give up (hack within a hack!)
|
2770
2894
|
if (node.property.name === 'length') return node;
|
2771
2895
|
|
2896
|
+
// no object name, give up
|
2897
|
+
if (!objectName) return node;
|
2898
|
+
|
2772
2899
|
const name = '__' + objectName + '_' + node.property.name;
|
2773
2900
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2774
2901
|
|
@@ -2827,10 +2954,8 @@ const generateFunc = (scope, decl) => {
|
|
2827
2954
|
const func = {
|
2828
2955
|
name,
|
2829
2956
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2830
|
-
|
2831
|
-
|
2832
|
-
throws: innerScope.throws,
|
2833
|
-
index: currentFuncIndex++
|
2957
|
+
index: currentFuncIndex++,
|
2958
|
+
...innerScope
|
2834
2959
|
};
|
2835
2960
|
funcIndex[name] = func.index;
|
2836
2961
|
|
@@ -2868,6 +2993,16 @@ const generateCode = (scope, decl) => {
|
|
2868
2993
|
};
|
2869
2994
|
|
2870
2995
|
const internalConstrs = {
|
2996
|
+
Boolean: {
|
2997
|
+
generate: (scope, decl) => {
|
2998
|
+
if (decl.arguments.length === 0) return number(0);
|
2999
|
+
|
3000
|
+
// should generate/run all args
|
3001
|
+
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3002
|
+
},
|
3003
|
+
type: TYPES.boolean
|
3004
|
+
},
|
3005
|
+
|
2871
3006
|
Array: {
|
2872
3007
|
generate: (scope, decl, global, name) => {
|
2873
3008
|
// new Array(i0, i1, ...)
|