porffor 0.0.0-d650361 → 0.0.0-e975d7a

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.
@@ -1,9 +1,9 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4 } from "./embedding.js";
6
+ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
7
7
  import parse from "./parse.js";
8
8
  import * as Rhemyn from "../rhemyn/compile.js";
9
9
 
@@ -326,6 +326,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
326
326
  return idx;
327
327
  };
328
328
 
329
+ const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
330
+
329
331
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
330
332
  const checks = {
331
333
  '||': falsy,
@@ -338,6 +340,32 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
338
340
  // generic structure for {a} OP {b}
339
341
  // -->
340
342
  // _ = {a}; if (OP_CHECK) {b} else _
343
+
344
+ // if we can, use int tmp and convert at the end to help prevent unneeded conversions
345
+ // (like if we are in an if condition - very common)
346
+ const leftIsInt = isIntOp(left[left.length - 1]);
347
+ const rightIsInt = isIntOp(right[right.length - 1]);
348
+
349
+ const canInt = leftIsInt && rightIsInt;
350
+
351
+ if (canInt) {
352
+ // remove int -> float conversions from left and right
353
+ left.pop();
354
+ right.pop();
355
+
356
+ return [
357
+ ...left,
358
+ [ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
359
+ ...checks[op](scope, [], leftType, true),
360
+ [ Opcodes.if, Valtype.i32 ],
361
+ ...right,
362
+ [ Opcodes.else ],
363
+ [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
364
+ [ Opcodes.end ],
365
+ Opcodes.i32_from
366
+ ];
367
+ }
368
+
341
369
  return [
342
370
  ...left,
343
371
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
@@ -361,6 +389,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
361
389
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
362
390
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
363
391
 
392
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
393
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
394
+
364
395
  if (assign) {
365
396
  const pointer = arrays.get(name ?? '$undeclared');
366
397
 
@@ -588,12 +619,12 @@ const compareStrings = (scope, left, right) => {
588
619
  ];
589
620
  };
590
621
 
591
- const truthy = (scope, wasm, type) => {
622
+ const truthy = (scope, wasm, type, int = false) => {
592
623
  // arrays are always truthy
593
624
  if (type === TYPES._array) return [
594
625
  ...wasm,
595
626
  [ Opcodes.drop ],
596
- ...number(1)
627
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
597
628
  ];
598
629
 
599
630
  if (type === TYPES.string) {
@@ -609,8 +640,8 @@ const truthy = (scope, wasm, type) => {
609
640
  // if length != 0
610
641
  /* [ Opcodes.i32_eqz ],
611
642
  [ Opcodes.i32_eqz ], */
612
- Opcodes.i32_from_u
613
- ]
643
+ ...(int ? [] : [ Opcodes.i32_from_u ])
644
+ ];
614
645
  }
615
646
 
616
647
  // if != 0
@@ -623,12 +654,12 @@ const truthy = (scope, wasm, type) => {
623
654
  ];
624
655
  };
625
656
 
626
- const falsy = (scope, wasm, type) => {
657
+ const falsy = (scope, wasm, type, int = false) => {
627
658
  // arrays are always truthy
628
659
  if (type === TYPES._array) return [
629
660
  ...wasm,
630
661
  [ Opcodes.drop ],
631
- ...number(0)
662
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
632
663
  ];
633
664
 
634
665
  if (type === TYPES.string) {
@@ -643,7 +674,7 @@ const falsy = (scope, wasm, type) => {
643
674
 
644
675
  // if length == 0
645
676
  [ Opcodes.i32_eqz ],
646
- Opcodes.i32_from_u
677
+ ...(int ? [] : [ Opcodes.i32_from_u ])
647
678
  ]
648
679
  }
649
680
 
@@ -651,31 +682,29 @@ const falsy = (scope, wasm, type) => {
651
682
  return [
652
683
  ...wasm,
653
684
 
654
- ...Opcodes.eqz,
655
- Opcodes.i32_from_u
685
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
656
686
  ];
657
687
  };
658
688
 
659
- const nullish = (scope, wasm, type) => {
689
+ const nullish = (scope, wasm, type, int = false) => {
660
690
  // undefined
661
691
  if (type === TYPES.undefined) return [
662
692
  ...wasm,
663
693
  [ Opcodes.drop ],
664
- ...number(1)
694
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
665
695
  ];
666
696
 
667
697
  // null (if object and = "0")
668
698
  if (type === TYPES.object) return [
669
699
  ...wasm,
670
- ...Opcodes.eqz,
671
- Opcodes.i32_from_u
700
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
672
701
  ];
673
702
 
674
703
  // not
675
704
  return [
676
705
  ...wasm,
677
706
  [ Opcodes.drop ],
678
- ...number(0)
707
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
679
708
  ];
680
709
  };
681
710
 
@@ -989,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
989
1018
  case 'bigint': return number(TYPES.bigint);
990
1019
  }
991
1020
 
1021
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
1022
+ let wellFormed = aotWFA ? true : undefined;
1023
+
992
1024
  const str = decl.value;
993
1025
  const rawElements = new Array(str.length);
1026
+ let j = 0;
994
1027
  for (let i = 0; i < str.length; i++) {
995
1028
  rawElements[i] = str.charCodeAt(i);
1029
+
1030
+ if (wellFormed) {
1031
+ // check if surrogate
1032
+ if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
1033
+ // unpaired trailing surrogate
1034
+ if (str.charCodeAt(j) >= 0xDC00) {
1035
+ wellFormed = false;
1036
+ }
1037
+
1038
+ // unpaired leading surrogate
1039
+ // if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
1040
+ if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
1041
+ wellFormed = false;
1042
+ }
1043
+ }
1044
+
1045
+ j++;
1046
+ }
996
1047
  }
997
1048
 
1049
+ // console.log(wellFormed, str);
1050
+
1051
+ if (aotWFA) addVarMeta(name, { wellFormed });
1052
+
998
1053
  return makeArray(scope, {
999
1054
  rawElements
1000
1055
  }, global, name, false, 'i16')[0];
@@ -1213,29 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
1213
1268
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1214
1269
 
1215
1270
  let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1271
+ let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1216
1272
 
1217
1273
  // use local for cached i32 length as commonly used
1218
1274
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1219
1275
 
1276
+ let lengthI32CacheUsed = false;
1277
+
1278
+ const protoOut = protoFunc(pointer, {
1279
+ getCachedI32: () => {
1280
+ lengthI32CacheUsed = true;
1281
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1282
+ },
1283
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1284
+ get: () => arrayUtil.getLength(pointer),
1285
+ getI32: () => arrayUtil.getLengthI32(pointer),
1286
+ set: value => arrayUtil.setLength(pointer, value),
1287
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1288
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1289
+ return makeArray(scope, {
1290
+ rawElements: new Array(length)
1291
+ }, _global, _name, true, itemType);
1292
+ }, varMetadata.get(baseName));
1293
+
1220
1294
  return [
1221
1295
  ...out,
1222
1296
 
1223
- ...arrayUtil.getLengthI32(pointer),
1224
- [ Opcodes.local_set, lengthLocal ],
1297
+ ...(!lengthI32CacheUsed ? [] : [
1298
+ ...arrayUtil.getLengthI32(pointer),
1299
+ [ Opcodes.local_set, lengthLocal ],
1300
+ ]),
1225
1301
 
1226
1302
  [ Opcodes.block, valtypeBinary ],
1227
- ...protoFunc(pointer, {
1228
- getCachedI32: () => [ [ Opcodes.local_get, lengthLocal ] ],
1229
- setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1230
- get: () => arrayUtil.getLength(pointer),
1231
- getI32: () => arrayUtil.getLengthI32(pointer),
1232
- set: value => arrayUtil.setLength(pointer, value),
1233
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1234
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1235
- return makeArray(scope, {
1236
- rawElements: new Array(length)
1237
- }, _global, _name, true, itemType);
1238
- }),
1303
+ ...protoOut,
1239
1304
  [ Opcodes.end ]
1240
1305
  ];
1241
1306
  }
@@ -1306,7 +1371,7 @@ const generateCall = (scope, decl, _global, _name) => {
1306
1371
  const generateNew = (scope, decl, _global, _name) => {
1307
1372
  // hack: basically treat this as a normal call for builtins for now
1308
1373
  const name = mapName(decl.callee.name);
1309
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1374
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1310
1375
  if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1311
1376
 
1312
1377
  return generateCall(scope, decl, _global, _name);
@@ -1324,12 +1389,12 @@ const unhackName = name => {
1324
1389
  };
1325
1390
 
1326
1391
  const generateVar = (scope, decl) => {
1327
- const out = [];
1392
+ let out = [];
1328
1393
 
1329
1394
  const topLevel = scope.name === 'main';
1330
1395
 
1331
1396
  // global variable if in top scope (main) and var ..., or if wanted
1332
- const global = decl.kind === 'var';
1397
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1333
1398
  const target = global ? globals : scope.locals;
1334
1399
 
1335
1400
  for (const x of decl.declarations) {
@@ -1366,7 +1431,7 @@ const generateVar = (scope, decl) => {
1366
1431
 
1367
1432
  // x.init ??= DEFAULT_VALUE;
1368
1433
  if (x.init) {
1369
- out.push(...generate(scope, x.init, global, name));
1434
+ out = out.concat(generate(scope, x.init, global, name));
1370
1435
 
1371
1436
  // if our value is the result of a function, infer the type from that func's return value
1372
1437
  if (out[out.length - 1][0] === Opcodes.call) {
@@ -1435,20 +1500,22 @@ const generateAssign = (scope, decl) => {
1435
1500
  ];
1436
1501
  }
1437
1502
 
1503
+ const op = decl.operator.slice(0, -1) || '=';
1504
+
1505
+ // arr[i] | str[i]
1438
1506
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1439
- // arr[i] | str[i]
1440
1507
  const name = decl.left.object.name;
1441
1508
  const pointer = arrays.get(name);
1442
1509
 
1443
1510
  const aotPointer = pointer != null;
1444
1511
 
1445
- const newValueTmp = localTmp(scope, '__member_setter_tmp');
1512
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1513
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1446
1514
 
1447
1515
  const parentType = getNodeType(scope, decl.left.object);
1448
1516
 
1449
- const op = decl.operator.slice(0, -1);
1450
1517
  return [
1451
- ...(aotPointer ? number(0, Valtype.i32) : [
1518
+ ...(aotPointer ? [] : [
1452
1519
  ...generate(scope, decl.left.object),
1453
1520
  Opcodes.i32_to_u
1454
1521
  ]),
@@ -1460,9 +1527,13 @@ const generateAssign = (scope, decl) => {
1460
1527
  Opcodes.i32_to_u,
1461
1528
  ...number(ValtypeSize[valtype], Valtype.i32),
1462
1529
  [ Opcodes.i32_mul ],
1463
- [ Opcodes.i32_add ],
1530
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1531
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1464
1532
 
1465
- ...(op === '' ? generate(scope, decl.right, false, name) : performOp(scope, op, generate(scope, decl.left), generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1533
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1534
+ [ Opcodes.local_get, pointerTmp ],
1535
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1536
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1466
1537
  [ Opcodes.local_tee, newValueTmp ],
1467
1538
 
1468
1539
  ...(parentType === TYPES._array ? [
@@ -1479,10 +1550,10 @@ const generateAssign = (scope, decl) => {
1479
1550
  const [ local, isGlobal ] = lookupName(scope, name);
1480
1551
 
1481
1552
  if (local === undefined) {
1482
- // todo: this should be a devtools/repl/??? only thing
1553
+ // todo: this should be a sloppy mode only thing
1483
1554
 
1484
1555
  // only allow = for this
1485
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1556
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1486
1557
 
1487
1558
  if (builtinVars[name]) {
1488
1559
  // just return rhs (eg `NaN = 2`)
@@ -1491,14 +1562,14 @@ const generateAssign = (scope, decl) => {
1491
1562
 
1492
1563
  // set global and return (eg a = 2)
1493
1564
  return [
1494
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1565
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1495
1566
  [ Opcodes.global_get, globals[name].idx ]
1496
1567
  ];
1497
1568
  }
1498
1569
 
1499
1570
  typeStates[name] = getNodeType(scope, decl.right);
1500
1571
 
1501
- if (decl.operator === '=') {
1572
+ if (op === '=') {
1502
1573
  // typeStates[name] = getNodeType(scope, decl.right);
1503
1574
 
1504
1575
  return [
@@ -1508,7 +1579,6 @@ const generateAssign = (scope, decl) => {
1508
1579
  ];
1509
1580
  }
1510
1581
 
1511
- const op = decl.operator.slice(0, -1);
1512
1582
  if (op === '||' || op === '&&' || op === '??') {
1513
1583
  // todo: is this needed?
1514
1584
  // for logical assignment ops, it is not left @= right ~= left = left @ right
@@ -1964,10 +2034,28 @@ const StoreOps = {
1964
2034
  i16: Opcodes.i32_store16
1965
2035
  };
1966
2036
 
2037
+ let data = [];
2038
+
2039
+ const compileBytes = (val, itemType, signed = true) => {
2040
+ switch (itemType) {
2041
+ case 'i8': return [ val % 256 ];
2042
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2043
+
2044
+ case 'i32':
2045
+ case 'i64':
2046
+ return enforceFourBytes(signedLEB128(val));
2047
+
2048
+ case 'f64': return ieee754_binary64(val);
2049
+ }
2050
+ };
2051
+
1967
2052
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1968
2053
  const out = [];
1969
2054
 
2055
+ let firstAssign = false;
1970
2056
  if (!arrays.has(name) || name === '$undeclared') {
2057
+ firstAssign = true;
2058
+
1971
2059
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1972
2060
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1973
2061
  arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
@@ -1978,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1978
2066
  const useRawElements = !!decl.rawElements;
1979
2067
  const elements = useRawElements ? decl.rawElements : decl.elements;
1980
2068
 
2069
+ const valtype = itemTypeToValtype[itemType];
1981
2070
  const length = elements.length;
1982
2071
 
2072
+ if (firstAssign && useRawElements) {
2073
+ let bytes = compileBytes(length, 'i32');
2074
+
2075
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2076
+ if (elements[i] == null) continue;
2077
+
2078
+ bytes.push(...compileBytes(elements[i], itemType));
2079
+ }
2080
+
2081
+ data.push({
2082
+ offset: pointer,
2083
+ bytes
2084
+ });
2085
+
2086
+ // local value as pointer
2087
+ out.push(...number(pointer));
2088
+
2089
+ return [ out, pointer ];
2090
+ }
2091
+
1983
2092
  // store length as 0th array
1984
2093
  out.push(
1985
2094
  ...number(0, Valtype.i32),
@@ -1988,7 +2097,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1988
2097
  );
1989
2098
 
1990
2099
  const storeOp = StoreOps[itemType];
1991
- const valtype = itemTypeToValtype[itemType];
1992
2100
 
1993
2101
  if (!initEmpty) for (let i = 0; i < length; i++) {
1994
2102
  if (elements[i] == null) continue;
@@ -2011,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
2011
2119
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2012
2120
  };
2013
2121
 
2122
+ let varMetadata = new Map();
2123
+ const addVarMeta = (_name, obj) => {
2124
+ const name = _name ?? '$undeclared';
2125
+ if (!varMetadata.has(name)) varMetadata.set(name, {});
2126
+
2127
+ const meta = varMetadata.get(name);
2128
+ for (const k in obj) {
2129
+ meta[k] = obj[k];
2130
+ }
2131
+ };
2132
+
2014
2133
  export const generateMember = (scope, decl, _global, _name) => {
2015
2134
  const type = getNodeType(scope, decl.object);
2016
2135
 
@@ -2351,6 +2470,18 @@ const internalConstrs = {
2351
2470
  ];
2352
2471
  },
2353
2472
  type: TYPES._array
2473
+ },
2474
+
2475
+ __Array_of: {
2476
+ // this is not a constructor but best fits internal structure here
2477
+ generate: (scope, decl, global, name) => {
2478
+ // Array.of(i0, i1, ...)
2479
+ return generateArray(scope, {
2480
+ elements: decl.arguments
2481
+ }, global, name);
2482
+ },
2483
+ type: TYPES._array,
2484
+ notConstr: true
2354
2485
  }
2355
2486
  };
2356
2487
 
@@ -2364,7 +2495,9 @@ export default program => {
2364
2495
  depth = [];
2365
2496
  typeStates = {};
2366
2497
  arrays = new Map();
2498
+ varMetadata = new Map();
2367
2499
  pages = new Map();
2500
+ data = [];
2368
2501
  currentFuncIndex = importedFuncs.length;
2369
2502
 
2370
2503
  globalThis.valtype = 'f64';
@@ -2441,5 +2574,5 @@ export default program => {
2441
2574
  // if blank main func and other exports, remove it
2442
2575
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2443
2576
 
2444
- return { funcs, globals, tags, exceptions, pages };
2577
+ return { funcs, globals, tags, exceptions, pages, data };
2445
2578
  };
@@ -9,11 +9,15 @@ export const number = (n, valtype = valtypeBinary) => {
9
9
  }
10
10
  };
11
11
 
12
- const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
12
+ export const enforceOneByte = arr => [ arr[0] ?? 0 ];
13
+ export const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0 ];
14
+ export const enforceFourBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
15
+ export const enforceEightBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0, arr[4] ?? 0, arr[5] ?? 0, arr[6] ?? 0, arr[7] ?? 0 ];
16
+
13
17
  export const i32x4 = (a, b, c, d) => [ [
14
18
  ...Opcodes.v128_const,
15
- ...enforceTwoBytes(signedLEB128(a)),
16
- ...enforceTwoBytes(signedLEB128(b)),
17
- ...enforceTwoBytes(signedLEB128(c)),
18
- ...enforceTwoBytes(signedLEB128(d))
19
+ ...enforceFourBytes(signedLEB128(a)),
20
+ ...enforceFourBytes(signedLEB128(b)),
21
+ ...enforceFourBytes(signedLEB128(c)),
22
+ ...enforceFourBytes(signedLEB128(d))
19
23
  ] ];
package/compiler/index.js CHANGED
@@ -59,7 +59,7 @@ export default (code, flags) => {
59
59
  if (flags.includes('info')) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
60
60
 
61
61
  const t1 = performance.now();
62
- const { funcs, globals, tags, exceptions, pages } = codeGen(program);
62
+ const { funcs, globals, tags, exceptions, pages, data } = codeGen(program);
63
63
  if (flags.includes('info')) console.log(`2. generated code in ${(performance.now() - t1).toFixed(2)}ms`);
64
64
 
65
65
  if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
@@ -71,14 +71,14 @@ export default (code, flags) => {
71
71
  if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
72
72
 
73
73
  const t3 = performance.now();
74
- const sections = produceSections(funcs, globals, tags, pages, flags);
74
+ const sections = produceSections(funcs, globals, tags, pages, data, flags);
75
75
  if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
76
76
 
77
77
  if (allocLog) {
78
78
  const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
79
79
  const bytes = wasmPages * 65536;
80
80
  log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
81
- // console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
81
+ console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
82
82
  }
83
83
 
84
84
  const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
package/compiler/opt.js CHANGED
@@ -317,28 +317,24 @@ export default (funcs, globals) => {
317
317
  continue;
318
318
  }
319
319
 
320
- if (i < 2) continue;
321
- const lastLastInst = wasm[i - 2];
320
+ // remove unneeded before get with update exprs (n++, etc) when value is unused
321
+ if (i < wasm.length - 4 && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get && wasm[i + 1][0] === Opcodes.const && [Opcodes.add, Opcodes.sub].includes(wasm[i + 2][0]) && wasm[i + 3][0] === Opcodes.local_set && wasm[i + 3][1] === inst[1] && (wasm[i + 4][0] === Opcodes.drop || wasm[i + 4][0] === Opcodes.br)) {
322
+ // local.get 1
323
+ // local.get 1
324
+ // -->
325
+ // local.get 1
322
326
 
323
- if (depth.length === 2) {
324
- // hack to remove unneeded before get in for loops with (...; i++)
325
- if (lastLastInst[0] === Opcodes.end && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get) {
326
- // local.get 1
327
- // local.get 1
328
- // -->
329
- // local.get 1
330
-
331
- // remove drop at the end as well
332
- if (wasm[i + 4][0] === Opcodes.drop) {
333
- wasm.splice(i + 4, 1);
334
- }
327
+ // remove drop at the end as well
328
+ if (wasm[i + 4][0] === Opcodes.drop) wasm.splice(i + 4, 1);
335
329
 
336
- wasm.splice(i, 1); // remove this inst (second get)
337
- i--;
338
- continue;
339
- }
330
+ wasm.splice(i, 1); // remove this inst (second get)
331
+ i--;
332
+ continue;
340
333
  }
341
334
 
335
+ if (i < 2) continue;
336
+ const lastLastInst = wasm[i - 2];
337
+
342
338
  if (lastLastInst[1] === inst[1] && inst[0] === Opcodes.local_get && lastInst[0] === Opcodes.local_tee && lastLastInst[0] === Opcodes.local_set) {
343
339
  // local.set x
344
340
  // local.tee y
@@ -23,7 +23,9 @@ const TYPES = {
23
23
 
24
24
  export const PrototypeFuncs = function() {
25
25
  const noUnlikelyChecks = process.argv.includes('-funsafe-no-unlikely-proto-checks');
26
- const noCCAChecks = process.argv.includes('-funsafe-no-charcodeat-checks');
26
+ let zeroChecks = process.argv.find(x => x.startsWith('-funsafe-zero-proto-checks='));
27
+ if (zeroChecks) zeroChecks = zeroChecks.split('=')[1].split(',').reduce((acc, x) => { acc[x.toLowerCase()] = true; return acc; }, {});
28
+ else zeroChecks = {};
27
29
 
28
30
  this[TYPES._array] = {
29
31
  // lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
@@ -203,7 +205,7 @@ export const PrototypeFuncs = function() {
203
205
  this[TYPES._array].fill.returnType = TYPES._array;
204
206
 
205
207
  this[TYPES.string] = {
206
- at: (pointer, length, wIndex, iTmp, arrayShell) => {
208
+ at: (pointer, length, wIndex, iTmp, _, arrayShell) => {
207
209
  const [ newOut, newPointer ] = arrayShell(1, 'i16');
208
210
 
209
211
  return [
@@ -258,7 +260,7 @@ export const PrototypeFuncs = function() {
258
260
  },
259
261
 
260
262
  // todo: out of bounds properly
261
- charAt: (pointer, length, wIndex, _, arrayShell) => {
263
+ charAt: (pointer, length, wIndex, _1, _2, arrayShell) => {
262
264
  const [ newOut, newPointer ] = arrayShell(1, 'i16');
263
265
 
264
266
  return [
@@ -290,7 +292,7 @@ export const PrototypeFuncs = function() {
290
292
  ...wIndex,
291
293
  Opcodes.i32_to,
292
294
 
293
- ...(noCCAChecks ? [] : [
295
+ ...(zeroChecks.charcodeat ? [] : [
294
296
  [ Opcodes.local_set, iTmp ],
295
297
 
296
298
  // index < 0
@@ -322,10 +324,87 @@ export const PrototypeFuncs = function() {
322
324
  Opcodes.i32_from_u
323
325
  ];
324
326
  },
327
+
328
+ isWellFormed: (pointer, length, wIndex, iTmp, iTmp2, arrayShell, { wellFormed } = {}) => {
329
+ // aot approx metadata
330
+ if (wellFormed != null) return number(wellFormed ? 1 : 0);
331
+
332
+ return [
333
+ // note: we cannot presume it begins as 0 in case it was used previously
334
+ ...number(0, Valtype.i32),
335
+ [ Opcodes.local_set, iTmp ],
336
+
337
+ [ Opcodes.loop, Blocktype.void ],
338
+
339
+ [ Opcodes.block, Blocktype.void ],
340
+
341
+ [ Opcodes.local_get, iTmp ],
342
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32) ],
343
+ [ Opcodes.local_set, iTmp2 ],
344
+
345
+ // if not surrogate, continue
346
+ [ Opcodes.local_get, iTmp2 ],
347
+ ...number(0xF800, Valtype.i32),
348
+ [ Opcodes.i32_and ],
349
+ ...number(0xD800, Valtype.i32),
350
+ [ Opcodes.i32_ne ],
351
+ [ Opcodes.br_if, 0 ],
352
+
353
+ // if not leading surrogate, return false
354
+ [ Opcodes.local_get, iTmp2 ],
355
+ ...number(0xDC00, Valtype.i32),
356
+ [ Opcodes.i32_ge_s ],
357
+ [ Opcodes.if, Blocktype.void ],
358
+ ...number(0),
359
+ [ Opcodes.br, 3 ],
360
+ [ Opcodes.end ],
361
+
362
+ // if not followed by trailing surrogate, return false
363
+ [ Opcodes.local_get, iTmp ],
364
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + ValtypeSize.i16) ],
365
+ ...number(0xFC00, Valtype.i32),
366
+ [ Opcodes.i32_and ],
367
+ ...number(0xDC00, Valtype.i32),
368
+ [ Opcodes.i32_ne ],
369
+ [ Opcodes.if, Blocktype.void ],
370
+ ...number(0),
371
+ [ Opcodes.br, 3 ],
372
+ [ Opcodes.end ],
373
+
374
+ // bump index again since gone through two valid chars
375
+ [ Opcodes.local_get, iTmp ],
376
+ ...number(ValtypeSize.i16, Valtype.i32),
377
+ [ Opcodes.i32_add ],
378
+ [ Opcodes.local_set, iTmp ],
379
+
380
+ [ Opcodes.end ],
381
+
382
+ // bump pointer and loop if not at the end
383
+ [ Opcodes.local_get, iTmp ],
384
+ ...number(ValtypeSize.i16, Valtype.i32),
385
+ [ Opcodes.i32_add ],
386
+ [ Opcodes.local_tee, iTmp ],
387
+
388
+ ...length.getCachedI32(),
389
+ ...number(ValtypeSize.i16, Valtype.i32),
390
+ [ Opcodes.i32_mul ],
391
+ [ Opcodes.i32_ne ],
392
+ [ Opcodes.br_if, 0 ],
393
+
394
+ [ Opcodes.end ],
395
+
396
+ // return true
397
+ ...number(1)
398
+ ]
399
+ }
325
400
  };
326
401
 
327
402
  this[TYPES.string].at.local = Valtype.i32;
328
403
  this[TYPES.string].at.returnType = TYPES.string;
329
404
  this[TYPES.string].charAt.returnType = TYPES.string;
330
405
  this[TYPES.string].charCodeAt.local = Valtype.i32;
406
+
407
+ this[TYPES.string].isWellFormed.local = Valtype.i32;
408
+ this[TYPES.string].isWellFormed.local2 = Valtype.i32;
409
+ this[TYPES.string].isWellFormed.returnType = TYPES.boolean;
331
410
  };