porffor 0.2.0-29c477a → 0.2.0-2b0f14b
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 +8 -5
- package/compiler/2c.js +316 -71
- package/compiler/builtins.js +43 -19
- package/compiler/codeGen.js +212 -87
- 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/runner/index.js +15 -2
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,7 +2630,8 @@ 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 = [];
|
@@ -2592,6 +2650,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2592
2650
|
}
|
2593
2651
|
};
|
2594
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
|
+
|
2595
2662
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2596
2663
|
const out = [];
|
2597
2664
|
|
@@ -2601,7 +2668,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2601
2668
|
|
2602
2669
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2603
2670
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2604
|
-
arrays.set(name, allocPage(`${itemType
|
2671
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2605
2672
|
}
|
2606
2673
|
|
2607
2674
|
const pointer = arrays.get(name);
|
@@ -2647,7 +2714,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2647
2714
|
out.push(
|
2648
2715
|
...number(0, Valtype.i32),
|
2649
2716
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2650
|
-
[ 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]) ]
|
2651
2718
|
);
|
2652
2719
|
}
|
2653
2720
|
|
@@ -2657,15 +2724,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2657
2724
|
return [ out, pointer ];
|
2658
2725
|
};
|
2659
2726
|
|
2660
|
-
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) => {
|
2661
2738
|
const rawElements = new Array(str.length);
|
2739
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2662
2740
|
for (let i = 0; i < str.length; i++) {
|
2663
|
-
|
2741
|
+
const c = str.charCodeAt(i);
|
2742
|
+
rawElements[i] = c;
|
2743
|
+
|
2744
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2664
2745
|
}
|
2665
2746
|
|
2747
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2748
|
+
|
2666
2749
|
return makeArray(scope, {
|
2667
2750
|
rawElements
|
2668
|
-
}, global, name, false, 'i16')[0];
|
2751
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2669
2752
|
};
|
2670
2753
|
|
2671
2754
|
let arrays = new Map();
|
@@ -2693,10 +2776,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2693
2776
|
];
|
2694
2777
|
}
|
2695
2778
|
|
2779
|
+
const object = generate(scope, decl.object);
|
2780
|
+
const property = generate(scope, decl.property);
|
2781
|
+
|
2696
2782
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2697
2783
|
// hack: this is naughty and will break things!
|
2698
2784
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2699
|
-
if (pages.
|
2785
|
+
if (pages.hasAnyString) {
|
2700
2786
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2701
2787
|
rawElements: new Array(1)
|
2702
2788
|
}, _global, _name, true, 'i16');
|
@@ -2705,7 +2791,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2705
2791
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2706
2792
|
[TYPES._array]: [
|
2707
2793
|
// get index as valtype
|
2708
|
-
...
|
2794
|
+
...property,
|
2709
2795
|
|
2710
2796
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2711
2797
|
Opcodes.i32_to_u,
|
@@ -2713,7 +2799,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2713
2799
|
[ Opcodes.i32_mul ],
|
2714
2800
|
|
2715
2801
|
...(aotPointer ? [] : [
|
2716
|
-
...
|
2802
|
+
...object,
|
2717
2803
|
Opcodes.i32_to_u,
|
2718
2804
|
[ Opcodes.i32_add ]
|
2719
2805
|
]),
|
@@ -2732,14 +2818,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2732
2818
|
|
2733
2819
|
...number(0, Valtype.i32), // base 0 for store later
|
2734
2820
|
|
2735
|
-
...
|
2736
|
-
|
2821
|
+
...property,
|
2737
2822
|
Opcodes.i32_to_u,
|
2823
|
+
|
2738
2824
|
...number(ValtypeSize.i16, Valtype.i32),
|
2739
2825
|
[ Opcodes.i32_mul ],
|
2740
2826
|
|
2741
2827
|
...(aotPointer ? [] : [
|
2742
|
-
...
|
2828
|
+
...object,
|
2743
2829
|
Opcodes.i32_to_u,
|
2744
2830
|
[ Opcodes.i32_add ]
|
2745
2831
|
]),
|
@@ -2756,8 +2842,36 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2756
2842
|
...number(TYPES.string, Valtype.i32),
|
2757
2843
|
setLastType(scope)
|
2758
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) ],
|
2759
2863
|
|
2760
|
-
|
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
|
+
],
|
2873
|
+
|
2874
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
2761
2875
|
});
|
2762
2876
|
};
|
2763
2877
|
|
@@ -2774,11 +2888,14 @@ const objectHack = node => {
|
|
2774
2888
|
// if object is not identifier or another member exp, give up
|
2775
2889
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2776
2890
|
|
2777
|
-
if (!objectName) objectName = objectHack(node.object)
|
2891
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2778
2892
|
|
2779
2893
|
// if .length, give up (hack within a hack!)
|
2780
2894
|
if (node.property.name === 'length') return node;
|
2781
2895
|
|
2896
|
+
// no object name, give up
|
2897
|
+
if (!objectName) return node;
|
2898
|
+
|
2782
2899
|
const name = '__' + objectName + '_' + node.property.name;
|
2783
2900
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2784
2901
|
|
@@ -2837,10 +2954,8 @@ const generateFunc = (scope, decl) => {
|
|
2837
2954
|
const func = {
|
2838
2955
|
name,
|
2839
2956
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2840
|
-
|
2841
|
-
|
2842
|
-
throws: innerScope.throws,
|
2843
|
-
index: currentFuncIndex++
|
2957
|
+
index: currentFuncIndex++,
|
2958
|
+
...innerScope
|
2844
2959
|
};
|
2845
2960
|
funcIndex[name] = func.index;
|
2846
2961
|
|
@@ -2878,6 +2993,16 @@ const generateCode = (scope, decl) => {
|
|
2878
2993
|
};
|
2879
2994
|
|
2880
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
|
+
|
2881
3006
|
Array: {
|
2882
3007
|
generate: (scope, decl, global, name) => {
|
2883
3008
|
// new Array(i0, i1, ...)
|