porffor 0.2.0-aea77ff → 0.2.0-c1d7382
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 +66 -43
- package/compiler/2c.js +316 -71
- package/compiler/builtins.js +43 -19
- package/compiler/codeGen.js +218 -103
- 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
|
|
@@ -1278,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1278
1300
|
return number(decl.value ? 1 : 0);
|
1279
1301
|
|
1280
1302
|
case 'string':
|
1281
|
-
|
1282
|
-
const rawElements = new Array(str.length);
|
1283
|
-
let j = 0;
|
1284
|
-
for (let i = 0; i < str.length; i++) {
|
1285
|
-
rawElements[i] = str.charCodeAt(i);
|
1286
|
-
}
|
1287
|
-
|
1288
|
-
return makeArray(scope, {
|
1289
|
-
rawElements
|
1290
|
-
}, global, name, false, 'i16')[0];
|
1303
|
+
return makeString(scope, decl.value, global, name);
|
1291
1304
|
|
1292
1305
|
default:
|
1293
1306
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1308,9 +1321,9 @@ const countLeftover = wasm => {
|
|
1308
1321
|
|
1309
1322
|
if (depth === 0)
|
1310
1323
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1311
|
-
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)) {}
|
1312
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++;
|
1313
|
-
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;
|
1314
1327
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1315
1328
|
else if (inst[0] === Opcodes.return) count = 0;
|
1316
1329
|
else if (inst[0] === Opcodes.call) {
|
@@ -1336,7 +1349,7 @@ const disposeLeftover = wasm => {
|
|
1336
1349
|
const generateExp = (scope, decl) => {
|
1337
1350
|
const expression = decl.expression;
|
1338
1351
|
|
1339
|
-
const out = generate(scope, expression);
|
1352
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1340
1353
|
disposeLeftover(out);
|
1341
1354
|
|
1342
1355
|
return out;
|
@@ -1394,7 +1407,7 @@ const RTArrayUtil = {
|
|
1394
1407
|
]
|
1395
1408
|
};
|
1396
1409
|
|
1397
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1410
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1398
1411
|
/* const callee = decl.callee;
|
1399
1412
|
const args = decl.arguments;
|
1400
1413
|
|
@@ -1460,8 +1473,8 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1460
1473
|
// literal.func()
|
1461
1474
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1462
1475
|
// megahack for /regex/.func()
|
1463
|
-
|
1464
|
-
|
1476
|
+
const funcName = decl.callee.property.name;
|
1477
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1465
1478
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1466
1479
|
|
1467
1480
|
funcIndex[func.name] = func.index;
|
@@ -1503,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1503
1516
|
|
1504
1517
|
if (protoName) {
|
1505
1518
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1506
|
-
|
1507
|
-
if (f) acc[x] = f;
|
1519
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1508
1520
|
return acc;
|
1509
1521
|
}, {});
|
1510
1522
|
|
@@ -1513,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1513
1525
|
// use local for cached i32 length as commonly used
|
1514
1526
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1515
1527
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1516
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1517
1528
|
|
1518
1529
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1519
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;
|
1520
1540
|
let lengthI32CacheUsed = false;
|
1521
1541
|
const protoBC = {};
|
1522
1542
|
for (const x in protoCands) {
|
@@ -1536,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1536
1556
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1537
1557
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1538
1558
|
|
1559
|
+
let optUnused = false;
|
1539
1560
|
const protoOut = protoFunc(getPointer, {
|
1540
1561
|
getCachedI32: () => {
|
1541
1562
|
lengthI32CacheUsed = true;
|
@@ -1550,10 +1571,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1550
1571
|
return makeArray(scope, {
|
1551
1572
|
rawElements: new Array(length)
|
1552
1573
|
}, _global, _name, true, itemType);
|
1574
|
+
}, () => {
|
1575
|
+
optUnused = true;
|
1576
|
+
return unusedValue;
|
1553
1577
|
});
|
1554
1578
|
|
1579
|
+
if (!optUnused) allOptUnused = false;
|
1580
|
+
|
1555
1581
|
protoBC[x] = [
|
1556
|
-
[ Opcodes.block, valtypeBinary ],
|
1582
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1557
1583
|
...protoOut,
|
1558
1584
|
|
1559
1585
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1562,11 +1588,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1562
1588
|
];
|
1563
1589
|
}
|
1564
1590
|
|
1565
|
-
|
1566
|
-
...generate(scope, target),
|
1591
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1567
1592
|
|
1568
|
-
|
1569
|
-
|
1593
|
+
return [
|
1594
|
+
...(usePointerCache ? [
|
1595
|
+
...rawPointer,
|
1596
|
+
[ Opcodes.local_set, pointerLocal ],
|
1597
|
+
] : []),
|
1570
1598
|
|
1571
1599
|
...(!lengthI32CacheUsed ? [] : [
|
1572
1600
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1578,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1578
1606
|
|
1579
1607
|
// TODO: error better
|
1580
1608
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1581
|
-
}, valtypeBinary),
|
1609
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1582
1610
|
];
|
1583
1611
|
}
|
1584
1612
|
}
|
@@ -1786,6 +1814,8 @@ const brTable = (input, bc, returns) => {
|
|
1786
1814
|
};
|
1787
1815
|
|
1788
1816
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1817
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1818
|
+
|
1789
1819
|
const known = knownType(scope, type);
|
1790
1820
|
if (known != null) {
|
1791
1821
|
return bc[known] ?? bc.default;
|
@@ -1884,6 +1914,8 @@ const extractTypeAnnotation = decl => {
|
|
1884
1914
|
const typeName = type;
|
1885
1915
|
type = typeAnnoToPorfType(type);
|
1886
1916
|
|
1917
|
+
if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
|
1918
|
+
|
1887
1919
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1888
1920
|
|
1889
1921
|
return { type, typeName, elementType };
|
@@ -1900,6 +1932,8 @@ const generateVar = (scope, decl) => {
|
|
1900
1932
|
for (const x of decl.declarations) {
|
1901
1933
|
const name = mapName(x.id.name);
|
1902
1934
|
|
1935
|
+
if (!name) return todo('destructuring is not supported yet');
|
1936
|
+
|
1903
1937
|
if (x.init && isFuncType(x.init.type)) {
|
1904
1938
|
// hack for let a = function () { ... }
|
1905
1939
|
x.init.id = { name };
|
@@ -2038,6 +2072,8 @@ const generateAssign = (scope, decl) => {
|
|
2038
2072
|
];
|
2039
2073
|
}
|
2040
2074
|
|
2075
|
+
if (!name) return todo('destructuring is not supported yet');
|
2076
|
+
|
2041
2077
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2042
2078
|
|
2043
2079
|
if (local === undefined) {
|
@@ -2176,6 +2212,8 @@ const generateUnary = (scope, decl) => {
|
|
2176
2212
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2177
2213
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2178
2214
|
|
2215
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2216
|
+
|
2179
2217
|
// object and internal types
|
2180
2218
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2181
2219
|
});
|
@@ -2281,8 +2319,10 @@ const generateFor = (scope, decl) => {
|
|
2281
2319
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2282
2320
|
depth.push('for');
|
2283
2321
|
|
2284
|
-
out.push(...generate(scope, decl.test));
|
2285
|
-
|
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 ]);
|
2286
2326
|
depth.push('if');
|
2287
2327
|
|
2288
2328
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2290,8 +2330,7 @@ const generateFor = (scope, decl) => {
|
|
2290
2330
|
out.push(...generate(scope, decl.body));
|
2291
2331
|
out.push([ Opcodes.end ]);
|
2292
2332
|
|
2293
|
-
out.push(...generate(scope, decl.update));
|
2294
|
-
depth.pop();
|
2333
|
+
if (decl.update) out.push(...generate(scope, decl.update));
|
2295
2334
|
|
2296
2335
|
out.push([ Opcodes.br, 1 ]);
|
2297
2336
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2348,7 +2387,13 @@ const generateForOf = (scope, decl) => {
|
|
2348
2387
|
// setup local for left
|
2349
2388
|
generate(scope, decl.left);
|
2350
2389
|
|
2351
|
-
|
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
|
+
|
2352
2397
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2353
2398
|
|
2354
2399
|
depth.push('block');
|
@@ -2357,13 +2402,14 @@ const generateForOf = (scope, decl) => {
|
|
2357
2402
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2358
2403
|
// hack: this is naughty and will break things!
|
2359
2404
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2360
|
-
if (pages.
|
2405
|
+
if (pages.hasAnyString) {
|
2361
2406
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2362
2407
|
rawElements: new Array(1)
|
2363
2408
|
}, isGlobal, leftName, true, 'i16');
|
2364
2409
|
}
|
2365
2410
|
|
2366
2411
|
// set type for local
|
2412
|
+
// todo: optimize away counter and use end pointer
|
2367
2413
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2368
2414
|
[TYPES._array]: [
|
2369
2415
|
...setType(scope, leftName, TYPES.number),
|
@@ -2488,7 +2534,7 @@ const generateThrow = (scope, decl) => {
|
|
2488
2534
|
// hack: throw new X("...") -> throw "..."
|
2489
2535
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2490
2536
|
constructor = decl.argument.callee.name;
|
2491
|
-
message = decl.argument.arguments[0]
|
2537
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2492
2538
|
}
|
2493
2539
|
|
2494
2540
|
if (tags.length === 0) tags.push({
|
@@ -2549,6 +2595,8 @@ const allocPage = (reason, type) => {
|
|
2549
2595
|
|
2550
2596
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2551
2597
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2598
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2599
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2552
2600
|
|
2553
2601
|
const ind = pages.size;
|
2554
2602
|
pages.set(reason, { ind, type });
|
@@ -2582,25 +2630,34 @@ const StoreOps = {
|
|
2582
2630
|
f64: Opcodes.f64_store,
|
2583
2631
|
|
2584
2632
|
// expects i32 input!
|
2585
|
-
|
2633
|
+
i8: Opcodes.i32_store8,
|
2634
|
+
i16: Opcodes.i32_store16,
|
2586
2635
|
};
|
2587
2636
|
|
2588
2637
|
let data = [];
|
2589
2638
|
|
2590
|
-
const compileBytes = (val, itemType
|
2639
|
+
const compileBytes = (val, itemType) => {
|
2591
2640
|
// todo: this is a mess and needs confirming / ????
|
2592
2641
|
switch (itemType) {
|
2593
2642
|
case 'i8': return [ val % 256 ];
|
2594
|
-
case 'i16': return [ val % 256,
|
2595
|
-
|
2596
|
-
case 'i32':
|
2597
|
-
|
2598
|
-
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
|
2599
2647
|
|
2600
2648
|
case 'f64': return ieee754_binary64(val);
|
2601
2649
|
}
|
2602
2650
|
};
|
2603
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
|
+
|
2604
2661
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2605
2662
|
const out = [];
|
2606
2663
|
|
@@ -2610,7 +2667,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2610
2667
|
|
2611
2668
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2612
2669
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2613
|
-
arrays.set(name, allocPage(`${itemType
|
2670
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2614
2671
|
}
|
2615
2672
|
|
2616
2673
|
const pointer = arrays.get(name);
|
@@ -2656,7 +2713,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2656
2713
|
out.push(
|
2657
2714
|
...number(0, Valtype.i32),
|
2658
2715
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2659
|
-
[ 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]) ]
|
2660
2717
|
);
|
2661
2718
|
}
|
2662
2719
|
|
@@ -2666,15 +2723,31 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2666
2723
|
return [ out, pointer ];
|
2667
2724
|
};
|
2668
2725
|
|
2669
|
-
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) => {
|
2670
2737
|
const rawElements = new Array(str.length);
|
2738
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2671
2739
|
for (let i = 0; i < str.length; i++) {
|
2672
|
-
|
2740
|
+
const c = str.charCodeAt(i);
|
2741
|
+
rawElements[i] = c;
|
2742
|
+
|
2743
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2673
2744
|
}
|
2674
2745
|
|
2746
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
2747
|
+
|
2675
2748
|
return makeArray(scope, {
|
2676
2749
|
rawElements
|
2677
|
-
}, global, name, false, 'i16')[0];
|
2750
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2678
2751
|
};
|
2679
2752
|
|
2680
2753
|
let arrays = new Map();
|
@@ -2702,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2702
2775
|
];
|
2703
2776
|
}
|
2704
2777
|
|
2778
|
+
const object = generate(scope, decl.object);
|
2779
|
+
const property = generate(scope, decl.property);
|
2780
|
+
|
2705
2781
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2706
2782
|
// hack: this is naughty and will break things!
|
2707
2783
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2708
|
-
if (pages.
|
2784
|
+
if (pages.hasAnyString) {
|
2709
2785
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2710
2786
|
rawElements: new Array(1)
|
2711
2787
|
}, _global, _name, true, 'i16');
|
@@ -2714,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2714
2790
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2715
2791
|
[TYPES._array]: [
|
2716
2792
|
// get index as valtype
|
2717
|
-
...
|
2793
|
+
...property,
|
2718
2794
|
|
2719
2795
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2720
2796
|
Opcodes.i32_to_u,
|
@@ -2722,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2722
2798
|
[ Opcodes.i32_mul ],
|
2723
2799
|
|
2724
2800
|
...(aotPointer ? [] : [
|
2725
|
-
...
|
2801
|
+
...object,
|
2726
2802
|
Opcodes.i32_to_u,
|
2727
2803
|
[ Opcodes.i32_add ]
|
2728
2804
|
]),
|
@@ -2741,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2741
2817
|
|
2742
2818
|
...number(0, Valtype.i32), // base 0 for store later
|
2743
2819
|
|
2744
|
-
...
|
2745
|
-
|
2820
|
+
...property,
|
2746
2821
|
Opcodes.i32_to_u,
|
2822
|
+
|
2747
2823
|
...number(ValtypeSize.i16, Valtype.i32),
|
2748
2824
|
[ Opcodes.i32_mul ],
|
2749
2825
|
|
2750
2826
|
...(aotPointer ? [] : [
|
2751
|
-
...
|
2827
|
+
...object,
|
2752
2828
|
Opcodes.i32_to_u,
|
2753
2829
|
[ Opcodes.i32_add ]
|
2754
2830
|
]),
|
@@ -2765,8 +2841,36 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2765
2841
|
...number(TYPES.string, Valtype.i32),
|
2766
2842
|
setLastType(scope)
|
2767
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) ],
|
2768
2862
|
|
2769
|
-
|
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')
|
2770
2874
|
});
|
2771
2875
|
};
|
2772
2876
|
|
@@ -2783,11 +2887,14 @@ const objectHack = node => {
|
|
2783
2887
|
// if object is not identifier or another member exp, give up
|
2784
2888
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2785
2889
|
|
2786
|
-
if (!objectName) objectName = objectHack(node.object)
|
2890
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2787
2891
|
|
2788
2892
|
// if .length, give up (hack within a hack!)
|
2789
2893
|
if (node.property.name === 'length') return node;
|
2790
2894
|
|
2895
|
+
// no object name, give up
|
2896
|
+
if (!objectName) return node;
|
2897
|
+
|
2791
2898
|
const name = '__' + objectName + '_' + node.property.name;
|
2792
2899
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2793
2900
|
|
@@ -2846,10 +2953,8 @@ const generateFunc = (scope, decl) => {
|
|
2846
2953
|
const func = {
|
2847
2954
|
name,
|
2848
2955
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2849
|
-
|
2850
|
-
|
2851
|
-
throws: innerScope.throws,
|
2852
|
-
index: currentFuncIndex++
|
2956
|
+
index: currentFuncIndex++,
|
2957
|
+
...innerScope
|
2853
2958
|
};
|
2854
2959
|
funcIndex[name] = func.index;
|
2855
2960
|
|
@@ -2887,6 +2992,16 @@ const generateCode = (scope, decl) => {
|
|
2887
2992
|
};
|
2888
2993
|
|
2889
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
|
+
|
2890
3005
|
Array: {
|
2891
3006
|
generate: (scope, decl, global, name) => {
|
2892
3007
|
// new Array(i0, i1, ...)
|