Skip to content
This repository was archived by the owner on Feb 12, 2022. It is now read-only.

Leaking of __empty variable and highly inefficient optimization #2573

@trueadm

Description

@trueadm

Whilst working with React children, one of the operations we do is "flatten" arrays. This is a very common use-case in JS applications and it seems to be one that Prepack struggles to do when the abstract contains abstract elements/lengths.

Take the below case:

(function() {
  function makeArr(x) {
    var arr = [1, 2, 3];
    if (x) {
      arr.push(4);
    } else {
      arr.pop();
    }
    return arr;
  }

  function recursivelyFlatten(arr, baseArr) {
    for (var i = 0; i < arr.length; i++) {
      var elem = arr[i];

      if (Array.isArray(elem)) {
        recursivelyFlatten(elem, baseArr);
      } else {
        baseArr.push(elem);
      }
    }
  }

  function flatten(arr) {
    var baseArr = [];
    recursivelyFlatten(arr, baseArr);
    return baseArr;
  }

  global.fn = function fn(x) {
    var arr1 = makeArr(x);
    var arr2 = makeArr(x);
    return flatten([arr1, arr2]);
  };

  global.__optimize && __optimize(fn);
})();

The output for this is:

(function () {
  var _$Q = this;

  var _1 = function (x) {
    var __get_scope_binding_0 = function (__selector) {
      var __captured;

      switch (__selector) {
        case 0:
        case 1:
          __captured = [void 0, void 0];
          break;
      }

      __scope_0[__selector] = __captured;
      return __captured;
    };

    var __scope_0 = new Array(2);

    var __leaked_6, __leaked_7, __leaked_4, __leaked_5;

    var _W = function () {
      var __captured__scope_1 = __scope_0[0] || __get_scope_binding_0(0);

      for (__captured__scope_1[0] = 0; __captured__scope_1[0] < __leaked_6.length; __captured__scope_1[0]++) {
        __captured__scope_1[1] = __leaked_6[__captured__scope_1[0]];

        if (Array.isArray(__captured__scope_1[1])) {
          _Z(__captured__scope_1[1], __leaked_7);
        } else {
          __leaked_7.push(__captured__scope_1[1]);
        }
      }
    };

    var _X = function () {
      var __captured__scope_2 = __scope_0[1] || __get_scope_binding_0(1);

      for (__captured__scope_2[0] = 0; __captured__scope_2[0] < __leaked_4.length; __captured__scope_2[0]++) {
        __captured__scope_2[1] = __leaked_4[__captured__scope_2[0]];

        if (Array.isArray(__captured__scope_2[1])) {
          _Z(__captured__scope_2[1], __leaked_5);
        } else {
          __leaked_5.push(__captured__scope_2[1]);
        }
      }
    };

    var _2 = [1, 2,,,];
    _2.length = x ? 4 : 2;
    _2[0] = 1;
    _2[1] = 2;

    var _5 = x ? 3 : __empty;

    if (x) _2[2] = 3;else delete _2[2];

    var _9 = x ? 4 : __empty;

    if (x) _2[3] = 4;else delete _2[3];
    __leaked_6 = _2;
    var _I = [,,];
    _I.length = 0;
    delete _I[0];
    delete _I[1];
    __leaked_7 = _I;

    var _$0 = _W();

    var _M = [1, 2,,,];
    _M.length = x ? 4 : 2;
    _M[0] = 1;
    _M[1] = 2;
    if (x) _M[2] = 3;else delete _M[2];
    if (x) _M[3] = 4;else delete _M[3];
    __leaked_4 = _M;
    __leaked_5 = _I;

    var _$5 = _X();

    return _I;
  };

  var _Z = function (arr, baseArr) {
    for (var i = 0; i < arr.length; i++) {
      var elem = arr[i];

      if (Array.isArray(elem)) {
        _Z(elem, baseArr);
      } else {
        baseArr.push(elem);
      }
    }
  };

  var __empty = {};
  _$Q.fn = _1;
}).call(this);

You can see we have __empty serialized out above, which accordingly to @NTillmann, should never happen.

The output that Prepack creates is not optimal at all and definitely will hit the slow paths in all JS VMs. Ideally what we should be outputting in the base-case is this:

function _1(x) {
  var _$1 = x ? [3, 4] : [];
  var _$2 = x ? [3, 4] : [];
  return [1, 2, ..._$0, 1, 2, ..._$1];
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions