porffor 0.2.0-f2bbe1f → 0.2.0-fde989a
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 +63 -43
- package/compiler/2c.js +316 -71
- package/compiler/builtins.js +43 -19
- package/compiler/codeGen.js +217 -93
- 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 +12 -1
- package/filesize.cmd +2 -0
- package/package.json +1 -1
- package/porf +2 -0
- package/runner/index.js +15 -2
- package/tmp.c +661 -0
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);
|
@@ -86,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
86
86
|
return generateExp(scope, decl);
|
87
87
|
|
88
88
|
case 'CallExpression':
|
89
|
-
return generateCall(scope, decl, global, name);
|
89
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
90
90
|
|
91
91
|
case 'NewExpression':
|
92
92
|
return generateNew(scope, decl, global, name);
|
@@ -160,7 +160,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
160
160
|
|
161
161
|
case 'TaggedTemplateExpression': {
|
162
162
|
const funcs = {
|
163
|
-
|
163
|
+
__Porffor_asm: str => {
|
164
164
|
let out = [];
|
165
165
|
|
166
166
|
for (const line of str.split('\n')) {
|
@@ -196,19 +196,19 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
196
196
|
return out;
|
197
197
|
},
|
198
198
|
|
199
|
-
|
200
|
-
|
199
|
+
__Porffor_bs: str => [
|
200
|
+
...makeString(scope, str, undefined, undefined, true),
|
201
201
|
|
202
|
-
|
203
|
-
|
204
|
-
|
202
|
+
...number(TYPES._bytestring, Valtype.i32),
|
203
|
+
setLastType(scope)
|
204
|
+
],
|
205
|
+
__Porffor_s: str => [
|
206
|
+
...makeString(scope, str, undefined, undefined, false),
|
205
207
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
}
|
211
|
-
}
|
208
|
+
...number(TYPES.string, Valtype.i32),
|
209
|
+
setLastType(scope)
|
210
|
+
],
|
211
|
+
};
|
212
212
|
|
213
213
|
const name = decl.tag.name;
|
214
214
|
// hack for inline asm
|
@@ -274,25 +274,25 @@ const generateIdent = (scope, decl) => {
|
|
274
274
|
const name = mapName(rawName);
|
275
275
|
let local = scope.locals[rawName];
|
276
276
|
|
277
|
-
if (builtinVars
|
277
|
+
if (Object.hasOwn(builtinVars, name)) {
|
278
278
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
279
279
|
return builtinVars[name];
|
280
280
|
}
|
281
281
|
|
282
|
-
if (builtinFuncs
|
282
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
283
283
|
// todo: return an actual something
|
284
284
|
return number(1);
|
285
285
|
}
|
286
286
|
|
287
|
-
if (local === undefined) {
|
287
|
+
if (local?.idx === undefined) {
|
288
288
|
// no local var with name
|
289
|
-
if (
|
290
|
-
if (funcIndex
|
289
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
290
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
291
291
|
|
292
|
-
if (globals
|
292
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
293
293
|
}
|
294
294
|
|
295
|
-
if (local === undefined && rawName.startsWith('__')) {
|
295
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
296
296
|
// return undefined if unknown key in already known var
|
297
297
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
298
298
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -301,7 +301,7 @@ const generateIdent = (scope, decl) => {
|
|
301
301
|
if (!parentLookup[1]) return number(UNDEFINED);
|
302
302
|
}
|
303
303
|
|
304
|
-
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);
|
305
305
|
|
306
306
|
return [ [ Opcodes.local_get, local.idx ] ];
|
307
307
|
};
|
@@ -685,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
685
685
|
[ Opcodes.i32_eqz ], */
|
686
686
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
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
|
+
],
|
688
697
|
default: def
|
689
698
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
690
699
|
];
|
@@ -712,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
712
721
|
[ Opcodes.i32_eqz ],
|
713
722
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
714
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
|
+
],
|
715
735
|
default: [
|
716
736
|
// if value == 0
|
717
737
|
[ Opcodes.local_get, tmp ],
|
@@ -905,7 +925,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
905
925
|
[ Opcodes.i32_or ],
|
906
926
|
[ Opcodes.if, Blocktype.void ],
|
907
927
|
...number(0, Valtype.i32),
|
908
|
-
[ Opcodes.br,
|
928
|
+
[ Opcodes.br, 2 ],
|
909
929
|
[ Opcodes.end ],
|
910
930
|
|
911
931
|
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
@@ -961,7 +981,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
961
981
|
localInd: allLocals.length,
|
962
982
|
};
|
963
983
|
|
964
|
-
wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
|
984
|
+
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
965
985
|
}
|
966
986
|
|
967
987
|
let baseGlobalIdx, i = 0;
|
@@ -1044,7 +1064,8 @@ const TYPES = {
|
|
1044
1064
|
|
1045
1065
|
// these are not "typeof" types but tracked internally
|
1046
1066
|
_array: 0x10,
|
1047
|
-
_regexp: 0x11
|
1067
|
+
_regexp: 0x11,
|
1068
|
+
_bytestring: 0x12
|
1048
1069
|
};
|
1049
1070
|
|
1050
1071
|
const TYPE_NAMES = {
|
@@ -1058,7 +1079,8 @@ const TYPE_NAMES = {
|
|
1058
1079
|
[TYPES.bigint]: 'BigInt',
|
1059
1080
|
|
1060
1081
|
[TYPES._array]: 'Array',
|
1061
|
-
[TYPES._regexp]: 'RegExp'
|
1082
|
+
[TYPES._regexp]: 'RegExp',
|
1083
|
+
[TYPES._bytestring]: 'ByteString'
|
1062
1084
|
};
|
1063
1085
|
|
1064
1086
|
const getType = (scope, _name) => {
|
@@ -1111,6 +1133,8 @@ const getNodeType = (scope, node) => {
|
|
1111
1133
|
if (node.type === 'Literal') {
|
1112
1134
|
if (node.regex) return TYPES._regexp;
|
1113
1135
|
|
1136
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1137
|
+
|
1114
1138
|
return TYPES[typeof node.value];
|
1115
1139
|
}
|
1116
1140
|
|
@@ -1124,6 +1148,15 @@ const getNodeType = (scope, node) => {
|
|
1124
1148
|
|
1125
1149
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1126
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
|
+
|
1127
1160
|
const func = funcs.find(x => x.name === name);
|
1128
1161
|
|
1129
1162
|
if (func) {
|
@@ -1142,7 +1175,7 @@ const getNodeType = (scope, node) => {
|
|
1142
1175
|
const spl = name.slice(2).split('_');
|
1143
1176
|
|
1144
1177
|
const func = spl[spl.length - 1];
|
1145
|
-
const protoFuncs = Object.
|
1178
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1146
1179
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1147
1180
|
}
|
1148
1181
|
|
@@ -1218,7 +1251,7 @@ const getNodeType = (scope, node) => {
|
|
1218
1251
|
if (node.operator === '!') return TYPES.boolean;
|
1219
1252
|
if (node.operator === 'void') return TYPES.undefined;
|
1220
1253
|
if (node.operator === 'delete') return TYPES.boolean;
|
1221
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1254
|
+
if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
|
1222
1255
|
|
1223
1256
|
return TYPES.number;
|
1224
1257
|
}
|
@@ -1227,7 +1260,13 @@ const getNodeType = (scope, node) => {
|
|
1227
1260
|
// hack: if something.length, number type
|
1228
1261
|
if (node.property.name === 'length') return TYPES.number;
|
1229
1262
|
|
1230
|
-
//
|
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
|
1231
1270
|
return TYPES.number;
|
1232
1271
|
}
|
1233
1272
|
|
@@ -1244,28 +1283,11 @@ const getNodeType = (scope, node) => {
|
|
1244
1283
|
return ret;
|
1245
1284
|
};
|
1246
1285
|
|
1247
|
-
const toString = (scope, wasm, type) => {
|
1248
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1249
|
-
return [
|
1250
|
-
...wasm,
|
1251
|
-
[ Opcodes.local_set, tmp ],
|
1252
|
-
|
1253
|
-
...typeSwitch(scope, type, {
|
1254
|
-
[TYPES.string]: [
|
1255
|
-
[ Opcodes.local_get, tmp ]
|
1256
|
-
],
|
1257
|
-
[TYPES.undefined]: [
|
1258
|
-
// [ Opcodes.]
|
1259
|
-
]
|
1260
|
-
})
|
1261
|
-
]
|
1262
|
-
};
|
1263
|
-
|
1264
1286
|
const generateLiteral = (scope, decl, global, name) => {
|
1265
1287
|
if (decl.value === null) return number(NULL);
|
1266
1288
|
|
1289
|
+
// hack: just return 1 for regex literals
|
1267
1290
|
if (decl.regex) {
|
1268
|
-
scope.regex[name] = decl.regex;
|
1269
1291
|
return number(1);
|
1270
1292
|
}
|
1271
1293
|
|
@@ -1299,9 +1321,9 @@ const countLeftover = wasm => {
|
|
1299
1321
|
|
1300
1322
|
if (depth === 0)
|
1301
1323
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1302
|
-
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)) {}
|
1303
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++;
|
1304
|
-
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;
|
1305
1327
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1306
1328
|
else if (inst[0] === Opcodes.return) count = 0;
|
1307
1329
|
else if (inst[0] === Opcodes.call) {
|
@@ -1327,7 +1349,7 @@ const disposeLeftover = wasm => {
|
|
1327
1349
|
const generateExp = (scope, decl) => {
|
1328
1350
|
const expression = decl.expression;
|
1329
1351
|
|
1330
|
-
const out = generate(scope, expression);
|
1352
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1331
1353
|
disposeLeftover(out);
|
1332
1354
|
|
1333
1355
|
return out;
|
@@ -1385,7 +1407,7 @@ const RTArrayUtil = {
|
|
1385
1407
|
]
|
1386
1408
|
};
|
1387
1409
|
|
1388
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1410
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1389
1411
|
/* const callee = decl.callee;
|
1390
1412
|
const args = decl.arguments;
|
1391
1413
|
|
@@ -1451,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1451
1473
|
// literal.func()
|
1452
1474
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1453
1475
|
// megahack for /regex/.func()
|
1454
|
-
|
1455
|
-
|
1476
|
+
const funcName = decl.callee.property.name;
|
1477
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1456
1478
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1457
1479
|
|
1458
1480
|
funcIndex[func.name] = func.index;
|
@@ -1494,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1494
1516
|
|
1495
1517
|
if (protoName) {
|
1496
1518
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1497
|
-
|
1498
|
-
if (f) acc[x] = f;
|
1519
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1499
1520
|
return acc;
|
1500
1521
|
}, {});
|
1501
1522
|
|
@@ -1504,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1504
1525
|
// use local for cached i32 length as commonly used
|
1505
1526
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1506
1527
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1507
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1508
1528
|
|
1509
1529
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1510
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;
|
1511
1540
|
let lengthI32CacheUsed = false;
|
1512
1541
|
const protoBC = {};
|
1513
1542
|
for (const x in protoCands) {
|
@@ -1527,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1527
1556
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1528
1557
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1529
1558
|
|
1559
|
+
let optUnused = false;
|
1530
1560
|
const protoOut = protoFunc(getPointer, {
|
1531
1561
|
getCachedI32: () => {
|
1532
1562
|
lengthI32CacheUsed = true;
|
@@ -1541,10 +1571,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1541
1571
|
return makeArray(scope, {
|
1542
1572
|
rawElements: new Array(length)
|
1543
1573
|
}, _global, _name, true, itemType);
|
1574
|
+
}, () => {
|
1575
|
+
optUnused = true;
|
1576
|
+
return unusedValue;
|
1544
1577
|
});
|
1545
1578
|
|
1579
|
+
if (!optUnused) allOptUnused = false;
|
1580
|
+
|
1546
1581
|
protoBC[x] = [
|
1547
|
-
[ Opcodes.block, valtypeBinary ],
|
1582
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1548
1583
|
...protoOut,
|
1549
1584
|
|
1550
1585
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1553,11 +1588,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1553
1588
|
];
|
1554
1589
|
}
|
1555
1590
|
|
1556
|
-
|
1557
|
-
...generate(scope, target),
|
1591
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1558
1592
|
|
1559
|
-
|
1560
|
-
|
1593
|
+
return [
|
1594
|
+
...(usePointerCache ? [
|
1595
|
+
...rawPointer,
|
1596
|
+
[ Opcodes.local_set, pointerLocal ],
|
1597
|
+
] : []),
|
1561
1598
|
|
1562
1599
|
...(!lengthI32CacheUsed ? [] : [
|
1563
1600
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1569,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1569
1606
|
|
1570
1607
|
// TODO: error better
|
1571
1608
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1572
|
-
}, valtypeBinary),
|
1609
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1573
1610
|
];
|
1574
1611
|
}
|
1575
1612
|
}
|
@@ -1777,6 +1814,8 @@ const brTable = (input, bc, returns) => {
|
|
1777
1814
|
};
|
1778
1815
|
|
1779
1816
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1817
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1818
|
+
|
1780
1819
|
const known = knownType(scope, type);
|
1781
1820
|
if (known != null) {
|
1782
1821
|
return bc[known] ?? bc.default;
|
@@ -1875,6 +1914,8 @@ const extractTypeAnnotation = decl => {
|
|
1875
1914
|
const typeName = type;
|
1876
1915
|
type = typeAnnoToPorfType(type);
|
1877
1916
|
|
1917
|
+
if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
|
1918
|
+
|
1878
1919
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1879
1920
|
|
1880
1921
|
return { type, typeName, elementType };
|
@@ -1891,6 +1932,8 @@ const generateVar = (scope, decl) => {
|
|
1891
1932
|
for (const x of decl.declarations) {
|
1892
1933
|
const name = mapName(x.id.name);
|
1893
1934
|
|
1935
|
+
if (!name) return todo('destructuring is not supported yet');
|
1936
|
+
|
1894
1937
|
if (x.init && isFuncType(x.init.type)) {
|
1895
1938
|
// hack for let a = function () { ... }
|
1896
1939
|
x.init.id = { name };
|
@@ -2029,6 +2072,8 @@ const generateAssign = (scope, decl) => {
|
|
2029
2072
|
];
|
2030
2073
|
}
|
2031
2074
|
|
2075
|
+
if (!name) return todo('destructuring is not supported yet');
|
2076
|
+
|
2032
2077
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2033
2078
|
|
2034
2079
|
if (local === undefined) {
|
@@ -2167,6 +2212,8 @@ const generateUnary = (scope, decl) => {
|
|
2167
2212
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2168
2213
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2169
2214
|
|
2215
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2216
|
+
|
2170
2217
|
// object and internal types
|
2171
2218
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2172
2219
|
});
|
@@ -2272,8 +2319,10 @@ const generateFor = (scope, decl) => {
|
|
2272
2319
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2273
2320
|
depth.push('for');
|
2274
2321
|
|
2275
|
-
out.push(...generate(scope, decl.test));
|
2276
|
-
|
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 ]);
|
2277
2326
|
depth.push('if');
|
2278
2327
|
|
2279
2328
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2281,8 +2330,7 @@ const generateFor = (scope, decl) => {
|
|
2281
2330
|
out.push(...generate(scope, decl.body));
|
2282
2331
|
out.push([ Opcodes.end ]);
|
2283
2332
|
|
2284
|
-
out.push(...generate(scope, decl.update));
|
2285
|
-
depth.pop();
|
2333
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2286
2334
|
|
2287
2335
|
out.push([ Opcodes.br, 1 ]);
|
2288
2336
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2339,7 +2387,13 @@ const generateForOf = (scope, decl) => {
|
|
2339
2387
|
// setup local for left
|
2340
2388
|
generate(scope, decl.left);
|
2341
2389
|
|
2342
|
-
|
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
|
+
|
2343
2397
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2344
2398
|
|
2345
2399
|
depth.push('block');
|
@@ -2348,13 +2402,14 @@ const generateForOf = (scope, decl) => {
|
|
2348
2402
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2349
2403
|
// hack: this is naughty and will break things!
|
2350
2404
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2351
|
-
if (pages.
|
2405
|
+
if (pages.hasAnyString) {
|
2352
2406
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2353
2407
|
rawElements: new Array(1)
|
2354
2408
|
}, isGlobal, leftName, true, 'i16');
|
2355
2409
|
}
|
2356
2410
|
|
2357
2411
|
// set type for local
|
2412
|
+
// todo: optimize away counter and use end pointer
|
2358
2413
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2359
2414
|
[TYPES._array]: [
|
2360
2415
|
...setType(scope, leftName, TYPES.number),
|
@@ -2479,7 +2534,7 @@ const generateThrow = (scope, decl) => {
|
|
2479
2534
|
// hack: throw new X("...") -> throw "..."
|
2480
2535
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2481
2536
|
constructor = decl.argument.callee.name;
|
2482
|
-
message = decl.argument.arguments[0]
|
2537
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2483
2538
|
}
|
2484
2539
|
|
2485
2540
|
if (tags.length === 0) tags.push({
|
@@ -2540,6 +2595,8 @@ const allocPage = (reason, type) => {
|
|
2540
2595
|
|
2541
2596
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2542
2597
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2598
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2599
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2543
2600
|
|
2544
2601
|
const ind = pages.size;
|
2545
2602
|
pages.set(reason, { ind, type });
|
@@ -2573,25 +2630,34 @@ const StoreOps = {
|
|
2573
2630
|
f64: Opcodes.f64_store,
|
2574
2631
|
|
2575
2632
|
// expects i32 input!
|
2576
|
-
|
2633
|
+
i8: Opcodes.i32_store8,
|
2634
|
+
i16: Opcodes.i32_store16,
|
2577
2635
|
};
|
2578
2636
|
|
2579
2637
|
let data = [];
|
2580
2638
|
|
2581
|
-
const compileBytes = (val, itemType
|
2639
|
+
const compileBytes = (val, itemType) => {
|
2582
2640
|
// todo: this is a mess and needs confirming / ????
|
2583
2641
|
switch (itemType) {
|
2584
2642
|
case 'i8': return [ val % 256 ];
|
2585
|
-
case 'i16': return [ val % 256,
|
2586
|
-
|
2587
|
-
case 'i32':
|
2588
|
-
|
2589
|
-
return enforceFourBytes(signedLEB128(val));
|
2643
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2644
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
2645
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
2646
|
+
// todo: i64
|
2590
2647
|
|
2591
2648
|
case 'f64': return ieee754_binary64(val);
|
2592
2649
|
}
|
2593
2650
|
};
|
2594
2651
|
|
2652
|
+
const getAllocType = itemType => {
|
2653
|
+
switch (itemType) {
|
2654
|
+
case 'i8': return 'bytestring';
|
2655
|
+
case 'i16': return 'string';
|
2656
|
+
|
2657
|
+
default: return 'array';
|
2658
|
+
}
|
2659
|
+
};
|
2660
|
+
|
2595
2661
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2596
2662
|
const out = [];
|
2597
2663
|
|
@@ -2601,7 +2667,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2601
2667
|
|
2602
2668
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2603
2669
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2604
|
-
arrays.set(name, allocPage(`${itemType
|
2670
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2605
2671
|
}
|
2606
2672
|
|
2607
2673
|
const pointer = arrays.get(name);
|
@@ -2647,7 +2713,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2647
2713
|
out.push(
|
2648
2714
|
...number(0, Valtype.i32),
|
2649
2715
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2650
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2716
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2651
2717
|
);
|
2652
2718
|
}
|
2653
2719
|
|
@@ -2657,15 +2723,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2657
2723
|
return [ out, pointer ];
|
2658
2724
|
};
|
2659
2725
|
|
2660
|
-
const
|
2726
|
+
const byteStringable = str => {
|
2727
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2728
|
+
|
2729
|
+
for (let i = 0; i < str.length; i++) {
|
2730
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2731
|
+
}
|
2732
|
+
|
2733
|
+
return true;
|
2734
|
+
};
|
2735
|
+
|
2736
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2661
2737
|
const rawElements = new Array(str.length);
|
2738
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2662
2739
|
for (let i = 0; i < str.length; i++) {
|
2663
|
-
|
2740
|
+
const c = str.charCodeAt(i);
|
2741
|
+
rawElements[i] = c;
|
2742
|
+
|
2743
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2664
2744
|
}
|
2665
2745
|
|
2746
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2747
|
+
|
2666
2748
|
return makeArray(scope, {
|
2667
2749
|
rawElements
|
2668
|
-
}, global, name, false, 'i16')[0];
|
2750
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2669
2751
|
};
|
2670
2752
|
|
2671
2753
|
let arrays = new Map();
|
@@ -2693,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2693
2775
|
];
|
2694
2776
|
}
|
2695
2777
|
|
2778
|
+
const object = generate(scope, decl.object);
|
2779
|
+
const property = generate(scope, decl.property);
|
2780
|
+
|
2696
2781
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2697
2782
|
// hack: this is naughty and will break things!
|
2698
2783
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2699
|
-
if (pages.
|
2784
|
+
if (pages.hasAnyString) {
|
2700
2785
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2701
2786
|
rawElements: new Array(1)
|
2702
2787
|
}, _global, _name, true, 'i16');
|
@@ -2705,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2705
2790
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2706
2791
|
[TYPES._array]: [
|
2707
2792
|
// get index as valtype
|
2708
|
-
...
|
2793
|
+
...property,
|
2709
2794
|
|
2710
2795
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2711
2796
|
Opcodes.i32_to_u,
|
@@ -2713,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2713
2798
|
[ Opcodes.i32_mul ],
|
2714
2799
|
|
2715
2800
|
...(aotPointer ? [] : [
|
2716
|
-
...
|
2801
|
+
...object,
|
2717
2802
|
Opcodes.i32_to_u,
|
2718
2803
|
[ Opcodes.i32_add ]
|
2719
2804
|
]),
|
@@ -2732,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2732
2817
|
|
2733
2818
|
...number(0, Valtype.i32), // base 0 for store later
|
2734
2819
|
|
2735
|
-
...
|
2736
|
-
|
2820
|
+
...property,
|
2737
2821
|
Opcodes.i32_to_u,
|
2822
|
+
|
2738
2823
|
...number(ValtypeSize.i16, Valtype.i32),
|
2739
2824
|
[ Opcodes.i32_mul ],
|
2740
2825
|
|
2741
2826
|
...(aotPointer ? [] : [
|
2742
|
-
...
|
2827
|
+
...object,
|
2743
2828
|
Opcodes.i32_to_u,
|
2744
2829
|
[ Opcodes.i32_add ]
|
2745
2830
|
]),
|
@@ -2756,8 +2841,36 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2756
2841
|
...number(TYPES.string, Valtype.i32),
|
2757
2842
|
setLastType(scope)
|
2758
2843
|
],
|
2844
|
+
[TYPES._bytestring]: [
|
2845
|
+
// setup new/out array
|
2846
|
+
...newOut,
|
2847
|
+
[ Opcodes.drop ],
|
2848
|
+
|
2849
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2850
|
+
|
2851
|
+
...property,
|
2852
|
+
Opcodes.i32_to_u,
|
2853
|
+
|
2854
|
+
...(aotPointer ? [] : [
|
2855
|
+
...object,
|
2856
|
+
Opcodes.i32_to_u,
|
2857
|
+
[ Opcodes.i32_add ]
|
2858
|
+
]),
|
2859
|
+
|
2860
|
+
// load current string ind {arg}
|
2861
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2759
2862
|
|
2760
|
-
|
2863
|
+
// store to new string ind 0
|
2864
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2865
|
+
|
2866
|
+
// return new string (page)
|
2867
|
+
...number(newPointer),
|
2868
|
+
|
2869
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2870
|
+
setLastType(scope)
|
2871
|
+
],
|
2872
|
+
|
2873
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2761
2874
|
});
|
2762
2875
|
};
|
2763
2876
|
|
@@ -2774,11 +2887,14 @@ const objectHack = node => {
|
|
2774
2887
|
// if object is not identifier or another member exp, give up
|
2775
2888
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2776
2889
|
|
2777
|
-
if (!objectName) objectName = objectHack(node.object)
|
2890
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2778
2891
|
|
2779
2892
|
// if .length, give up (hack within a hack!)
|
2780
2893
|
if (node.property.name === 'length') return node;
|
2781
2894
|
|
2895
|
+
// no object name, give up
|
2896
|
+
if (!objectName) return node;
|
2897
|
+
|
2782
2898
|
const name = '__' + objectName + '_' + node.property.name;
|
2783
2899
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2784
2900
|
|
@@ -2837,10 +2953,8 @@ const generateFunc = (scope, decl) => {
|
|
2837
2953
|
const func = {
|
2838
2954
|
name,
|
2839
2955
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2840
|
-
|
2841
|
-
|
2842
|
-
throws: innerScope.throws,
|
2843
|
-
index: currentFuncIndex++
|
2956
|
+
index: currentFuncIndex++,
|
2957
|
+
...innerScope
|
2844
2958
|
};
|
2845
2959
|
funcIndex[name] = func.index;
|
2846
2960
|
|
@@ -2878,6 +2992,16 @@ const generateCode = (scope, decl) => {
|
|
2878
2992
|
};
|
2879
2993
|
|
2880
2994
|
const internalConstrs = {
|
2995
|
+
Boolean: {
|
2996
|
+
generate: (scope, decl) => {
|
2997
|
+
if (decl.arguments.length === 0) return number(0);
|
2998
|
+
|
2999
|
+
// should generate/run all args
|
3000
|
+
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3001
|
+
},
|
3002
|
+
type: TYPES.boolean
|
3003
|
+
},
|
3004
|
+
|
2881
3005
|
Array: {
|
2882
3006
|
generate: (scope, decl, global, name) => {
|
2883
3007
|
// new Array(i0, i1, ...)
|