@markw65/monkeyc-optimizer 1.1.10 → 1.1.12

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/build/api.cjs CHANGED
@@ -619,7 +619,7 @@ class LocalState {
619
619
  this.unreachable = true;
620
620
  }
621
621
  }
622
- function buildReducedGraph(state, func, notice) {
622
+ function buildReducedGraph(state, func, refsForUpdate, notice) {
623
623
  const { stack, pre, post } = state;
624
624
  try {
625
625
  const localState = new LocalState(func.node);
@@ -627,8 +627,11 @@ function buildReducedGraph(state, func, notice) {
627
627
  state.stack = [...func.stack];
628
628
  const stmtStack = [func.node];
629
629
  const testStack = [{ node: func.node }];
630
+ const allEvents = [];
631
+ const eventsStack = [];
630
632
  let tryActive = 0;
631
633
  state.pre = (node) => {
634
+ eventsStack.push(allEvents.length);
632
635
  if (state.inType || localState.unreachable) {
633
636
  return [];
634
637
  }
@@ -954,6 +957,12 @@ function buildReducedGraph(state, func, notice) {
954
957
  }
955
958
  return [];
956
959
  case "AssignmentExpression":
960
+ if (refsForUpdate && node.operator !== "=") {
961
+ // if its an update, we need to see a "ref"
962
+ // of the lhs, then whatever happens on the rhs,
963
+ // and then the assignment itself
964
+ return null;
965
+ }
957
966
  if (node.left.type === "MemberExpression") {
958
967
  state.traverse(node.left.object);
959
968
  if (node.left.computed) {
@@ -983,6 +992,7 @@ function buildReducedGraph(state, func, notice) {
983
992
  return null;
984
993
  };
985
994
  const addEvent = (block, event) => {
995
+ allEvents.push(event);
986
996
  if (!block.events) {
987
997
  block.events = [event];
988
998
  }
@@ -991,11 +1001,13 @@ function buildReducedGraph(state, func, notice) {
991
1001
  }
992
1002
  };
993
1003
  state.post = (node) => {
1004
+ const eventIndex = eventsStack.pop();
1005
+ const getContainedEvents = () => allEvents.slice(eventIndex);
994
1006
  const curStmt = stmtStack[stmtStack.length - 1];
995
1007
  const topTest = testStack[testStack.length - 1];
996
1008
  if (!state.inType) {
997
1009
  const throws = tryActive > 0 && mayThrow(node);
998
- const events = notice(node, curStmt, throws);
1010
+ const events = notice(node, curStmt, throws, getContainedEvents);
999
1011
  if (throws) {
1000
1012
  if (!events) {
1001
1013
  throw new Error("mayThrow expression in try/catch must generate an event");
@@ -1042,7 +1054,7 @@ function buildReducedGraph(state, func, notice) {
1042
1054
  throw new Error("Internal error: Unexpected successor edges");
1043
1055
  }
1044
1056
  localState.addEdge(localState.curBlock, topTest.false);
1045
- const event = notice(node, curStmt, 1);
1057
+ const event = notice(node, curStmt, 1, getContainedEvents);
1046
1058
  if (event) {
1047
1059
  if (Array.isArray(event)) {
1048
1060
  throw new Error(`Unexpected array of flw events`);
@@ -1183,8 +1195,10 @@ function getPreOrder(head) {
1183
1195
  /* harmony import */ var _ast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6652);
1184
1196
  /* harmony import */ var _control_flow__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5101);
1185
1197
  /* harmony import */ var _function_info__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(819);
1186
- /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6906);
1187
- /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_util__WEBPACK_IMPORTED_MODULE_5__);
1198
+ /* harmony import */ var _type_flow_type_flow_util__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(1638);
1199
+ /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6906);
1200
+ /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_util__WEBPACK_IMPORTED_MODULE_6__);
1201
+
1188
1202
 
1189
1203
 
1190
1204
 
@@ -1349,7 +1363,7 @@ function buildDataFlowGraph(state, func, wantsLiteral, trackInsertionPoints, wan
1349
1363
  };
1350
1364
  return {
1351
1365
  identifiers,
1352
- graph: buildReducedGraph(state, func, (node, stmt, mayThrow) => {
1366
+ graph: buildReducedGraph(state, func, wantsAllRefs, (node, stmt, mayThrow, getContainedEvents) => {
1353
1367
  if (mayThrow === 1) {
1354
1368
  return wantsAllRefs ? getFlowEvent(node, stmt, findDecl) : null;
1355
1369
  }
@@ -1473,6 +1487,12 @@ function buildDataFlowGraph(state, func, wantsLiteral, trackInsertionPoints, wan
1473
1487
  if (rhs)
1474
1488
  def.rhs = rhs;
1475
1489
  }
1490
+ if (declIsLocal(decl)) {
1491
+ const contained = getContainedEvents().filter((e) => e.type === "ref" || e.type === "mod");
1492
+ if (contained.length) {
1493
+ def.containedEvents = contained;
1494
+ }
1495
+ }
1476
1496
  return def;
1477
1497
  }
1478
1498
  break;
@@ -1487,13 +1507,20 @@ function buildDataFlowGraph(state, func, wantsLiteral, trackInsertionPoints, wan
1487
1507
  decl,
1488
1508
  mayThrow,
1489
1509
  };
1490
- if (wantsAllRefs &&
1491
- node.operator === "=" &&
1492
- (node.right.type === "Identifier" ||
1493
- node.right.type === "MemberExpression")) {
1494
- const rhs = findDecl(node.right);
1495
- if (rhs)
1496
- def.rhs = rhs;
1510
+ if (wantsAllRefs) {
1511
+ if ((node.right.type === "Identifier" ||
1512
+ node.right.type === "MemberExpression") &&
1513
+ node.operator === "=") {
1514
+ const rhs = findDecl(node.right);
1515
+ if (rhs)
1516
+ def.rhs = rhs;
1517
+ }
1518
+ if (declIsLocal(decl)) {
1519
+ const contained = getContainedEvents().filter((e) => e.type === "ref" || e.type === "mod");
1520
+ if (contained.length) {
1521
+ def.containedEvents = contained;
1522
+ }
1523
+ }
1497
1524
  }
1498
1525
  return def;
1499
1526
  }
@@ -1546,6 +1573,12 @@ function buildDataFlowGraph(state, func, wantsLiteral, trackInsertionPoints, wan
1546
1573
  mod.calleeObj = calleeDecl.base;
1547
1574
  }
1548
1575
  }
1576
+ else if (node.callee.type === "MemberExpression") {
1577
+ const calleeObj = findDecl(node.callee.object);
1578
+ if (calleeObj) {
1579
+ mod.calleeObj = calleeObj;
1580
+ }
1581
+ }
1549
1582
  return mod;
1550
1583
  }
1551
1584
  }
@@ -1846,7 +1879,7 @@ function findCalleesByNode(state, callee) {
1846
1879
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
1847
1880
 
1848
1881
  "use strict";
1849
- /* unused harmony exports inlinableSubExpression, shouldInline, unused, inlineDiagnostic, inlineFunction, applyTypeIfNeeded */
1882
+ /* unused harmony exports inlinableSubExpression, inlineRequested, shouldInline, unused, inlineDiagnostic, inlineFunction, applyTypeIfNeeded */
1850
1883
  /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6817);
1851
1884
  /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_api__WEBPACK_IMPORTED_MODULE_0__);
1852
1885
  /* harmony import */ var _ast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6652);
@@ -2533,16 +2566,18 @@ function updateLocationForInline(node, original, context, func) {
2533
2566
  throw new Error("Internal error: Inlined call had no location info");
2534
2567
  }
2535
2568
  traverseAst(node, (node) => {
2536
- if (node.loc &&
2537
- (node.loc.source !== loc.source ||
2538
- node.loc.start.offset > loc.end.offset ||
2539
- node.loc.end.offset <= loc.start.offset)) {
2540
- if (!node.origins) {
2541
- node.origins = [];
2569
+ if (!node.loc ||
2570
+ node.loc.source !== loc.source ||
2571
+ node.loc.start.offset > loc.end.offset ||
2572
+ node.loc.end.offset <= loc.start.offset) {
2573
+ if (node.loc) {
2574
+ if (!node.origins) {
2575
+ node.origins = [];
2576
+ }
2577
+ node.origins.unshift({ loc: node.loc, func: func.fullName });
2542
2578
  }
2543
- node.origins.unshift({ loc: node.loc, func: func.fullName });
2579
+ withLoc(node, context, context === original ? context : false);
2544
2580
  }
2545
- withLoc(node, context, context === original ? context : false);
2546
2581
  });
2547
2582
  return node;
2548
2583
  }
@@ -2570,8 +2605,8 @@ function inlineFunction(state, func, call, context) {
2570
2605
  const ret = inlineFunctionHelper(state, func, call, context);
2571
2606
  if (!ret)
2572
2607
  return ret;
2573
- const typecheckFalse = func.node.attrs?.attributes?.elements.find((attr) => isTypecheckArg(attr, false));
2574
- if (!typecheckFalse) {
2608
+ const typecheckAttrs = func.node.attrs?.attributes?.elements.filter((attr) => isTypecheckArg(attr, null));
2609
+ if (!typecheckAttrs || !typecheckAttrs.length) {
2575
2610
  return ret;
2576
2611
  }
2577
2612
  const callerElem = state.stack.find((elem) => elem.sn.type === "FunctionDeclaration");
@@ -2580,6 +2615,9 @@ function inlineFunction(state, func, call, context) {
2580
2615
  }
2581
2616
  const callerSn = callerElem.sn;
2582
2617
  const caller = callerSn.node;
2618
+ if (caller.attrs?.attributes?.elements.find((attr) => isTypecheckArg(attr, null))) {
2619
+ return ret;
2620
+ }
2583
2621
  if (!caller.attrs) {
2584
2622
  caller.attrs = withLoc({
2585
2623
  type: "AttributeList",
@@ -2588,10 +2626,7 @@ function inlineFunction(state, func, call, context) {
2588
2626
  if (!caller.attrs.attributes) {
2589
2627
  caller.attrs.attributes = withLoc({ type: "Attributes", elements: [] }, caller.attrs, false);
2590
2628
  }
2591
- if (caller.attrs.attributes.elements.find((attr) => isTypecheckArg(attr, null))) {
2592
- return ret;
2593
- }
2594
- caller.attrs.attributes.elements.unshift(withLocDeep({ ...typecheckFalse }, caller.attrs, false));
2629
+ caller.attrs.attributes.elements.unshift(...typecheckAttrs.map((typecheck) => withLocDeep({ ...typecheck }, caller.attrs, false)));
2595
2630
  return ret;
2596
2631
  }
2597
2632
  function applyTypeIfNeeded(node) {
@@ -3177,6 +3212,8 @@ async function optimizeMonkeyC(fnMap, resourcesMap, manifestXML, config) {
3177
3212
  state.localsStack = [{}];
3178
3213
  state.calledFunctions = {};
3179
3214
  state.usedByName = {};
3215
+ const checkLookupRules = config.checkCompilerLookupRules;
3216
+ config.checkCompilerLookupRules = "OFF";
3180
3217
  let again = false;
3181
3218
  const optimizeCallHelper = (istate, call, node) => {
3182
3219
  const result = optimizeCall(istate, call, node);
@@ -3749,6 +3786,7 @@ async function optimizeMonkeyC(fnMap, resourcesMap, manifestXML, config) {
3749
3786
  return ret;
3750
3787
  });
3751
3788
  });
3789
+ config.checkCompilerLookupRules = checkLookupRules;
3752
3790
  reportMissingSymbols(state, config);
3753
3791
  if (state.inlineDiagnostics) {
3754
3792
  if (!state.diagnostics) {
@@ -3970,7 +4008,9 @@ function pragmaChecker(state, ast, diagnostics) {
3970
4008
  if (quote === '"') {
3971
4009
  return haystack.includes(needle);
3972
4010
  }
3973
- const re = new RegExp(needle.replace(/@([-\d.\w]+|"[^"]*")/g, (_match, pat) => `(?:${pat}|pre_${pat.replace(/\W/g, "_")}(?:_\\d+)?)`));
4011
+ const re = new RegExp(needle.replace(/@([-\d.\w]+|"[^"]*")/g, (_match, pat) => `(?:${pat}|pre_${pat
4012
+ .replace(/^([a-zA-Z_]+\.)*/, "")
4013
+ .replace(/\W/g, "_")}(?:_\\d+)?)`));
3974
4014
  return re.test(haystack);
3975
4015
  };
3976
4016
  next();
@@ -4167,7 +4207,9 @@ function sizeBasedPRE(state, func) {
4167
4207
  applyReplacements(func.node, nodeMap, declMap);
4168
4208
  func.node.body.body.unshift(variableDecl);
4169
4209
  }
4170
- minimizeLocals(state, func);
4210
+ if (state.config?.minimizeLocals ?? true) {
4211
+ minimizeLocals(state, func);
4212
+ }
4171
4213
  }
4172
4214
  function buildPREGraph(state, func) {
4173
4215
  const result = buildDataFlowGraph(state, func, (literal) => refCost(literal) > LocalRefCost, true, false);
@@ -4825,18 +4867,20 @@ function applyReplacements(func, nodeMap, declMap) {
4825
4867
  /* harmony import */ var _control_flow__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5101);
4826
4868
  /* harmony import */ var _data_flow__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8180);
4827
4869
  /* harmony import */ var _function_info__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(819);
4828
- /* harmony import */ var _optimizer_types__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(9697);
4829
- /* harmony import */ var _type_flow_could_be__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(4055);
4830
- /* harmony import */ var _type_flow_dead_store__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(8486);
4831
- /* harmony import */ var _type_flow_interp__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(7161);
4832
- /* harmony import */ var _type_flow_interp_call__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(2222);
4833
- /* harmony import */ var _type_flow_intersection_type__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(6973);
4834
- /* harmony import */ var _type_flow_sub_type__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(9234);
4835
- /* harmony import */ var _type_flow_type_flow_util__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(1638);
4836
- /* harmony import */ var _type_flow_types__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(7255);
4837
- /* harmony import */ var _type_flow_union_type__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(757);
4838
- /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(6906);
4839
- /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(_util__WEBPACK_IMPORTED_MODULE_16__);
4870
+ /* harmony import */ var _inliner__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(333);
4871
+ /* harmony import */ var _optimizer_types__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(9697);
4872
+ /* harmony import */ var _type_flow_could_be__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(4055);
4873
+ /* harmony import */ var _type_flow_dead_store__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(8486);
4874
+ /* harmony import */ var _type_flow_interp__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(7161);
4875
+ /* harmony import */ var _type_flow_interp_call__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(2222);
4876
+ /* harmony import */ var _type_flow_intersection_type__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(6973);
4877
+ /* harmony import */ var _type_flow_sub_type__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(9234);
4878
+ /* harmony import */ var _type_flow_type_flow_util__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(1638);
4879
+ /* harmony import */ var _type_flow_types__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(7255);
4880
+ /* harmony import */ var _type_flow_union_type__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(757);
4881
+ /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(6906);
4882
+ /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_17___default = /*#__PURE__*/__webpack_require__.n(_util__WEBPACK_IMPORTED_MODULE_17__);
4883
+
4840
4884
 
4841
4885
 
4842
4886
 
@@ -4875,15 +4919,24 @@ function buildTypeInfo(state, func, optimizeEquivalencies) {
4875
4919
  const logThisRun = logging && loggingEnabledFor("TYPEFLOW_FUNC", func);
4876
4920
  while (true) {
4877
4921
  const { graph } = buildDataFlowGraph(state, func, () => false, false, true);
4878
- if (optimizeEquivalencies &&
4879
- eliminateDeadStores(state, func, graph, logThisRun)) {
4880
- /*
4881
- * eliminateDeadStores can change the control flow graph,
4882
- * so we need to recompute it if it did anything.
4883
- */
4884
- continue;
4922
+ let copyPropStores;
4923
+ if (optimizeEquivalencies) {
4924
+ const result = eliminateDeadStores(state, func, graph, logThisRun);
4925
+ if (result.changes) {
4926
+ /*
4927
+ * eliminateDeadStores can change the control flow graph,
4928
+ * so we need to recompute it if it did anything.
4929
+ */
4930
+ continue;
4931
+ }
4932
+ if (result.copyPropStores.size) {
4933
+ copyPropStores = result.copyPropStores;
4934
+ }
4935
+ }
4936
+ const result = propagateTypes({ ...state, stack: func.stack }, func, graph, optimizeEquivalencies, copyPropStores, logThisRun);
4937
+ if (!result.redo) {
4938
+ return result.istate;
4885
4939
  }
4886
- return propagateTypes({ ...state, stack: func.stack }, func, graph, optimizeEquivalencies, logThisRun).istate;
4887
4940
  }
4888
4941
  }
4889
4942
  function buildConflictGraph(state, func) {
@@ -4891,8 +4944,8 @@ function buildConflictGraph(state, func) {
4891
4944
  return;
4892
4945
  const logThisRun = logging && loggingEnabledFor("CONFLICT_FUNC", func);
4893
4946
  const { graph, identifiers } = buildDataFlowGraph(state, func, () => false, false, true);
4894
- const { nodeEquivs } = propagateTypes({ ...state, stack: func.stack }, func, graph, false, false);
4895
- const { locals, localConflicts } = findDeadStores(func, graph, nodeEquivs, logThisRun);
4947
+ const { nodeEquivs } = propagateTypes({ ...state, stack: func.stack }, func, graph, false, undefined, logThisRun);
4948
+ const { locals, localConflicts } = findDeadStores(func, graph, nodeEquivs, false, logThisRun);
4896
4949
  return { graph, localConflicts, locals, identifiers, logThisRun };
4897
4950
  }
4898
4951
  function addEquiv(ts, key, equiv) {
@@ -4903,74 +4956,43 @@ function addEquiv(ts, key, equiv) {
4903
4956
  if (!keyVal || !equivVal)
4904
4957
  return false;
4905
4958
  if (equivVal.equivSet) {
4906
- if (keyVal.equivSet) {
4907
- // key is already a member of a set, see if
4908
- // equiv is part of it.
4909
- let s = keyVal.equivSet.next;
4910
- do {
4911
- if (s === equiv) {
4912
- // these two are already equivalent
4913
- return true;
4914
- }
4915
- const next = ts.get(s);
4916
- if (!next || !next.equivSet) {
4917
- throw new Error(`Inconsistent equivSet for ${tsKey(key)}: missing value for ${tsKey(s)}`);
4918
- }
4919
- s = next.equivSet.next;
4920
- } while (s !== key);
4959
+ if (equivVal.equivSet.has(key)) {
4960
+ return true;
4921
4961
  }
4922
4962
  // equiv is already a member of a set. remove it
4923
4963
  removeEquiv(ts, equiv);
4924
4964
  }
4925
4965
  // equiv is not (or no longer) part of an equivSet
4926
- keyVal = { ...keyVal };
4927
4966
  if (!keyVal.equivSet) {
4928
- keyVal.equivSet = { next: key };
4967
+ keyVal = { ...keyVal };
4968
+ keyVal.equivSet = new Set([key, equiv]);
4969
+ ts.set(key, keyVal);
4970
+ }
4971
+ else {
4972
+ keyVal.equivSet.add(equiv);
4929
4973
  }
4930
4974
  equivVal = { ...equivVal, equivSet: keyVal.equivSet };
4931
- keyVal.equivSet = { next: equiv };
4932
- ts.set(key, keyVal);
4933
4975
  ts.set(equiv, equivVal);
4934
4976
  return false;
4935
4977
  }
4936
4978
  function removeEquiv(ts, equiv) {
4937
- const equivVal = ts.get(equiv);
4979
+ let equivVal = ts.get(equiv);
4938
4980
  if (!equivVal?.equivSet)
4939
4981
  return;
4940
- let s = equivVal.equivSet.next;
4941
- do {
4942
- const next = ts.get(s);
4943
- if (!next || !next.equivSet) {
4944
- throw new Error(`Inconsistent equivSet for ${tsKey(equiv)}: missing value for ${tsKey(s)}`);
4945
- }
4946
- if (next.equivSet.next === equiv) {
4947
- const { equivSet: _e, ...rest } = next;
4948
- if (equivVal.equivSet.next === s) {
4949
- // this is a pair. just kill both
4950
- ts.set(s, rest);
4951
- }
4952
- else {
4953
- ts.set(s, { ...rest, equivSet: equivVal.equivSet });
4954
- }
4955
- break;
4956
- }
4957
- s = next.equivSet.next;
4958
- } while (true);
4959
- const newVal = { ...equivVal };
4960
- delete newVal.equivSet;
4961
- ts.set(equiv, newVal);
4982
+ equivVal.equivSet.delete(equiv);
4983
+ if (equivVal.equivSet.size === 1) {
4984
+ const other = Array.from(equivVal.equivSet)[0];
4985
+ const otherVal = { ...ts.get(other) };
4986
+ delete otherVal.equivSet;
4987
+ ts.set(other, otherVal);
4988
+ }
4989
+ equivVal = { ...equivVal };
4990
+ delete equivVal.equivSet;
4991
+ ts.set(equiv, equivVal);
4962
4992
  }
4963
4993
  function getEquivSet(ts, k) {
4964
- const keys = new Set();
4965
- let s = k;
4966
- do {
4967
- const next = ts.get(s);
4968
- if (!next || !next.equivSet) {
4969
- throw new Error(`Inconsistent equivSet for ${tsKey(k)}: missing value for ${tsKey(s)}`);
4970
- }
4971
- keys.add(s);
4972
- s = next.equivSet.next;
4973
- } while (s !== k);
4994
+ const keys = ts.get(k)?.equivSet;
4995
+ assert(keys);
4974
4996
  return keys;
4975
4997
  }
4976
4998
  function intersectEquiv(ts1, ts2, k) {
@@ -4982,21 +5004,26 @@ function intersectEquiv(ts1, ts2, k) {
4982
5004
  removeEquiv(ts1, k);
4983
5005
  return true;
4984
5006
  }
4985
- const keys = getEquivSet(ts2, k);
4986
- let ret = false;
4987
- let s = eq1.equivSet.next;
4988
- do {
4989
- const next = ts1.get(s);
4990
- if (!next || !next.equivSet) {
4991
- throw new Error(`Inconsistent equivSet for ${tsKey(k)}: missing value for ${tsKey(s)}`);
4992
- }
4993
- if (!keys.has(s)) {
4994
- ret = true;
4995
- removeEquiv(ts1, s);
5007
+ let removed = null;
5008
+ eq1.equivSet.forEach((key) => {
5009
+ if (!eq2.equivSet.has(key)) {
5010
+ eq1.equivSet.delete(key);
5011
+ if (!removed) {
5012
+ removed = new Set();
5013
+ }
5014
+ removed.add(key);
4996
5015
  }
4997
- s = next.equivSet.next;
4998
- } while (s !== k);
4999
- return ret;
5016
+ });
5017
+ if (eq1.equivSet.size === 1) {
5018
+ assert(eq1.equivSet.has(k));
5019
+ delete eq1.equivSet;
5020
+ }
5021
+ if (removed) {
5022
+ removed.forEach((k) => removed.size === 1
5023
+ ? delete ts1.get(k).equivSet
5024
+ : (ts1.get(k).equivSet = removed));
5025
+ }
5026
+ return false;
5000
5027
  }
5001
5028
  function clearAssocPaths(blockState, decl, v) {
5002
5029
  if (v.assocPaths?.size) {
@@ -5012,12 +5039,24 @@ function clearAssocPaths(blockState, decl, v) {
5012
5039
  }
5013
5040
  }
5014
5041
  function cloneTypeState(blockState) {
5015
- const { map, trackedMemberDecls, ...rest } = blockState;
5042
+ const { map, trackedMemberDecls, liveCopyPropEvents, ...rest } = blockState;
5016
5043
  const clone = { map: new Map(map), ...rest };
5044
+ clone.map.forEach((value, key) => {
5045
+ if (value.equivSet && key === Array.from(value.equivSet)[0]) {
5046
+ const equivSet = new Set(value.equivSet);
5047
+ equivSet.forEach((k) => clone.map.set(k, { ...clone.map.get(k), equivSet }));
5048
+ }
5049
+ });
5017
5050
  if (trackedMemberDecls) {
5018
5051
  clone.trackedMemberDecls = new Map();
5019
5052
  trackedMemberDecls.forEach((value, key) => {
5020
- clone.trackedMemberDecls.set(key, new Map(value));
5053
+ clone.trackedMemberDecls.set(key, new Set(value));
5054
+ });
5055
+ }
5056
+ if (liveCopyPropEvents) {
5057
+ clone.liveCopyPropEvents = new Map();
5058
+ liveCopyPropEvents.forEach((value, key) => {
5059
+ clone.liveCopyPropEvents?.set(key, new Set(value));
5021
5060
  });
5022
5061
  }
5023
5062
  return clone;
@@ -5029,18 +5068,84 @@ function addTrackedMemberDecl(blockState, key, assocKey) {
5029
5068
  assocKey.split(".").forEach((pathItem) => {
5030
5069
  const entries = blockState.trackedMemberDecls.get(pathItem);
5031
5070
  if (!entries) {
5032
- blockState.trackedMemberDecls.set(pathItem, new Map([[key, new Set([assocKey])]]));
5071
+ blockState.trackedMemberDecls.set(pathItem, new Set([key]));
5033
5072
  return;
5034
5073
  }
5035
- const entry = entries.get(key);
5036
- if (!entry) {
5037
- entries.set(key, new Set([assocKey]));
5038
- return;
5074
+ entries.add(key);
5075
+ });
5076
+ }
5077
+ function addCopyPropEvent(blockState, item) {
5078
+ if (!blockState.liveCopyPropEvents) {
5079
+ blockState.liveCopyPropEvents = new Map();
5080
+ }
5081
+ const liveCopyPropEvents = blockState.liveCopyPropEvents;
5082
+ const decl = item.event.decl;
5083
+ assert(declIsLocal(decl));
5084
+ let tov = blockState.map.get(decl);
5085
+ assert(tov);
5086
+ if (tov.copyPropItem !== item) {
5087
+ tov = { ...tov, copyPropItem: item };
5088
+ blockState.map.set(decl, tov);
5089
+ }
5090
+ item.contained.forEach((value, key) => {
5091
+ const decls = liveCopyPropEvents.get(key);
5092
+ if (!decls) {
5093
+ liveCopyPropEvents.set(key, new Set([decl]));
5094
+ }
5095
+ else {
5096
+ decls.add(decl);
5039
5097
  }
5040
- entry.add(assocKey);
5041
5098
  });
5042
5099
  }
5043
- function mergeTypeState(blockStates, index, from) {
5100
+ function clearCopyProp(blockState, item) {
5101
+ const liveCopyPropEvents = blockState.liveCopyPropEvents;
5102
+ assert(liveCopyPropEvents);
5103
+ const itemDecl = item.event.decl;
5104
+ item.contained.forEach((event, decl) => {
5105
+ const decls = liveCopyPropEvents.get(decl);
5106
+ assert(decls && decls.has(itemDecl));
5107
+ decls.delete(itemDecl);
5108
+ });
5109
+ }
5110
+ function copyPropFailed(blockState, item, nodeCopyProp) {
5111
+ clearCopyProp(blockState, item);
5112
+ const ref = nodeCopyProp.get(item.event.node);
5113
+ if (ref) {
5114
+ nodeCopyProp.delete(ref);
5115
+ }
5116
+ nodeCopyProp.set(item.event.node, false);
5117
+ }
5118
+ function clearRelatedCopyPropEvents(blockState, decl, nodeCopyProp) {
5119
+ blockState.liveCopyPropEvents
5120
+ ?.get(decl)
5121
+ ?.forEach((cpDecl) => {
5122
+ let value = blockState.map.get(cpDecl);
5123
+ assert(value && value.copyPropItem);
5124
+ assert(Array.from(value.copyPropItem.contained).some(([key]) => {
5125
+ return decl === key;
5126
+ }));
5127
+ copyPropFailed(blockState, value.copyPropItem, nodeCopyProp);
5128
+ value = { ...value };
5129
+ delete value.copyPropItem;
5130
+ blockState.map.set(cpDecl, value);
5131
+ });
5132
+ }
5133
+ function validateTypeState(curState) {
5134
+ curState.liveCopyPropEvents?.forEach((decls) => decls.forEach((cpDecl) => {
5135
+ const value = curState.map.get(cpDecl);
5136
+ assert(value && value.copyPropItem);
5137
+ }));
5138
+ curState.trackedMemberDecls?.forEach((affected, key) => {
5139
+ affected.forEach((decl) => {
5140
+ const value = curState.map.get(decl);
5141
+ assert(value && value.assocPaths);
5142
+ if (!Array.from(value.assocPaths).some((path) => `.${path}.`.includes(`.${key}.`))) {
5143
+ throw new Error("What");
5144
+ }
5145
+ });
5146
+ });
5147
+ }
5148
+ function mergeTypeState(blockStates, index, from, nodeCopyProp) {
5044
5149
  const to = blockStates[index];
5045
5150
  if (!to) {
5046
5151
  blockStates[index] = cloneTypeState(from);
@@ -5048,9 +5153,9 @@ function mergeTypeState(blockStates, index, from) {
5048
5153
  return true;
5049
5154
  }
5050
5155
  const widen = ++to.visits > 10;
5051
- // we'll rebuild this from scratch via
5052
- // addTrackedMemberDecl below.
5156
+ // we'll rebuild these from scratch
5053
5157
  delete to.trackedMemberDecls;
5158
+ delete to.liveCopyPropEvents;
5054
5159
  let changes = false;
5055
5160
  to.map.forEach((tov, k) => {
5056
5161
  const fromv = from.map.get(k);
@@ -5069,25 +5174,57 @@ function mergeTypeState(blockStates, index, from) {
5069
5174
  }
5070
5175
  }
5071
5176
  if (tov.assocPaths) {
5072
- const assocPaths = new Set(tov.assocPaths);
5073
5177
  tov = { ...tov };
5074
5178
  if (!fromv.assocPaths) {
5075
5179
  changes = true;
5076
5180
  delete tov.assocPaths;
5077
5181
  }
5078
5182
  else {
5183
+ const assocPaths = new Set(tov.assocPaths);
5079
5184
  assocPaths.forEach((key) => {
5080
5185
  if (!fromv.assocPaths.has(key)) {
5081
5186
  assocPaths.delete(key);
5187
+ changes = true;
5082
5188
  }
5083
5189
  else {
5084
5190
  addTrackedMemberDecl(to, k, key);
5085
5191
  }
5086
5192
  });
5087
- tov.assocPaths = assocPaths;
5193
+ if (assocPaths.size) {
5194
+ tov.assocPaths = assocPaths;
5195
+ }
5196
+ else {
5197
+ delete tov.assocPaths;
5198
+ }
5088
5199
  }
5089
5200
  to.map.set(k, tov);
5090
5201
  }
5202
+ // if both from and to have copyPropEvents, we can only
5203
+ // keep it if they're the same event.
5204
+ if (tov.copyPropItem) {
5205
+ if (tov.copyPropItem.event !== fromv.copyPropItem?.event) {
5206
+ const toProp = nodeCopyProp.get(tov.copyPropItem.event.node);
5207
+ if (toProp) {
5208
+ nodeCopyProp.delete(toProp);
5209
+ }
5210
+ nodeCopyProp.set(tov.copyPropItem.event.node, false);
5211
+ if (fromv.copyPropItem) {
5212
+ const fromProp = nodeCopyProp.get(fromv.copyPropItem.event.node);
5213
+ if (fromProp) {
5214
+ nodeCopyProp.delete(fromProp);
5215
+ }
5216
+ nodeCopyProp.set(fromv.copyPropItem.event.node, false);
5217
+ }
5218
+ tov = { ...tov };
5219
+ delete tov.copyPropItem;
5220
+ changes = true;
5221
+ to.map.set(k, tov);
5222
+ }
5223
+ else {
5224
+ assert(k === tov.copyPropItem.event.decl);
5225
+ addCopyPropEvent(to, tov.copyPropItem);
5226
+ }
5227
+ }
5091
5228
  if (widen) {
5092
5229
  if (subtypeOf(fromv.curType, tov.curType))
5093
5230
  return;
@@ -5110,19 +5247,6 @@ function mergeTypeState(blockStates, index, from) {
5110
5247
  });
5111
5248
  return changes;
5112
5249
  }
5113
- function tsEquivs(state, key) {
5114
- const result = [];
5115
- let s = key;
5116
- do {
5117
- result.push(tsKey(s));
5118
- const next = state.get(s);
5119
- if (!next || !next.equivSet) {
5120
- throw new Error(`Inconsistent equivSet for ${tsKey(key)}: missing value for ${tsKey(s)}`);
5121
- }
5122
- s = next.equivSet.next;
5123
- } while (s !== key);
5124
- return `[(${result.join(", ")})]`;
5125
- }
5126
5250
  function typeStateEntry(value, key) {
5127
5251
  return `${tsKey(key)} = ${display(value.curType)}`;
5128
5252
  }
@@ -5133,21 +5257,26 @@ function printBlockState(block, state, indent = "") {
5133
5257
  return;
5134
5258
  }
5135
5259
  state.map.forEach((value, key) => {
5136
- console.log(`${indent} - ${typeStateEntry(value, key)}${value.equivSet ? " " + tsEquivs(state.map, key) : ""}`);
5260
+ console.log(`${indent} - ${typeStateEntry(value, key)}${value.equivSet
5261
+ ? " " + `[(${Array.from(value.equivSet).map(tsKey).join(", ")})]`
5262
+ : ""}`);
5137
5263
  });
5138
5264
  }
5139
5265
  function updateAffected(blockState, objectType, baseDecl, assignedPath, affectedName, affected, assignedType) {
5140
- affected.forEach((paths, key) => {
5266
+ affected.forEach((key) => {
5267
+ let droppedComponents = null;
5141
5268
  const entry = blockState.map.get(key);
5142
- assert(entry);
5269
+ assert(entry && entry.assocPaths);
5143
5270
  let newEntry = entry;
5144
- paths.forEach((path) => {
5271
+ entry.assocPaths.forEach((path) => {
5145
5272
  if (key === baseDecl && path === assignedPath) {
5146
5273
  return;
5147
5274
  }
5148
- assert(entry.assocPaths?.has(path));
5149
- const assocPath = [];
5150
5275
  const pathSegments = path.split(".");
5276
+ if (!pathSegments.includes(affectedName)) {
5277
+ return;
5278
+ }
5279
+ const assocPath = [];
5151
5280
  let type = entry.curType;
5152
5281
  for (let i = 0; i < pathSegments.length; i++) {
5153
5282
  const pathItem = pathSegments[i];
@@ -5157,20 +5286,25 @@ function updateAffected(blockState, objectType, baseDecl, assignedPath, affected
5157
5286
  });
5158
5287
  if (pathItem === affectedName && couldBeShallow(type, objectType)) {
5159
5288
  const newAssocKey = assocPath.map((av) => av.name ?? "*").join(".");
5160
- const baseType = updateByAssocPath(assocPath, assignedType, true);
5161
5289
  if (newEntry === entry) {
5162
5290
  newEntry = { ...entry };
5163
5291
  }
5164
- newEntry.curType = baseType;
5165
- if (path !== newAssocKey) {
5166
- newEntry.assocPaths = new Set(entry.assocPaths);
5292
+ if (newAssocKey !== path) {
5293
+ newEntry.assocPaths = new Set(newEntry.assocPaths);
5167
5294
  newEntry.assocPaths.delete(path);
5168
- newEntry.assocPaths.add(newAssocKey);
5169
- const newPaths = new Set(paths);
5170
- newPaths.delete(path);
5171
- newPaths.add(newAssocKey);
5172
- affected.set(key, newPaths);
5295
+ // the "extra" path components will also have entries
5296
+ // in blockState.trackedMemberDecls. Since they're gone
5297
+ // from here, we (may) need to remove them from there
5298
+ if (!droppedComponents) {
5299
+ droppedComponents = new Set();
5300
+ }
5301
+ while (++i < pathSegments.length) {
5302
+ droppedComponents.add(pathSegments[i]);
5303
+ }
5304
+ break;
5173
5305
  }
5306
+ const baseType = updateByAssocPath(assocPath, assignedType, true);
5307
+ newEntry.curType = baseType;
5174
5308
  break;
5175
5309
  }
5176
5310
  if (pathItem === "*") {
@@ -5201,11 +5335,17 @@ function updateAffected(blockState, objectType, baseDecl, assignedPath, affected
5201
5335
  }
5202
5336
  });
5203
5337
  if (newEntry !== entry) {
5338
+ if (droppedComponents) {
5339
+ newEntry.assocPaths.forEach((path) => path
5340
+ .split(".")
5341
+ .forEach((pathComponent) => droppedComponents.delete(pathComponent)));
5342
+ droppedComponents.forEach((pathComponent) => blockState.trackedMemberDecls.get(pathComponent).delete(key));
5343
+ }
5204
5344
  blockState.map.set(key, newEntry);
5205
5345
  }
5206
5346
  });
5207
5347
  }
5208
- function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5348
+ function propagateTypes(state, func, graph, optimizeEquivalencies, copyPropStores, logThisRun) {
5209
5349
  // We want to traverse the blocks in reverse post order, in
5210
5350
  // order to propagate the "availability" of the types.
5211
5351
  const order = getPostOrder(graph).reverse();
@@ -5227,7 +5367,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5227
5367
  printBlockTrailer(block);
5228
5368
  });
5229
5369
  }
5230
- function memberDeclInfo(blockState, decl, clearEquiv, newValue) {
5370
+ function memberDeclInfo(blockState, decl, newValue) {
5231
5371
  const baseType = getStateType(blockState, decl.base);
5232
5372
  const typePath = [baseType];
5233
5373
  let next = null;
@@ -5307,13 +5447,11 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5307
5447
  }));
5308
5448
  const assocKey = assocValue.map((av) => av.name ?? "*").join(".");
5309
5449
  const newType = updateByAssocPath(assocValue, next, false);
5310
- setStateEvent(blockState, decl.base, newType, false);
5311
- // setStateEvent guarantees that tsv is "unshared" at this
5312
- // point. So we can munge it directly.
5313
- const tsv = blockState.map.get(decl.base);
5314
- if (!tsv.assocPaths)
5315
- tsv.assocPaths = new Set();
5450
+ setStateEvent(blockState, decl.base, newType, newValue ? 1 /* UpdateKind.Inner */ : 0 /* UpdateKind.None */);
5451
+ const tsv = { ...blockState.map.get(decl.base) };
5452
+ tsv.assocPaths = new Set(tsv.assocPaths);
5316
5453
  tsv.assocPaths.add(assocKey);
5454
+ blockState.map.set(decl.base, tsv);
5317
5455
  addTrackedMemberDecl(blockState, decl.base, assocKey);
5318
5456
  if (newValue) {
5319
5457
  const baseElem = assocValue[decl.path.length - 1];
@@ -5341,7 +5479,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5341
5479
  const doUpdate = (key, cur) => {
5342
5480
  const update = cloneType(cur.curType);
5343
5481
  unionInto(update, next);
5344
- setStateEvent(blockState, key, update, false);
5482
+ setStateEvent(blockState, key, update, 0 /* UpdateKind.None */);
5345
5483
  };
5346
5484
  if (decls.length === 1) {
5347
5485
  const cur = blockState.map.get(decls[0]);
@@ -5375,44 +5513,57 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5375
5513
  return cur;
5376
5514
  }, { type: 0 /* TypeTag.Never */ });
5377
5515
  }
5378
- function setStateEvent(blockState, decl, value, clearEquiv) {
5516
+ function setStateEvent(blockState, decl, value, updateKind) {
5379
5517
  if (Array.isArray(decl) ||
5380
5518
  (decl.type !== "MemberDecl" && decl.type !== "Unknown")) {
5381
- const v = { ...blockState.map.get(decl) };
5382
- if (!clearEquiv) {
5519
+ if (updateKind !== 0 /* UpdateKind.None */) {
5520
+ // even if we're only modifying an Object, rather
5521
+ // than reassigning it, we need to clear the
5522
+ // related copy prop events, because although
5523
+ // we may only see the object itself, the expression
5524
+ // could access its fields. eg if x is the decl, then
5525
+ //
5526
+ // foo(x);
5527
+ //
5528
+ // might change when x.a.b.c is changed, even if we know
5529
+ // that foo is side-effect free, and accesses no globals.
5530
+ clearRelatedCopyPropEvents(blockState, decl, nodeCopyProp);
5531
+ }
5532
+ if (updateKind !== 2 /* UpdateKind.Reassign */) {
5383
5533
  /*
5384
- * If we're not clearing the equivalencies then this update
5385
- * must be applied to every element of the set
5534
+ * If we're not re-assigning, the equivalencies don't
5535
+ * change, so this update must be applied to every
5536
+ * element of the set
5386
5537
  */
5387
- if (v.equivSet) {
5388
- let s = decl;
5389
- do {
5538
+ const v = blockState.map.get(decl);
5539
+ if (v?.equivSet) {
5540
+ v.equivSet.forEach((s) => {
5390
5541
  const next = blockState.map.get(s);
5391
- if (!next || !next.equivSet) {
5392
- throw new Error(`Inconsistent equivSet for ${tsKey(decl)}: missing value for ${tsKey(s)}`);
5393
- }
5542
+ assert(next && next.equivSet?.has(s));
5394
5543
  blockState.map.set(s, { ...next, curType: value });
5395
- s = next.equivSet.next;
5396
- } while (s !== decl);
5397
- return;
5544
+ });
5545
+ }
5546
+ else {
5547
+ blockState.map.set(decl, { ...v, curType: value });
5398
5548
  }
5399
5549
  }
5400
5550
  else {
5551
+ const v = blockState.map.get(decl);
5401
5552
  removeEquiv(blockState.map, decl);
5402
- delete v.equivSet;
5403
- if (v.assocPaths?.size) {
5553
+ if (v?.assocPaths?.size) {
5404
5554
  clearAssocPaths(blockState, decl, v);
5405
- delete v.assocPaths;
5406
5555
  }
5556
+ if (v?.copyPropItem) {
5557
+ copyPropFailed(blockState, v.copyPropItem, nodeCopyProp);
5558
+ }
5559
+ blockState.map.set(decl, { curType: value });
5407
5560
  }
5408
- v.curType = value;
5409
- blockState.map.set(decl, v);
5410
5561
  return;
5411
5562
  }
5412
5563
  if (decl.type === "Unknown") {
5413
5564
  return;
5414
5565
  }
5415
- return memberDeclInfo(blockState, decl, clearEquiv, value)?.[1];
5566
+ return memberDeclInfo(blockState, decl, value)?.[1];
5416
5567
  }
5417
5568
  function getStateType(blockState, decl) {
5418
5569
  return getStateEntry(blockState, decl).curType;
@@ -5430,7 +5581,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5430
5581
  if (decl.type === "Unknown") {
5431
5582
  return { curType: { type: 0 /* TypeTag.Never */ } };
5432
5583
  }
5433
- const info = memberDeclInfo(blockState, decl, false);
5584
+ const info = memberDeclInfo(blockState, decl);
5434
5585
  return { curType: info ? info[0] : { type: 524287 /* TypeTag.Any */ } };
5435
5586
  }
5436
5587
  const blockStates = [];
@@ -5451,23 +5602,58 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5451
5602
  if (succ.order == null) {
5452
5603
  throw new Error("Unreachable block was visited");
5453
5604
  }
5454
- if (mergeTypeState(blockStates, succ.order, curState)) {
5605
+ if (mergeTypeState(blockStates, succ.order, curState, nodeCopyProp)) {
5606
+ if (logThisRun) {
5607
+ console.log(`re-merge: ${top.order} -> ${succ.order}`);
5608
+ }
5455
5609
  queue.enqueue(succ);
5456
5610
  }
5457
5611
  });
5458
5612
  };
5459
- function handleMod(curState, calleeObjDecl, callees, node) {
5613
+ function checkModResults(curState, calleeObjDecl, callees, node) {
5460
5614
  const calleeObj = getStateType(curState, calleeObjDecl);
5461
- return every(callees, (callee) => {
5462
- const info = sysCallInfo(callee);
5615
+ const calleeResult = { type: 0 /* TypeTag.Never */ };
5616
+ const result = every(callees, (callee) => {
5617
+ const info = sysCallInfo(istate.state, callee);
5463
5618
  if (!info)
5464
5619
  return false;
5465
5620
  const result = info(istate.state, callee, calleeObj, () => node.arguments.map((arg) => evaluateExpr(state, arg, typeMap).value));
5621
+ if (!result.effectFree) {
5622
+ return false;
5623
+ }
5466
5624
  if (result.calleeObj) {
5467
- setStateEvent(curState, calleeObjDecl, result.calleeObj, false);
5625
+ unionInto(calleeResult, result.calleeObj);
5468
5626
  }
5469
5627
  return true;
5470
5628
  });
5629
+ return result ? calleeResult : null;
5630
+ }
5631
+ function modInterference(blockState, event, doUpdate, callback) {
5632
+ let callees = undefined;
5633
+ if (event.calleeDecl) {
5634
+ const calleeType = getStateType(blockState, event.calleeDecl);
5635
+ if (hasValue(calleeType) && calleeType.type === 8192 /* TypeTag.Function */) {
5636
+ callees = calleeType.value;
5637
+ }
5638
+ else {
5639
+ callees = findCalleesByNode(state, event.node);
5640
+ }
5641
+ }
5642
+ if (callees === undefined && event.callees !== undefined) {
5643
+ callees = event.callees;
5644
+ }
5645
+ if (event.calleeObj) {
5646
+ const calleeObjResult = checkModResults(blockState, event.calleeObj, callees, event.node);
5647
+ if (calleeObjResult) {
5648
+ if (calleeObjResult.type !== 0 /* TypeTag.Never */) {
5649
+ if (doUpdate) {
5650
+ setStateEvent(blockState, event.calleeObj, calleeObjResult, 0 /* UpdateKind.None */);
5651
+ }
5652
+ }
5653
+ return true;
5654
+ }
5655
+ }
5656
+ return callback(callees, event.calleeObj);
5471
5657
  }
5472
5658
  function handleFlowEvent(event, top, curState) {
5473
5659
  const fixTypes = (equal, left, right, leftDecl, rightDecl) => {
@@ -5487,7 +5673,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5487
5673
  }
5488
5674
  }
5489
5675
  const tmpState = cloneTypeState(curState);
5490
- setStateEvent(tmpState, leftDecl, leftr, false);
5676
+ setStateEvent(tmpState, leftDecl, leftr, 0 /* UpdateKind.None */);
5491
5677
  if (rightDecl) {
5492
5678
  let rightr = restrictByEquality(left, right);
5493
5679
  if (rightr.type === 0 /* TypeTag.Never */) {
@@ -5499,7 +5685,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5499
5685
  return false;
5500
5686
  }
5501
5687
  }
5502
- setStateEvent(tmpState, rightDecl, rightr, false);
5688
+ setStateEvent(tmpState, rightDecl, rightr, 0 /* UpdateKind.None */);
5503
5689
  }
5504
5690
  return tmpState;
5505
5691
  }
@@ -5513,7 +5699,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5513
5699
  if (singletonRemoved.type === 0 /* TypeTag.Never */)
5514
5700
  return false;
5515
5701
  const tmpState = cloneTypeState(curState);
5516
- setStateEvent(tmpState, leftDecl, singletonRemoved, false);
5702
+ setStateEvent(tmpState, leftDecl, singletonRemoved, 0 /* UpdateKind.None */);
5517
5703
  return tmpState;
5518
5704
  }
5519
5705
  return null;
@@ -5545,7 +5731,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5545
5731
  if (singletonRemoved.type === 0 /* TypeTag.Never */)
5546
5732
  return false;
5547
5733
  const tmpState = cloneTypeState(curState);
5548
- setStateEvent(tmpState, event.left, singletonRemoved, false);
5734
+ setStateEvent(tmpState, event.left, singletonRemoved, 0 /* UpdateKind.None */);
5549
5735
  return tmpState;
5550
5736
  }
5551
5737
  }
@@ -5560,7 +5746,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5560
5746
  if (nonNullRemoved.type === 0 /* TypeTag.Never */)
5561
5747
  return false;
5562
5748
  const tmpState = cloneTypeState(curState);
5563
- setStateEvent(tmpState, event.left, nonNullRemoved, false);
5749
+ setStateEvent(tmpState, event.left, nonNullRemoved, 0 /* UpdateKind.None */);
5564
5750
  return tmpState;
5565
5751
  }
5566
5752
  break;
@@ -5618,7 +5804,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5618
5804
  }
5619
5805
  if (result) {
5620
5806
  const tmpState = cloneTypeState(curState);
5621
- setStateEvent(tmpState, event.left, result, false);
5807
+ setStateEvent(tmpState, event.left, result, 0 /* UpdateKind.None */);
5622
5808
  return tmpState;
5623
5809
  }
5624
5810
  }
@@ -5645,7 +5831,10 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5645
5831
  console.log(` Flow (true): merge to ${trueSucc.order || -1}`);
5646
5832
  printBlockState(top, sTrue || curState, " >true ");
5647
5833
  }
5648
- if (mergeTypeState(blockStates, trueSucc.order, sTrue || curState)) {
5834
+ if (mergeTypeState(blockStates, trueSucc.order, sTrue || curState, nodeCopyProp)) {
5835
+ if (logThisRun) {
5836
+ console.log(`re-merge: ${top.order} -> ${trueSucc.order}`);
5837
+ }
5649
5838
  queue.enqueue(trueSucc);
5650
5839
  }
5651
5840
  }
@@ -5657,22 +5846,33 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5657
5846
  console.log(` Flow (false): merge to: ${falseSucc.order || -1}`);
5658
5847
  printBlockState(top, sFalse || curState, " >false ");
5659
5848
  }
5660
- if (mergeTypeState(blockStates, falseSucc.order, sFalse || curState)) {
5849
+ if (mergeTypeState(blockStates, falseSucc.order, sFalse || curState, nodeCopyProp)) {
5850
+ if (logThisRun) {
5851
+ console.log(`re-merge: ${top.order} -> ${falseSucc.order}`);
5852
+ }
5661
5853
  queue.enqueue(falseSucc);
5662
5854
  }
5663
5855
  }
5664
5856
  return true;
5665
5857
  }
5858
+ /*
5859
+ * nodeCopyProp contains two related maps. It maps ref nodes
5860
+ * to the def node that should be copy propagated. It also maps
5861
+ * def nodes to false, to indicate a previous failure to find
5862
+ * a copy prop candidate.
5863
+ */
5864
+ const nodeCopyProp = new Map();
5666
5865
  const nodeEquivs = new Map();
5667
5866
  const localDecls = new Map();
5668
5867
  const localConflicts = new Set();
5669
5868
  const selfAssignments = new Set();
5670
5869
  const processEvent = (top, curState, event, skipMerge) => {
5671
5870
  if (!skipMerge && event.mayThrow && top.exsucc) {
5672
- if (mergeTypeState(blockStates, top.exsucc.order, curState)) {
5871
+ if (mergeTypeState(blockStates, top.exsucc.order, curState, nodeCopyProp)) {
5673
5872
  queue.enqueue(top.exsucc);
5674
5873
  }
5675
5874
  }
5875
+ validateTypeState(curState);
5676
5876
  switch (event.type) {
5677
5877
  case "kil": {
5678
5878
  const curEntry = getStateEntry(curState, event.decl);
@@ -5682,11 +5882,15 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5682
5882
  if (curEntry.assocPaths) {
5683
5883
  clearAssocPaths(curState, event.decl, curEntry);
5684
5884
  }
5885
+ if (curEntry.copyPropItem) {
5886
+ copyPropFailed(curState, curEntry.copyPropItem, nodeCopyProp);
5887
+ }
5888
+ clearRelatedCopyPropEvents(curState, event.decl, nodeCopyProp);
5685
5889
  curState.map.delete(event.decl);
5686
5890
  break;
5687
5891
  }
5688
5892
  case "ref": {
5689
- const curEntry = getStateEntry(curState, event.decl);
5893
+ let curEntry = getStateEntry(curState, event.decl);
5690
5894
  typeMap.set(event.node, curEntry.curType);
5691
5895
  nodeEquivs.delete(event.node);
5692
5896
  if (curEntry.equivSet) {
@@ -5698,6 +5902,26 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5698
5902
  });
5699
5903
  }
5700
5904
  }
5905
+ if (copyPropStores) {
5906
+ nodeCopyProp.delete(event.node);
5907
+ if (curEntry.copyPropItem) {
5908
+ const copyPropInfo = copyPropStores.get(curEntry.copyPropItem.event.node);
5909
+ assert(copyPropInfo && copyPropInfo.ref === event.node);
5910
+ const defNode = nodeCopyProp.get(curEntry.copyPropItem.event.node);
5911
+ assert(!defNode || defNode === event.node);
5912
+ if (defNode !== false) {
5913
+ nodeCopyProp.set(event.node, curEntry.copyPropItem.event.node);
5914
+ nodeCopyProp.set(curEntry.copyPropItem.event.node, event.node);
5915
+ }
5916
+ clearCopyProp(curState, curEntry.copyPropItem);
5917
+ curEntry = { ...curEntry };
5918
+ delete curEntry.copyPropItem;
5919
+ curState.map.set(event.decl, curEntry);
5920
+ }
5921
+ else if (declIsNonLocal(event.decl)) {
5922
+ clearRelatedCopyPropEvents(curState, null, nodeCopyProp);
5923
+ }
5924
+ }
5701
5925
  if (logThisRun) {
5702
5926
  console.log(` ${describeEvent(event)} == ${display(curEntry.curType)}`);
5703
5927
  }
@@ -5707,53 +5931,76 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5707
5931
  if (logThisRun) {
5708
5932
  console.log(` ${describeEvent(event)}`);
5709
5933
  }
5710
- let callees = undefined;
5711
- if (event.calleeDecl) {
5712
- const calleeType = getStateType(curState, event.calleeDecl);
5713
- if (hasValue(calleeType) && calleeType.type === 8192 /* TypeTag.Function */) {
5714
- callees = calleeType.value;
5715
- }
5716
- else {
5717
- callees = findCalleesByNode(state, event.node);
5718
- }
5719
- }
5720
- if (callees === undefined && event.callees !== undefined) {
5721
- callees = event.callees;
5722
- }
5723
- if (event.calleeObj) {
5724
- if (handleMod(curState, event.calleeObj, callees, event.node)) {
5725
- break;
5934
+ modInterference(curState, event, true, (callees, calleeObj) => {
5935
+ clearRelatedCopyPropEvents(curState, null, nodeCopyProp);
5936
+ if (calleeObj) {
5937
+ const objType = getStateType(curState, calleeObj);
5938
+ if (objType.type &
5939
+ (32768 /* TypeTag.Object */ | 512 /* TypeTag.Array */ | 1024 /* TypeTag.Dictionary */)) {
5940
+ setStateEvent(curState, calleeObj, objType, 1 /* UpdateKind.Inner */);
5941
+ }
5726
5942
  }
5727
- }
5728
- curState.map.forEach((tsv, decl) => {
5729
- let type = tsv.curType;
5730
- if (!some(decl, (d) => d.type === "VariableDeclarator" &&
5731
- (d.node.kind === "var" ||
5732
- // even a "const" could have its "inner" type altered
5733
- (type.value != null && (type.type & 32768 /* TypeTag.Object */) !== 0)))) {
5734
- return;
5943
+ if (nodeCopyProp.size &&
5944
+ event.node.type === "CallExpression" &&
5945
+ some(callees, (callee) => inlineRequested(state, callee))) {
5946
+ // we don't want to copy-prop to the argument of
5947
+ // an inline function, because that could prevent
5948
+ // inlining.
5949
+ event.node.arguments.forEach((arg) => {
5950
+ const def = nodeCopyProp.get(arg);
5951
+ if (def && nodeCopyProp.get(def) !== false) {
5952
+ nodeCopyProp.set(def, false);
5953
+ nodeCopyProp.delete(arg);
5954
+ }
5955
+ });
5735
5956
  }
5736
- if (modifiableDecl(decl, callees)) {
5737
- if (tsv.equivSet) {
5738
- removeEquiv(curState.map, decl);
5957
+ let calleeEffects;
5958
+ curState.map.forEach((tsv, decl) => {
5959
+ let type = tsv.curType;
5960
+ if ((type.value == null ||
5961
+ !(type.type &
5962
+ (32768 /* TypeTag.Object */ | 512 /* TypeTag.Array */ | 1024 /* TypeTag.Dictionary */))) &&
5963
+ !some(decl, (d) => d.type === "VariableDeclarator" && !isLocal(d))) {
5964
+ return;
5739
5965
  }
5740
- if (tsv.assocPaths) {
5741
- clearAssocPaths(curState, decl, tsv);
5966
+ if (modifiableDecl(decl, callees)) {
5967
+ if (tsv.equivSet) {
5968
+ removeEquiv(curState.map, decl);
5969
+ }
5970
+ if (tsv.assocPaths) {
5971
+ clearAssocPaths(curState, decl, tsv);
5972
+ }
5973
+ // we only attach copyPropItems to locals,
5974
+ // which can't be modified by a call.
5975
+ assert(!tsv.copyPropItem);
5976
+ clearRelatedCopyPropEvents(curState, decl, nodeCopyProp);
5977
+ curState.map.set(decl, { curType: typeConstraint(decl) });
5742
5978
  }
5743
- curState.map.set(decl, { curType: typeConstraint(decl) });
5744
- }
5745
- else if (type.value != null &&
5746
- (!callees || !every(callees, (callee) => callee.info === false))) {
5747
- if (type.type & 32768 /* TypeTag.Object */) {
5748
- const odata = getObjectValue(tsv.curType);
5749
- if (odata?.obj) {
5750
- type = cloneType(type);
5751
- const newData = { klass: odata.klass };
5752
- setUnionComponent(type, 32768 /* TypeTag.Object */, newData);
5753
- curState.map.set(decl, { ...tsv, curType: type });
5979
+ else if (type.type &
5980
+ (32768 /* TypeTag.Object */ | 512 /* TypeTag.Array */ | 1024 /* TypeTag.Dictionary */) &&
5981
+ (calleeEffects == null
5982
+ ? (calleeEffects =
5983
+ !callees ||
5984
+ !every(callees, (callee) => callee.info === false))
5985
+ : calleeEffects)) {
5986
+ if (type.value != null && type.type & 32768 /* TypeTag.Object */) {
5987
+ const odata = getObjectValue(tsv.curType);
5988
+ if (odata?.obj) {
5989
+ type = cloneType(type);
5990
+ const newData = { klass: odata.klass };
5991
+ setUnionComponent(type, 32768 /* TypeTag.Object */, newData);
5992
+ if (tsv.assocPaths) {
5993
+ clearAssocPaths(curState, decl, tsv);
5994
+ tsv = { ...tsv };
5995
+ delete tsv.assocPaths;
5996
+ }
5997
+ curState.map.set(decl, { ...tsv, curType: type });
5998
+ }
5754
5999
  }
6000
+ clearRelatedCopyPropEvents(curState, decl, nodeCopyProp);
5755
6001
  }
5756
- }
6002
+ });
6003
+ return true;
5757
6004
  });
5758
6005
  break;
5759
6006
  }
@@ -5764,9 +6011,18 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5764
6011
  ? event.node.left
5765
6012
  : null;
5766
6013
  if (lval) {
5767
- const beforeType = getStateType(curState, event.decl);
5768
- if (beforeType) {
5769
- typeMap.set(lval, beforeType);
6014
+ const before = getStateEntry(curState, event.decl);
6015
+ if (before.curType) {
6016
+ typeMap.set(lval, before.curType);
6017
+ }
6018
+ if (before.copyPropItem &&
6019
+ (event.node.type !== "AssignmentExpression" ||
6020
+ event.node.operator !== "=")) {
6021
+ copyPropFailed(curState, before.copyPropItem, nodeCopyProp);
6022
+ const v = { ...before };
6023
+ delete v.copyPropItem;
6024
+ assert(isTypeStateKey(event.decl));
6025
+ curState.map.set(event.decl, v);
5770
6026
  }
5771
6027
  }
5772
6028
  const expr = event.node.type === "VariableDeclarator"
@@ -5775,7 +6031,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5775
6031
  const type = expr
5776
6032
  ? evaluate(istate, expr).value
5777
6033
  : { type: 524287 /* TypeTag.Any */ };
5778
- const wasComputedDecl = setStateEvent(curState, event.decl, type, true);
6034
+ const wasComputedDecl = setStateEvent(curState, event.decl, type, 2 /* UpdateKind.Reassign */);
5779
6035
  some(event.decl, (decl) => {
5780
6036
  if (decl.type !== "VariableDeclarator" ||
5781
6037
  decl.node.kind !== "var" ||
@@ -5806,13 +6062,16 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5806
6062
  if (value.assocPaths) {
5807
6063
  clearAssocPaths(curState, decls, value);
5808
6064
  }
6065
+ assert(!value.copyPropItem);
5809
6066
  curState.map.set(decls, { curType: typeConstraint(decls) });
6067
+ clearRelatedCopyPropEvents(curState, decls, nodeCopyProp);
5810
6068
  }
5811
6069
  });
5812
6070
  }
5813
6071
  if (event.rhs) {
5814
6072
  const selfAssign = addEquiv(curState.map, event.rhs, event.decl);
5815
- if (event.node.type === "AssignmentExpression") {
6073
+ if (event.node.type === "AssignmentExpression" &&
6074
+ event.node.operator === "=") {
5816
6075
  if (selfAssign) {
5817
6076
  // rhs and lhs are identical
5818
6077
  selfAssignments.add(event.node);
@@ -5822,7 +6081,91 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5822
6081
  }
5823
6082
  }
5824
6083
  }
5825
- if (declIsLocal(event.decl)) {
6084
+ if (!declIsLocal(event.decl)) {
6085
+ clearRelatedCopyPropEvents(curState, null, nodeCopyProp);
6086
+ }
6087
+ else {
6088
+ if (event.containedEvents &&
6089
+ copyPropStores &&
6090
+ nodeCopyProp.get(event.node) !== false &&
6091
+ (!event.rhs || !declIsLocal(event.rhs))) {
6092
+ const copyPropCandidate = copyPropStores.get(event.node);
6093
+ if (copyPropCandidate) {
6094
+ const contained = new Map();
6095
+ if (event.containedEvents.every((event) => {
6096
+ if (event.type === "mod") {
6097
+ if (modInterference(curState, event, false, () => false)) {
6098
+ return true;
6099
+ }
6100
+ if (!copyPropCandidate.ant ||
6101
+ // If the ref isn't anticipated, we can't propagate it
6102
+ // in case it has side effects.
6103
+ some(event.calleeDecl, (callee) => callee.type === "FunctionDeclaration" &&
6104
+ inlineRequested(state, callee))) {
6105
+ // Don't copy prop if the rhs is marked for
6106
+ // inline, because we might move it out of
6107
+ // assignment context, to somewhere it can't be
6108
+ // inlined.
6109
+ return false;
6110
+ }
6111
+ }
6112
+ if (!event.decl ||
6113
+ (isTypeStateKey(event.decl) &&
6114
+ some(event.decl, (decl) => (decl.type === "VariableDeclarator" &&
6115
+ decl.node.kind === "var") ||
6116
+ decl.type === "BinaryExpression" ||
6117
+ decl.type === "Identifier"))) {
6118
+ const key = event.decl ?? null;
6119
+ if (key && declIsLocal(key)) {
6120
+ if (nodeCopyProp.has(event.node)) {
6121
+ // we might have
6122
+ //
6123
+ // var x = foo();
6124
+ // var y = x + 1;
6125
+ // bar();
6126
+ // return y;
6127
+ //
6128
+ // In that case, its ok to drop "x = foo()" and rewrite as y = foo() + 1"
6129
+ // OR its ok to drop "y = x + 1" and rewrite as "return x + 1".
6130
+ // But we can't do both, and rewrite as "bar(); return foo() + 1;"
6131
+ // So just disable copy prop for *this* node. We'll re-run and have a
6132
+ // second chance later.
6133
+ return false;
6134
+ }
6135
+ else if (event.node.type === "AssignmentExpression" &&
6136
+ event.node.operator !== "=" &&
6137
+ nodeCopyProp.has(event.node.left)) {
6138
+ // If we're copy proping into the lhs of an update
6139
+ // assignment, we're going to have to rewrite it.
6140
+ // similar to the above, don't also do forward copy
6141
+ // prop. eg
6142
+ //
6143
+ // var x = a + b;
6144
+ // x += c;
6145
+ // return x;
6146
+ //
6147
+ // becomes
6148
+ //
6149
+ // var x;
6150
+ // x = (a + b) + c;
6151
+ // return x; // <- dont propagate to here (yet), in case a+b has changed.
6152
+ return false;
6153
+ }
6154
+ }
6155
+ const item = contained.get(key);
6156
+ if (!item) {
6157
+ contained.set(key, [event]);
6158
+ }
6159
+ else {
6160
+ item.push(event);
6161
+ }
6162
+ }
6163
+ return true;
6164
+ })) {
6165
+ addCopyPropEvent(curState, { event, contained });
6166
+ }
6167
+ }
6168
+ }
5826
6169
  const name = localDeclName(event.decl);
5827
6170
  const locals = localDecls.get(name);
5828
6171
  if (!locals) {
@@ -5870,7 +6213,7 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5870
6213
  func.node.params.forEach((param) => {
5871
6214
  setStateEvent(head, param, param.type === "BinaryExpression"
5872
6215
  ? typeFromTypespec(state, param.right)
5873
- : { type: 524287 /* TypeTag.Any */ }, false);
6216
+ : { type: 524287 /* TypeTag.Any */ }, 0 /* UpdateKind.None */);
5874
6217
  });
5875
6218
  queue.enqueue(order[0]);
5876
6219
  while (!queue.empty()) {
@@ -5904,6 +6247,16 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5904
6247
  printBlockTrailer(top);
5905
6248
  }
5906
6249
  }
6250
+ nodeCopyProp.forEach((value, key) => {
6251
+ if (key.type === "VariableDeclarator" ||
6252
+ key.type === "AssignmentExpression") {
6253
+ if (value === false) {
6254
+ nodeCopyProp.delete(key);
6255
+ return;
6256
+ }
6257
+ assert(nodeCopyProp.get(value) === key);
6258
+ }
6259
+ });
5907
6260
  if (logThisRun) {
5908
6261
  order.forEach((block) => {
5909
6262
  printBlockHeader(block);
@@ -5921,9 +6274,29 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5921
6274
  nodeEquivs.forEach((value, key) => {
5922
6275
  console.log(`${formatAst(key)} = [${value.equiv.map((equiv) => tsKey(equiv))}] ${key.loc && key.loc.source ? ` (${sourceLocation(key.loc)})` : ""}`);
5923
6276
  });
6277
+ console.log("====== Copy Prop =====");
6278
+ nodeCopyProp.forEach((value, key) => {
6279
+ assert(value !== false);
6280
+ if (key.type === "VariableDeclarator" ||
6281
+ key.type === "AssignmentExpression") {
6282
+ return;
6283
+ }
6284
+ assert((value.type === "VariableDeclarator" && value.init) ||
6285
+ value.type === "AssignmentExpression");
6286
+ const node = value.type === "VariableDeclarator" ? value.init : value.right;
6287
+ console.log(`${formatAst(key)} = [${formatAstLongLines(node)}] ${key.loc && key.loc.source ? ` (${sourceLocation(key.loc)})` : ""}`);
6288
+ });
6289
+ }
6290
+ if (logThisRun) {
6291
+ console.log(formatAstLongLines(func.node));
6292
+ if (copyPropStores) {
6293
+ copyPropStores.forEach(({ ref, ant }, node) => {
6294
+ console.log(`copy-prop-store: ${formatAstLongLines(node)}${ant ? "!" : ""} => ${nodeCopyProp.get(node) !== ref ? "Failed" : "Success"}`);
6295
+ });
6296
+ }
5924
6297
  }
5925
6298
  if (optimizeEquivalencies) {
5926
- if (!nodeEquivs.size && !selfAssignments.size) {
6299
+ if (!nodeEquivs.size && !selfAssignments.size && !nodeCopyProp.size) {
5927
6300
  return { istate, nodeEquivs };
5928
6301
  }
5929
6302
  if (logThisRun) {
@@ -5966,13 +6339,84 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
5966
6339
  }
5967
6340
  }
5968
6341
  }));
5969
- traverseAst(func.node.body, null, (node) => {
6342
+ traverseAst(func.node.body, (node) => {
6343
+ if (node.type === "AssignmentExpression" &&
6344
+ node.operator !== "=" &&
6345
+ nodeCopyProp.has(node.left)) {
6346
+ const left = cloneDeep(node.left);
6347
+ const right = withLoc({
6348
+ type: "BinaryExpression",
6349
+ operator: node.operator.slice(0, -1),
6350
+ left: withLocDeep(node.left, node.right, false, true),
6351
+ right: node.right,
6352
+ }, node.left, node.right);
6353
+ node.operator = "=";
6354
+ node.left = left;
6355
+ node.right = right;
6356
+ }
6357
+ }, (node) => {
6358
+ const copyNode = nodeCopyProp.get(node);
6359
+ if (copyNode) {
6360
+ if (node.type === "AssignmentExpression") {
6361
+ if (logThisRun) {
6362
+ console.log(`Killing copy-prop assignment ${formatAstLongLines(node)}`);
6363
+ }
6364
+ return withLoc({ type: "Literal", value: null, raw: "null" }, node, node);
6365
+ }
6366
+ if (node.type === "VariableDeclarator") {
6367
+ assert(node.init);
6368
+ if (logThisRun) {
6369
+ console.log(`Killing copy-prop variable initialization ${formatAstLongLines(node)}`);
6370
+ }
6371
+ const dup = { ...node };
6372
+ delete dup.init;
6373
+ return dup;
6374
+ }
6375
+ if (copyNode.type === "AssignmentExpression") {
6376
+ const replacement = copyNode.operator === "="
6377
+ ? copyNode.right
6378
+ : {
6379
+ type: "BinaryExpression",
6380
+ operator: copyNode.operator.slice(0, -1),
6381
+ left: copyNode.left,
6382
+ right: copyNode.right,
6383
+ };
6384
+ if (logThisRun) {
6385
+ console.log(`copy-prop ${formatAstLongLines(node)} => ${formatAstLongLines(replacement)}`);
6386
+ }
6387
+ return withLocDeep(replacement, node, node, false);
6388
+ }
6389
+ else if (copyNode.type === "VariableDeclarator") {
6390
+ assert(copyNode.init);
6391
+ if (logThisRun) {
6392
+ console.log(`copy-prop ${formatAstLongLines(node)} => ${formatAstLongLines(copyNode.init)}`);
6393
+ }
6394
+ return withLocDeep(copyNode.init, node, node, false);
6395
+ }
6396
+ assert(false);
6397
+ }
5970
6398
  if (selfAssignments.has(node)) {
5971
6399
  if (logThisRun) {
5972
6400
  console.log(`Deleting self assignment: ${formatAst(node)} (${sourceLocation(node.loc)})`);
5973
6401
  }
5974
6402
  return withLoc({ type: "Literal", value: null, raw: "null" }, node, node);
5975
6403
  }
6404
+ if (nodeCopyProp.size) {
6405
+ /*
6406
+ * Copy prop and equiv can interfere with each other:
6407
+ *
6408
+ * var c = g; // copy prop kills this
6409
+ * ...
6410
+ * var x = g + 2; // node equiv replaces g with c
6411
+ * ...
6412
+ * return c; // copy prop changes this to g
6413
+ *
6414
+ * So ignore equivalencies if copy prop is active.
6415
+ * Note that we have to re-run propagation anyway
6416
+ * if copy prop did anything.
6417
+ */
6418
+ return null;
6419
+ }
5976
6420
  const equiv = nodeEquivs.get(node);
5977
6421
  if (!equiv || localConflicts.has(equiv.decl))
5978
6422
  return null;
@@ -6024,7 +6468,11 @@ function propagateTypes(state, func, graph, optimizeEquivalencies, logThisRun) {
6024
6468
  return replacement;
6025
6469
  });
6026
6470
  }
6027
- return { istate, nodeEquivs };
6471
+ return {
6472
+ istate,
6473
+ nodeEquivs,
6474
+ redo: optimizeEquivalencies && nodeCopyProp.size,
6475
+ };
6028
6476
  }
6029
6477
  function updateByAssocPath(path, property, union) {
6030
6478
  const valueToStore = (base) => {
@@ -6261,20 +6709,48 @@ function couldBeObj(a, b) {
6261
6709
 
6262
6710
  "use strict";
6263
6711
  /* unused harmony exports findDeadStores, eliminateDeadStores */
6264
- /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6817);
6265
- /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_api__WEBPACK_IMPORTED_MODULE_0__);
6266
- /* harmony import */ var _ast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6652);
6267
- /* harmony import */ var _control_flow__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5101);
6268
- /* harmony import */ var _data_flow__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8180);
6269
- /* harmony import */ var _inliner__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(333);
6270
- /* harmony import */ var _type_flow_util__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(1638);
6712
+ /* harmony import */ var node_assert__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4143);
6713
+ /* harmony import */ var node_assert__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_assert__WEBPACK_IMPORTED_MODULE_0__);
6714
+ /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6817);
6715
+ /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_api__WEBPACK_IMPORTED_MODULE_1__);
6716
+ /* harmony import */ var _ast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6652);
6717
+ /* harmony import */ var _control_flow__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5101);
6718
+ /* harmony import */ var _data_flow__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8180);
6719
+ /* harmony import */ var _inliner__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(333);
6720
+ /* harmony import */ var _type_flow_util__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(1638);
6721
+
6271
6722
 
6272
6723
 
6273
6724
 
6274
6725
 
6275
6726
 
6276
6727
 
6277
- function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6728
+ function cloneAnt(antMap) {
6729
+ return new Map(antMap);
6730
+ }
6731
+ function addAnt(antMap, decl, node) {
6732
+ assert(node.type === "Identifier");
6733
+ const ant = antMap.get(decl);
6734
+ if (ant === false || ant === node)
6735
+ return;
6736
+ if (ant === undefined) {
6737
+ antMap.set(decl, node);
6738
+ }
6739
+ else {
6740
+ antMap.set(decl, false);
6741
+ }
6742
+ }
6743
+ function cloneState(blockState) {
6744
+ const clone = { dead: new Set(blockState.dead) };
6745
+ if (blockState.anticipated) {
6746
+ clone.anticipated = cloneAnt(blockState.anticipated);
6747
+ }
6748
+ if (blockState.partiallyAnticipated) {
6749
+ clone.partiallyAnticipated = cloneAnt(blockState.partiallyAnticipated);
6750
+ }
6751
+ return clone;
6752
+ }
6753
+ function findDeadStores(func, graph, nodeEquivs, findCopyPropCandidates, logThisRun) {
6278
6754
  const order = getPostOrder(graph);
6279
6755
  order.forEach((block, i) => {
6280
6756
  block.order = i;
@@ -6282,13 +6758,52 @@ function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6282
6758
  const blockStates = [];
6283
6759
  const nodeConflicts = nodeEquivs && new Map();
6284
6760
  const mergeStates = (to, from) => {
6285
- return Array.from(to).reduce((changed, decl) => {
6286
- if (!from.has(decl)) {
6287
- to.delete(decl);
6761
+ let changed = Array.from(to.dead).reduce((changed, decl) => {
6762
+ if (!from.dead.has(decl)) {
6763
+ to.dead.delete(decl);
6288
6764
  changed = true;
6289
6765
  }
6290
6766
  return changed;
6291
6767
  }, false);
6768
+ if (to.anticipated) {
6769
+ if (!from.anticipated) {
6770
+ delete to.anticipated;
6771
+ changed = true;
6772
+ }
6773
+ else {
6774
+ changed = Array.from(to.anticipated).reduce((changed, [decl, toant]) => {
6775
+ const fromant = from.anticipated.get(decl);
6776
+ if (toant !== fromant) {
6777
+ to.anticipated.delete(decl);
6778
+ changed = true;
6779
+ }
6780
+ return changed;
6781
+ }, changed);
6782
+ }
6783
+ }
6784
+ if (from.partiallyAnticipated) {
6785
+ if (!to.partiallyAnticipated) {
6786
+ to.partiallyAnticipated = cloneAnt(from.partiallyAnticipated);
6787
+ changed = true;
6788
+ }
6789
+ else {
6790
+ changed = Array.from(from.partiallyAnticipated).reduce((changed, [decl, fromant]) => {
6791
+ const toant = to.partiallyAnticipated.get(decl);
6792
+ if (toant === undefined) {
6793
+ to.partiallyAnticipated.set(decl, fromant);
6794
+ changed = true;
6795
+ }
6796
+ else {
6797
+ if (toant !== fromant) {
6798
+ changed = true;
6799
+ to.partiallyAnticipated.set(decl, false);
6800
+ }
6801
+ }
6802
+ return changed;
6803
+ }, changed);
6804
+ }
6805
+ }
6806
+ return changed;
6292
6807
  };
6293
6808
  const queue = new DataflowQueue();
6294
6809
  const locals = new Set(order
@@ -6299,10 +6814,11 @@ function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6299
6814
  : [])
6300
6815
  .concat(func.node.params));
6301
6816
  const deadStores = new Set();
6817
+ const copyPropStores = new Map();
6302
6818
  order.forEach((block) => {
6303
6819
  if (!block.succs) {
6304
6820
  queue.enqueue(block);
6305
- blockStates[block.order] = new Set(locals);
6821
+ blockStates[block.order] = { dead: new Set(locals) };
6306
6822
  }
6307
6823
  });
6308
6824
  while (!queue.empty()) {
@@ -6313,10 +6829,10 @@ function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6313
6829
  if (!blockStates[top.order]) {
6314
6830
  throw new Error(`Block ${top.order || 0} had no state!`);
6315
6831
  }
6316
- const curState = new Set(blockStates[top.order]);
6832
+ const curState = cloneState(blockStates[top.order]);
6317
6833
  if (logThisRun) {
6318
6834
  printBlockHeader(top);
6319
- curState.forEach((decl) => console.log(` - anticipated: ${tsKey(decl)}`));
6835
+ curState.dead.forEach((decl) => console.log(` - anticipated: ${tsKey(decl)}`));
6320
6836
  }
6321
6837
  if (top.events) {
6322
6838
  for (let i = top.events.length; i--;) {
@@ -6331,33 +6847,63 @@ function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6331
6847
  switch (event.type) {
6332
6848
  case "ref":
6333
6849
  if (isTypeStateKey(event.decl)) {
6334
- curState.delete(event.decl);
6335
6850
  if (logThisRun) {
6851
+ console.log(describeEvent(event));
6336
6852
  console.log(` kill => ${tsKey(event.decl)}`);
6337
6853
  }
6854
+ if (findCopyPropCandidates && declIsLocal(event.decl)) {
6855
+ if (!curState.anticipated) {
6856
+ curState.anticipated = new Map();
6857
+ }
6858
+ addAnt(curState.anticipated, event.decl, event.node);
6859
+ if (!curState.partiallyAnticipated) {
6860
+ curState.partiallyAnticipated = new Map();
6861
+ }
6862
+ addAnt(curState.partiallyAnticipated, event.decl, event.node);
6863
+ if (logThisRun) {
6864
+ console.log(` antrefs: ${curState.partiallyAnticipated.get(event.decl) !== false} ${curState.anticipated.get(event.decl) !== false}`);
6865
+ }
6866
+ }
6867
+ curState.dead.delete(event.decl);
6338
6868
  }
6339
6869
  break;
6340
6870
  case "def":
6341
6871
  if (isTypeStateKey(event.decl) &&
6342
6872
  (event.node.type !== "VariableDeclarator" || event.node.init)) {
6343
- if (curState.has(event.decl)) {
6873
+ if (logThisRun) {
6874
+ console.log(describeEvent(event));
6875
+ }
6876
+ const assignNode = (event.node.type === "AssignmentExpression" &&
6877
+ event.node.operator === "=" &&
6878
+ event.node.right) ||
6879
+ (event.node.type === "VariableDeclarator" && event.node.init);
6880
+ if (curState.dead.has(event.decl)) {
6344
6881
  deadStores.add(event.node);
6345
6882
  }
6346
6883
  else {
6347
6884
  deadStores.delete(event.node);
6885
+ copyPropStores.delete(event.node);
6886
+ if (declIsLocal(event.decl) && curState.partiallyAnticipated) {
6887
+ const pant = curState.partiallyAnticipated.get(event.decl);
6888
+ if (pant) {
6889
+ if (logThisRun) {
6890
+ console.log(` is copy-prop-candidate ${curState.anticipated?.get(event.decl) === pant}`);
6891
+ }
6892
+ copyPropStores.set(event.node, {
6893
+ ref: pant,
6894
+ ant: curState.anticipated?.get(event.decl) === pant,
6895
+ });
6896
+ }
6897
+ curState.partiallyAnticipated.delete(event.decl);
6898
+ curState.anticipated?.delete(event.decl);
6899
+ }
6348
6900
  }
6349
6901
  if (nodeConflicts) {
6350
6902
  const conflicts = new Set(locals);
6351
- curState.forEach((dead) => conflicts.delete(dead));
6903
+ curState.dead.forEach((dead) => conflicts.delete(dead));
6352
6904
  if (event.rhs) {
6353
6905
  conflicts.delete(event.rhs);
6354
- const equiv = event.node.type === "AssignmentExpression" &&
6355
- event.node.operator === "="
6356
- ? nodeEquivs.get(event.node.right)
6357
- : event.node.type === "VariableDeclarator" &&
6358
- event.node.init
6359
- ? nodeEquivs.get(event.node.init)
6360
- : null;
6906
+ const equiv = assignNode && nodeEquivs.get(assignNode);
6361
6907
  if (equiv) {
6362
6908
  equiv.equiv.forEach((e) => isTypeStateKey(e) && conflicts.delete(e));
6363
6909
  isTypeStateKey(equiv.decl) && conflicts.delete(equiv.decl);
@@ -6366,10 +6912,8 @@ function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6366
6912
  conflicts.add(event.decl);
6367
6913
  nodeConflicts.set(event.node, conflicts);
6368
6914
  }
6369
- if ((event.node.type === "AssignmentExpression" &&
6370
- event.node.operator === "=") ||
6371
- (event.node.type === "VariableDeclarator" && event.node.init)) {
6372
- curState.add(event.decl);
6915
+ if (assignNode) {
6916
+ curState.dead.add(event.decl);
6373
6917
  if (logThisRun) {
6374
6918
  console.log(` anticipated => ${tsKey(event.decl)}`);
6375
6919
  }
@@ -6378,14 +6922,14 @@ function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6378
6922
  break;
6379
6923
  case "kil":
6380
6924
  if (isTypeStateKey(event.decl)) {
6381
- curState.add(event.decl);
6925
+ curState.dead.add(event.decl);
6382
6926
  if (logThisRun) {
6383
6927
  console.log(` anticipated => ${tsKey(event.decl)}`);
6384
6928
  }
6385
6929
  }
6386
6930
  break;
6387
6931
  case "mod":
6388
- curState.forEach((decl) => declIsLocal(decl) || curState.delete(decl));
6932
+ curState.dead.forEach((decl) => declIsLocal(decl) || curState.dead.delete(decl));
6389
6933
  break;
6390
6934
  case "flw":
6391
6935
  break;
@@ -6395,7 +6939,7 @@ function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6395
6939
  const doMerge = (pred) => {
6396
6940
  const pi = pred.order || 0;
6397
6941
  if (!blockStates[pi]) {
6398
- blockStates[pi] = new Set(curState);
6942
+ blockStates[pi] = cloneState(curState);
6399
6943
  queue.enqueue(pred);
6400
6944
  }
6401
6945
  else if (mergeStates(blockStates[pi], curState)) {
@@ -6427,15 +6971,18 @@ function findDeadStores(func, graph, nodeEquivs, logThisRun) {
6427
6971
  nodeConflicts.forEach((conflicts) => conflicts.forEach((conflict) => addConflicts(conflict, conflicts)));
6428
6972
  func.node.params.forEach((param, index, arr) => addConflicts(param, arr));
6429
6973
  }
6430
- return { deadStores, locals, localConflicts };
6974
+ return { deadStores, locals, localConflicts, copyPropStores };
6431
6975
  }
6432
6976
  function eliminateDeadStores(state, func, graph, logThisRun) {
6433
- const { deadStores } = findDeadStores(func, graph, null, logThisRun);
6977
+ const { deadStores, copyPropStores } = findDeadStores(func, graph, null, state.config?.singleUseCopyProp ?? true, logThisRun);
6434
6978
  if (!deadStores.size)
6435
- return false;
6979
+ return { changes: false, copyPropStores };
6436
6980
  if (logThisRun) {
6437
6981
  console.log("====== Dead Stores =====");
6438
- deadStores.forEach((dead) => console.log(`${formatAst(dead)} (${sourceLocation(dead.loc)})`));
6982
+ deadStores.forEach((dead) => (dead.type === "AssignmentExpression" ||
6983
+ dead.type === "UpdateExpression" ||
6984
+ dead.type === "VariableDeclarator") &&
6985
+ console.log(`${formatAst(dead)} (${sourceLocation(dead.loc)})`));
6439
6986
  }
6440
6987
  let changes = false;
6441
6988
  traverseAst(func.node.body, null, (node, parent) => {
@@ -6498,7 +7045,7 @@ function eliminateDeadStores(state, func, graph, logThisRun) {
6498
7045
  }
6499
7046
  return null;
6500
7047
  });
6501
- return changes;
7048
+ return { copyPropStores, changes };
6502
7049
  }
6503
7050
 
6504
7051
 
@@ -6509,9 +7056,13 @@ function eliminateDeadStores(state, func, graph, logThisRun) {
6509
7056
 
6510
7057
  "use strict";
6511
7058
  /* unused harmony exports evaluateBinaryTypes, evaluateLogicalTypes */
6512
- /* harmony import */ var _interp__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7161);
6513
- /* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7255);
6514
- /* harmony import */ var _union_type__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(757);
7059
+ /* harmony import */ var _could_be__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4055);
7060
+ /* harmony import */ var _interp__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7161);
7061
+ /* harmony import */ var _sub_type__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9234);
7062
+ /* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7255);
7063
+ /* harmony import */ var _union_type__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(757);
7064
+
7065
+
6515
7066
 
6516
7067
 
6517
7068
 
@@ -6842,14 +7393,6 @@ function evaluateBinaryTypes(op, left, right) {
6842
7393
  : 2 /* TypeTag.False */,
6843
7394
  }),
6844
7395
  },
6845
- instanceof: {
6846
- allowed: 524287 /* TypeTag.Any */,
6847
- typeFn: () => ({
6848
- tag: 6 /* TypeTag.Boolean */,
6849
- castArgs: false,
6850
- }),
6851
- valueFn: (_left, _right) => undefined,
6852
- },
6853
7396
  has: {
6854
7397
  allowed: 524287 /* TypeTag.Any */,
6855
7398
  typeFn: () => ({
@@ -6860,6 +7403,21 @@ function evaluateBinaryTypes(op, left, right) {
6860
7403
  },
6861
7404
  };
6862
7405
  }
7406
+ if (op === "instanceof") {
7407
+ if (right.type & 16384 /* TypeTag.Class */) {
7408
+ if (!isExact(right)) {
7409
+ return { type: 6 /* TypeTag.Boolean */ };
7410
+ }
7411
+ right = { type: 32768 /* TypeTag.Object */, value: { klass: right } };
7412
+ }
7413
+ return {
7414
+ type: subtypeOf(left, right)
7415
+ ? 4 /* TypeTag.True */
7416
+ : !couldBeWeak(left, right)
7417
+ ? 2 /* TypeTag.False */
7418
+ : 6 /* TypeTag.Boolean */,
7419
+ };
7420
+ }
6863
7421
  const info = operators[op];
6864
7422
  if (!info)
6865
7423
  return { type: 524287 /* TypeTag.Any */ };
@@ -6982,7 +7540,7 @@ function checkCallArgs(istate, node, callees, args) {
6982
7540
  let argEffects = true;
6983
7541
  const object = calleeObjectType(istate, node.callee);
6984
7542
  if (object) {
6985
- const info = sysCallInfo(cur);
7543
+ const info = sysCallInfo(istate.state, cur);
6986
7544
  if (info) {
6987
7545
  const result = info(istate.state, cur, object, () => args);
6988
7546
  if (result.argTypes)
@@ -7113,16 +7671,18 @@ function isOverride(cur, funcs) {
7113
7671
  return false;
7114
7672
  }
7115
7673
  let systemCallInfo = null;
7116
- function sysCallInfo(func) {
7117
- const info = getSystemCallTable();
7674
+ let systemCallVersion;
7675
+ function sysCallInfo(state, func) {
7676
+ const info = getSystemCallTable(state);
7118
7677
  if (hasProperty(info, func.fullName)) {
7119
7678
  return info[func.fullName];
7120
7679
  }
7121
7680
  return null;
7122
7681
  }
7123
- function getSystemCallTable() {
7124
- if (systemCallInfo)
7682
+ function getSystemCallTable(state) {
7683
+ if (systemCallInfo && systemCallVersion === state.sdk) {
7125
7684
  return systemCallInfo;
7685
+ }
7126
7686
  const arrayAdd = (state, callee, calleeObj, getArgs) => {
7127
7687
  const ret = {};
7128
7688
  if (calleeObj.type & 512 /* TypeTag.Array */) {
@@ -7342,7 +7902,8 @@ function getSystemCallTable() {
7342
7902
  }
7343
7903
  return results;
7344
7904
  };
7345
- return (systemCallInfo = {
7905
+ systemCallVersion = state.sdk;
7906
+ return (systemCallInfo = expandKeys(state, {
7346
7907
  "$.Toybox.Lang.Array.add": arrayAdd,
7347
7908
  "$.Toybox.Lang.Array.addAll": arrayAdd,
7348
7909
  "$.Toybox.Lang.Array.remove": mod,
@@ -7385,7 +7946,47 @@ function getSystemCallTable() {
7385
7946
  "$.Toybox.Math.variance": nop,
7386
7947
  "$.Toybox.Math.srand": mod,
7387
7948
  "$.Toybox.Math.rand": mod,
7949
+ "$.Toybox.Lang.*.(to*|equals|abs)": nop,
7950
+ "$.Toybox.Time.Gregorian.(duration|info|localMoment|moment|utcInfo)": nop,
7951
+ "$.Toybox.Time.(Duration|LocalMoment|Moment).(?!initialize)*": nop,
7952
+ "$.Toybox.Graphics.Dc.get*": nop,
7953
+ }));
7954
+ }
7955
+ function expandKeys(state, table) {
7956
+ const result = {};
7957
+ const pattern = /[*()|]/;
7958
+ Object.entries(table).forEach(([key, value]) => {
7959
+ if (!pattern.test(key)) {
7960
+ result[key] = value;
7961
+ return;
7962
+ }
7963
+ if (state.stack) {
7964
+ const decls = key
7965
+ .split(".")
7966
+ .slice(1)
7967
+ .reduce((decls, decl) => {
7968
+ if (pattern.test(decl)) {
7969
+ const re = new RegExp(`^${decl.replace(/\*/g, ".*")}$`);
7970
+ return decls.flatMap((sn) => isStateNode(sn) && sn.decls
7971
+ ? Object.keys(sn.decls)
7972
+ .filter((m) => re.test(m))
7973
+ .flatMap((m) => sn.decls[m])
7974
+ : []);
7975
+ }
7976
+ else {
7977
+ return decls.flatMap((sn) => (isStateNode(sn) && sn.decls?.[decl]) || []);
7978
+ }
7979
+ }, [state.stack[0].sn]);
7980
+ decls.forEach((decl) => {
7981
+ if (decl.type === "FunctionDeclaration") {
7982
+ if (!hasProperty(result, decl.fullName)) {
7983
+ result[decl.fullName] = value;
7984
+ }
7985
+ }
7986
+ });
7987
+ }
7388
7988
  });
7989
+ return result;
7389
7990
  }
7390
7991
 
7391
7992
 
@@ -9064,6 +9665,24 @@ function optimizeFunction(state, func) {
9064
9665
  }
9065
9666
  function beforeEvaluate(istate, node) {
9066
9667
  switch (node.type) {
9668
+ case "ExpressionStatement": {
9669
+ if (node.expression.type !== "Literal") {
9670
+ const expression = popIstate(istate, node.expression);
9671
+ if (expression.embeddedEffects) {
9672
+ istate.stack.push(expression);
9673
+ }
9674
+ else {
9675
+ const rep = withLoc({ type: "Literal", value: null, raw: "null" }, node, node);
9676
+ istate.stack.push({
9677
+ value: { type: 1 /* TypeTag.Null */ },
9678
+ embeddedEffects: false,
9679
+ node: rep,
9680
+ });
9681
+ node.expression = rep;
9682
+ }
9683
+ }
9684
+ break;
9685
+ }
9067
9686
  case "ConditionalExpression": {
9068
9687
  let alternate = tryPop(istate, node.alternate);
9069
9688
  let consequent = tryPop(istate, node.consequent);
@@ -9812,7 +10431,7 @@ function subtypeOfObj(a, b) {
9812
10431
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9813
10432
  /* harmony export */ "nK": () => (/* binding */ findObjectDeclsByProperty)
9814
10433
  /* harmony export */ });
9815
- /* unused harmony exports isTypeStateKey, declIsLocal, localDeclName, tsKey, sourceLocation, printBlockHeader, describeEvent, printBlockEvents, printBlockTrailer, refineObjectTypeByDecls, findNextObjectType, resolveDottedMember */
10434
+ /* unused harmony exports isTypeStateKey, declIsLocal, declIsNonLocal, localDeclName, tsKey, sourceLocation, printBlockHeader, describeEvent, printBlockEvents, printBlockTrailer, refineObjectTypeByDecls, findNextObjectType, resolveDottedMember */
9816
10435
  /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6817);
9817
10436
  /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_api__WEBPACK_IMPORTED_MODULE_0__);
9818
10437
  /* harmony import */ var _data_flow__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8180);
@@ -9838,6 +10457,9 @@ function declIsLocal(decl) {
9838
10457
  d.type === "Identifier" ||
9839
10458
  (d.type === "VariableDeclarator" && isLocal(d)));
9840
10459
  }
10460
+ function declIsNonLocal(decl) {
10461
+ return some(decl, (d) => d.type === "VariableDeclarator" && !isLocal(d));
10462
+ }
9841
10463
  function localDeclName(decl) {
9842
10464
  if (Array.isArray(decl))
9843
10465
  decl = decl[0];
@@ -11300,6 +11922,23 @@ function cleanupUnusedVars(state, node) {
11300
11922
  return false;
11301
11923
  const varDeclarations = new Map();
11302
11924
  const stack = [];
11925
+ /*
11926
+ * Every local mentioned in toRemove can be removed, but
11927
+ * occurances of the identifier prior to its declaration
11928
+ * must be non-local. So reconstruct the toRemove record
11929
+ * as we go. This is to prevent issues with something like
11930
+ *
11931
+ * var g = 0;
11932
+ * function foo(var h) {
11933
+ * // if we just consult toRemove, we'll remove this assignment
11934
+ * g = h * 2;
11935
+ * var x = g;
11936
+ * ...
11937
+ * // *This* g is unused, and "g" gets added to activeRemove here.
11938
+ * var g = 42;
11939
+ * }
11940
+ */
11941
+ const activeRemove = {};
11303
11942
  let changes = false;
11304
11943
  traverseAst(node, (node) => {
11305
11944
  switch (node.type) {
@@ -11320,6 +11959,7 @@ function cleanupUnusedVars(state, node) {
11320
11959
  node.declarations.forEach((decl, i) => {
11321
11960
  const name = variableDeclarationName(decl.id);
11322
11961
  if (hasProperty(toRemove, name)) {
11962
+ activeRemove[name] = toRemove[name];
11323
11963
  const info = varDeclarations.get(node);
11324
11964
  if (info) {
11325
11965
  info.indices.push(i);
@@ -11337,14 +11977,14 @@ function cleanupUnusedVars(state, node) {
11337
11977
  case "ExpressionStatement":
11338
11978
  if (node.expression.type === "AssignmentExpression") {
11339
11979
  if (node.expression.left.type === "Identifier" &&
11340
- hasProperty(toRemove, node.expression.left.name)) {
11980
+ hasProperty(activeRemove, node.expression.left.name)) {
11341
11981
  changes = true;
11342
11982
  return unused(state, node.expression.right);
11343
11983
  }
11344
11984
  }
11345
11985
  else if (node.expression.type === "UpdateExpression" &&
11346
11986
  node.expression.argument.type === "Identifier" &&
11347
- hasProperty(toRemove, node.expression.argument.name)) {
11987
+ hasProperty(activeRemove, node.expression.argument.name)) {
11348
11988
  return false;
11349
11989
  }
11350
11990
  break;
@@ -11353,7 +11993,7 @@ function cleanupUnusedVars(state, node) {
11353
11993
  const expr = node.expressions[i];
11354
11994
  if (expr.type === "AssignmentExpression") {
11355
11995
  if (expr.left.type === "Identifier" &&
11356
- hasProperty(toRemove, expr.left.name)) {
11996
+ hasProperty(activeRemove, expr.left.name)) {
11357
11997
  const rep = unused(state, expr.right);
11358
11998
  if (!rep.length) {
11359
11999
  changes = true;
@@ -11362,14 +12002,14 @@ function cleanupUnusedVars(state, node) {
11362
12002
  else {
11363
12003
  // Sequence expressions can only be assignments
11364
12004
  // or update expressions. Even calls aren't allowed
11365
- toRemove[expr.left.name] = null;
12005
+ activeRemove[expr.left.name] = null;
11366
12006
  expr.operator = "=";
11367
12007
  }
11368
12008
  }
11369
12009
  }
11370
12010
  else if (expr.type === "UpdateExpression" &&
11371
12011
  expr.argument.type === "Identifier" &&
11372
- hasProperty(toRemove, expr.argument.name)) {
12012
+ hasProperty(activeRemove, expr.argument.name)) {
11373
12013
  changes = true;
11374
12014
  node.expressions.splice(i, 1);
11375
12015
  }
@@ -11385,7 +12025,7 @@ function cleanupUnusedVars(state, node) {
11385
12025
  const i = info.indices[ii];
11386
12026
  const vdecl = decl.declarations[i];
11387
12027
  const name = variableDeclarationName(vdecl.id);
11388
- if (hasProperty(toRemove, name)) {
12028
+ if (hasProperty(activeRemove, name)) {
11389
12029
  const rep = vdecl.init ? unused(state, vdecl.init) : [];
11390
12030
  if (rep.length) {
11391
12031
  if ((state.sdkVersion || 0) < 4001007 &&
@@ -11430,7 +12070,7 @@ function cleanupUnusedVars(state, node) {
11430
12070
  j = i;
11431
12071
  continue;
11432
12072
  }
11433
- if (toRemove[name]) {
12073
+ if (activeRemove[name]) {
11434
12074
  changes = true;
11435
12075
  j--;
11436
12076
  decl.declarations.splice(i, 1);
@@ -13392,13 +14032,14 @@ async function createDocumentationMap(functionDocumentation) {
13392
14032
  state.allFunctions[info.name]?.forEach((decl) => decl.node?.loc?.source === "api.mir" &&
13393
14033
  decl.fullName.endsWith(`.${info.parent}.${info.name}`) &&
13394
14034
  docMap.set(decl.fullName, info.doc
14035
+ .replace(/@example\s*(.*?)<br\/>(.*?)(@|$)/g, (match, title, m1, m2) => `\n#### Example: ${title}\n\`\`\`${m1.replace(/<br\/>/g, "\n")}\`\`\`${m2}`)
13395
14036
  .replace(/(\*.*?)\s*<br\/>\s*(?!\s*\*)/g, "$1\n\n")
13396
- .replace(/@example(.*?)(@|$)/g, (match, m1, m2) => `\n#### Example\n\`\`\`${m1.replace(/<br\/>/g, "\n")}\`\`\`${m2}`)
13397
14037
  .replace(/@note/g, "\n#### Note\n")
13398
14038
  .replace(/@see/, "\n#### See Also:\n$&")
13399
14039
  .replace(/@see\s+(.*?)(?=<br\/>)/g, "\n * {$1}")
13400
14040
  .replace(/@throws/, "\n#### Throws:\n$&")
13401
14041
  .replace(/@throws\s+(.*?)(?=<br\/>)/g, "\n * $1")
14042
+ .replace(/@option\s+\w+\s+(.*?)(?=<br\/>)/g, "\n - $1")
13402
14043
  .replace(/@since\s+(.*?)(?=<br\/>)/, "\n#### Since:\nAPI Level $1\n")
13403
14044
  .replace(/<div class="description">/, "### Description\n")
13404
14045
  .replace(/<div class="param">/, "\n#### Parameters\n$&")