Bug 1641359 - Add Iterator.from method. r=jorendorff
authorAdam Vandolder <avandolder@mozilla.com>
Wed, 03 Jun 2020 14:18:01 +0000
changeset 533705 9bcf68d2c0df4b5910ae8f96299a2ff9291720bf
parent 533704 99cce372d20cc3cc99df7728b950fa8fcc234bc1
child 533706 b986ac44aea1a0cd3b5c59467a7566560853d1a3
push id37476
push userccoroiu@mozilla.com
push dateWed, 03 Jun 2020 21:49:22 +0000
treeherdermozilla-central@66d3efe9fc7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1641359
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1641359 - Add Iterator.from method. r=jorendorff Implement Iterator.from static method from the Iterator Helpers proposal. Involves adding a WrapForValidIterator object and prototype that is used to wrap iterators returned by `Iterator.from`. Differential Revision: https://phabricator.services.mozilla.com/D77178
js/public/Class.h
js/src/builtin/Iterator.js
js/src/builtin/SelfHostingDefines.h
js/src/jit/InlinableNatives.h
js/src/jit/MCallOptimize.cpp
js/src/tests/jstests.list
js/src/tests/non262/Iterator/from/Iterator.from-descriptor.js
js/src/tests/non262/Iterator/from/Iterator.from-length.js
js/src/tests/non262/Iterator/from/Iterator.from-name.js
js/src/tests/non262/Iterator/from/call-from-with-different-this.js
js/src/tests/non262/Iterator/from/iterator-not-callable-throws.js
js/src/tests/non262/Iterator/from/modify-next.js
js/src/tests/non262/Iterator/from/modify-return.js
js/src/tests/non262/Iterator/from/modify-throw.js
js/src/tests/non262/Iterator/from/o-not-object-throws.js
js/src/tests/non262/Iterator/from/proxy-not-wrapped.js
js/src/tests/non262/Iterator/from/proxy-wrap-next.js
js/src/tests/non262/Iterator/from/proxy-wrap-return.js
js/src/tests/non262/Iterator/from/proxy-wrap-throw.js
js/src/tests/non262/Iterator/from/return-iterator-if-iterable.js
js/src/tests/non262/Iterator/from/return-wrapper-if-not-iterable.js
js/src/tests/non262/Iterator/from/return-wrapper-if-not-iterator-instance.js
js/src/tests/non262/Iterator/from/wrap-method-with-non-wrap-this-throws.js
js/src/tests/non262/Iterator/from/wrap-new-global.js
js/src/tests/non262/Iterator/from/wrap-next-forwards-value.js
js/src/tests/non262/Iterator/from/wrap-next-not-object-throws.js
js/src/tests/non262/Iterator/from/wrap-return-closes-iterator.js
js/src/tests/non262/Iterator/from/wrap-throw.js
js/src/vm/GlobalObject.h
js/src/vm/Iteration.cpp
js/src/vm/Iteration.h
js/src/vm/SelfHosting.cpp
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -695,17 +695,17 @@ static const uint32_t JSCLASS_FOREGROUND
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
 static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
-    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 25;
+    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 26;
 
 static constexpr uint32_t JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(uint32_t n) {
   return JSCLASS_IS_GLOBAL |
          JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + n);
 }
 
 static constexpr uint32_t JSCLASS_GLOBAL_FLAGS =
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0);
--- a/js/src/builtin/Iterator.js
+++ b/js/src/builtin/Iterator.js
@@ -1,7 +1,118 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function IteratorIdentity() {
-    return this;
+  return this;
+}
+
+/* Iterator Helpers proposal 1.1.1 */
+function GetIteratorDirect(obj) {
+  // Step 1.
+  if (!IsObject(obj))
+    ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, obj));
+
+  // Step 2.
+  const nextMethod = obj.next;
+  // Step 3.
+  if (!IsCallable(nextMethod))
+    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, nextMethod));
+
+  // Steps 4-5.
+  return {
+    iterator: obj,
+    nextMethod,
+    done: false,
+  };
+}
+
+/* Iterator Helpers proposal 2.1.3.3.1 */
+function IteratorFrom(O) {
+  // Step 1.
+  const usingIterator = O[std_iterator];
+
+  let iteratorRecord;
+  // Step 2.
+  if (usingIterator !== undefined && usingIterator !== null) {
+    // Step a.
+    // Inline call to GetIterator.
+    const iterator = callContentFunction(usingIterator, O);
+    iteratorRecord = GetIteratorDirect(iterator);
+    // Step b-c.
+    if (iteratorRecord.iterator instanceof GetBuiltinConstructor("Iterator"))
+      return iteratorRecord.iterator;
+  } else {
+    // Step 3.
+    iteratorRecord = GetIteratorDirect(O);
+  }
+
+  // Step 4.
+  const wrapper = NewWrapForValidIterator();
+  // Step 5.
+  UnsafeSetReservedSlot(wrapper, ITERATED_SLOT, iteratorRecord);
+  // Step 6.
+  return wrapper;
 }
+
+/* Iterator Helpers proposal 2.1.3.3.1.1.1 */
+function WrapForValidIteratorNext(value) {
+  // Step 1-2.
+  let O;
+  if (!IsObject(this) || (O = GuardToWrapForValidIterator(this)) === null)
+    ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, O));
+  const iterated = UnsafeGetReservedSlot(O, ITERATED_SLOT);
+  // Step 3.
+  let result;
+  if (arguments.length === 0) {
+    result = callContentFunction(iterated.nextMethod, iterated.iterator);
+  } else { // Step 4.
+    result = callContentFunction(iterated.nextMethod, iterated.iterator, value);
+  }
+  // Inlined from IteratorNext.
+  if (!IsObject(result))
+    ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, result));
+  return result;
+}
+
+/* Iterator Helpers proposal 2.1.3.3.1.1.2 */
+function WrapForValidIteratorReturn(value) {
+  // Step 1-2.
+  let O;
+  if (!IsObject(this) || (O = GuardToWrapForValidIterator(this)) === null)
+    ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, O));
+  const iterated = UnsafeGetReservedSlot(O, ITERATED_SLOT);
+
+  // Step 3.
+  // Inline call to IteratorClose.
+  const iterator = iterated.iterator;
+  const returnMethod = iterator.return;
+  if (returnMethod !== undefined && returnMethod !== null) {
+    let innerResult = callContentFunction(returnMethod, iterator);
+    if (!IsObject(innerResult))
+      ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, innerResult));
+  }
+  // Step 4.
+  return {
+    done: true,
+    value,
+  };
+}
+
+/* Iterator Helpers proposal 2.1.3.3.1.1.3 */
+function WrapForValidIteratorThrow(value) {
+  // Step 1-2.
+  let O;
+  if (!IsObject(this) || (O = GuardToWrapForValidIterator(this)) === null)
+    ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, O));
+  const iterated = UnsafeGetReservedSlot(O, ITERATED_SLOT);
+  // Step 3.
+  const iterator = iterated.iterator;
+  // Step 4.
+  const throwMethod = iterator.throw;
+  // Step 5.
+  if (throwMethod === undefined || throwMethod === null) {
+    throw value;
+  }
+  // Step 6.
+  return callContentFunction(throwMethod, iterator, value);
+}
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -129,9 +129,11 @@
 #define TYPEDARRAY_KIND_INT32 4
 #define TYPEDARRAY_KIND_UINT32 5
 #define TYPEDARRAY_KIND_FLOAT32 6
 #define TYPEDARRAY_KIND_FLOAT64 7
 #define TYPEDARRAY_KIND_UINT8CLAMPED 8
 #define TYPEDARRAY_KIND_BIGINT64 9
 #define TYPEDARRAY_KIND_BIGUINT64 10
 
+#define ITERATED_SLOT 0
+
 #endif
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -147,16 +147,17 @@
                                                    \
   _(IntrinsicIsSuspendedGenerator)                 \
                                                    \
   _(IntrinsicGuardToArrayIterator)                 \
   _(IntrinsicGuardToMapIterator)                   \
   _(IntrinsicGuardToSetIterator)                   \
   _(IntrinsicGuardToStringIterator)                \
   _(IntrinsicGuardToRegExpStringIterator)          \
+  _(IntrinsicGuardToWrapForValidIterator)          \
                                                    \
   _(IntrinsicGuardToMapObject)                     \
   _(IntrinsicGetNextMapEntryForIterator)           \
                                                    \
   _(IntrinsicGuardToSetObject)                     \
   _(IntrinsicGetNextSetEntryForIterator)           \
                                                    \
   _(IntrinsicNewArrayIterator)                     \
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -147,16 +147,17 @@ static bool CanInlineCrossRealm(Inlinabl
     case InlinableNative::IntrinsicIsConstructing:
     case InlinableNative::IntrinsicIsSuspendedGenerator:
     case InlinableNative::IntrinsicSubstringKernel:
     case InlinableNative::IntrinsicGuardToArrayIterator:
     case InlinableNative::IntrinsicGuardToMapIterator:
     case InlinableNative::IntrinsicGuardToSetIterator:
     case InlinableNative::IntrinsicGuardToStringIterator:
     case InlinableNative::IntrinsicGuardToRegExpStringIterator:
+    case InlinableNative::IntrinsicGuardToWrapForValidIterator:
     case InlinableNative::IntrinsicObjectHasPrototype:
     case InlinableNative::IntrinsicFinishBoundFunctionInit:
     case InlinableNative::IntrinsicIsPackedArray:
     case InlinableNative::IntrinsicGuardToMapObject:
     case InlinableNative::IntrinsicGetNextMapEntryForIterator:
     case InlinableNative::IntrinsicGuardToSetObject:
     case InlinableNative::IntrinsicGetNextSetEntryForIterator:
     case InlinableNative::IntrinsicGuardToArrayBuffer:
@@ -569,16 +570,18 @@ IonBuilder::InliningResult IonBuilder::i
     case InlinableNative::IntrinsicGuardToMapIterator:
       return inlineGuardToClass(callInfo, &MapIteratorObject::class_);
     case InlinableNative::IntrinsicGuardToSetIterator:
       return inlineGuardToClass(callInfo, &SetIteratorObject::class_);
     case InlinableNative::IntrinsicGuardToStringIterator:
       return inlineGuardToClass(callInfo, &StringIteratorObject::class_);
     case InlinableNative::IntrinsicGuardToRegExpStringIterator:
       return inlineGuardToClass(callInfo, &RegExpStringIteratorObject::class_);
+    case InlinableNative::IntrinsicGuardToWrapForValidIterator:
+      return inlineGuardToClass(callInfo, &WrapForValidIteratorObject::class_);
     case InlinableNative::IntrinsicObjectHasPrototype:
       return inlineObjectHasPrototype(callInfo);
     case InlinableNative::IntrinsicFinishBoundFunctionInit:
       return inlineFinishBoundFunctionInit(callInfo);
     case InlinableNative::IntrinsicIsPackedArray:
       return inlineIsPackedArray(callInfo);
 
     // Map intrinsics.
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -723,8 +723,30 @@ shell-option(--enable-iterator-helpers) 
 shell-option(--enable-iterator-helpers) script non262/Iterator/length.js
 shell-option(--enable-iterator-helpers) script non262/Iterator/name.js
 shell-option(--enable-iterator-helpers) script non262/Iterator/proto.js
 shell-option(--enable-iterator-helpers) script non262/Iterator/constructor.js
 shell-option(--enable-iterator-helpers) script non262/Iterator/constructor-subclassable.js
 shell-option(--enable-iterator-helpers) script non262/Iterator/constructor-throw-when-called-directly.js
 shell-option(--enable-iterator-helpers) script non262/Iterator/constructor-throw-without-new.js
 shell-option(--enable-iterator-helpers) script non262/Iterator/toStringTag.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/call-from-with-different-this.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/Iterator.from-descriptor.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/Iterator.from-length.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/Iterator.from-name.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/iterator-not-callable-throws.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/modify-next.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/modify-return.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/modify-throw.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/o-not-object-throws.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/proxy-not-wrapped.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/proxy-wrap-next.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/proxy-wrap-return.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/proxy-wrap-throw.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/return-iterator-if-iterable.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/return-wrapper-if-not-iterable.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/return-wrapper-if-not-iterator-instance.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/wrap-method-with-non-wrap-this-throws.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/wrap-next-forwards-value.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/wrap-next-not-object-throws.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/wrap-new-global.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/wrap-return-closes-iterator.js
+shell-option(--enable-iterator-helpers) script non262/Iterator/from/wrap-throw.js
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/Iterator.from-descriptor.js
@@ -0,0 +1,12 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+/*---
+  Descriptor property of Iterator.from
+---*/
+
+const propDesc = Reflect.getOwnPropertyDescriptor(Iterator, 'from');
+assertEq(propDesc.writable, true);
+assertEq(propDesc.enumerable, false);
+assertEq(propDesc.configurable, true);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/Iterator.from-length.js
@@ -0,0 +1,17 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+/*---
+  The `length` property of Iterator.from.
+info: |
+  ES7 section 17: Unless otherwise specified, the length property of a built-in
+  Function object has the attributes { [[Writable]]: false, [[Enumerable]]:
+  false, [[Configurable]]: true }.
+---*/
+
+const propDesc = Reflect.getOwnPropertyDescriptor(Iterator.from, 'length');
+assertEq(propDesc.value, 1);
+assertEq(propDesc.writable, false);
+assertEq(propDesc.enumerable, false);
+assertEq(propDesc.configurable, true);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/Iterator.from-name.js
@@ -0,0 +1,13 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+/*---
+  `name` property of Iterator.from.
+---*/
+
+const propDesc = Reflect.getOwnPropertyDescriptor(Iterator.from, 'name');
+assertEq(propDesc.value, 'from');
+assertEq(propDesc.writable, false);
+assertEq(propDesc.enumerable, false);
+assertEq(propDesc.configurable, true);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/call-from-with-different-this.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const iter = {
+  next: () => ({done: false, value: 0}),
+};
+const wrap = Iterator.from.call(undefined, iter);
+
+const result = wrap.next();
+assertEq(result.done, false);
+assertEq(result.value, 0);
+
+const returnResult = wrap.return(1);
+assertEq(returnResult.done, true);
+assertEq(returnResult.value, 1);
+
+assertThrowsInstanceOf(() => wrap.throw(new Error()), Error);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/iterator-not-callable-throws.js
@@ -0,0 +1,13 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+/*---
+  Iterator.from throws when called with an object with a non-callable @@iterator property.
+---*/
+
+assertThrowsInstanceOf(() => Iterator.from({ [Symbol.iterator]: 0 }), TypeError);
+assertThrowsInstanceOf(() => Iterator.from({ [Symbol.iterator]: false }), TypeError);
+assertThrowsInstanceOf(() => Iterator.from({ [Symbol.iterator]: "" }), TypeError);
+assertThrowsInstanceOf(() => Iterator.from({ [Symbol.iterator]: {} }), TypeError);
+assertThrowsInstanceOf(() => Iterator.from({ [Symbol.iterator]: Symbol('') }), TypeError);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/modify-next.js
@@ -0,0 +1,15 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const iter = {
+  next: () => ({ done: false, value: 0 }),
+};
+
+const wrap = Iterator.from(iter);
+
+iter.next = () => ({ done: true, value: undefined });
+
+let {done, value} = wrap.next();
+assertEq(done, false);
+assertEq(value, 0);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/modify-return.js
@@ -0,0 +1,22 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const iter = {
+  next: () => ({ done: false, value: 0 }),
+  return: (value) => ({ done: true, value }),
+};
+
+const wrap = Iterator.from(iter);
+
+let {done, value} = wrap.return(1);
+assertEq(done, true);
+assertEq(value, 1);
+
+iter.return = () => { throw new Error(); };
+assertThrowsInstanceOf(wrap.return, Error);
+
+iter.return = null;
+let nullResult = wrap.return(2);
+assertEq(nullResult.done, true);
+assertEq(nullResult.value, 2);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/modify-throw.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const iter = {
+  next: () => ({ done: false, value: 0 }),
+  throw: (value) => ({ done: true, value }),
+};
+
+const wrap = Iterator.from(iter);
+
+let {done, value} = wrap.throw(0);
+assertEq(done, true);
+assertEq(value, 0);
+
+class TestError extends Error {}
+iter.throw = () => { throw new TestError(); };
+assertThrowsInstanceOf(() => wrap.throw(), TestError);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/o-not-object-throws.js
@@ -0,0 +1,13 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+/*---
+  Iterator.from throws when called with a non-object.
+---*/
+
+assertThrowsInstanceOf(() => Iterator.from(undefined), TypeError);
+assertThrowsInstanceOf(() => Iterator.from(null), TypeError);
+assertThrowsInstanceOf(() => Iterator.from(0), TypeError);
+assertThrowsInstanceOf(() => Iterator.from(false), TypeError);
+assertThrowsInstanceOf(() => Iterator.from(Symbol('')), TypeError);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/proxy-not-wrapped.js
@@ -0,0 +1,34 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const log = [];
+const handlerProxy = new Proxy({}, {
+  get: (target, key, receiver) => (...args) => {
+    log.push(`${key}: ${args[1]?.toString()}`);
+
+    const item = Reflect[key](...args);
+    if (typeof item === 'function')
+      return (...args) => new Proxy(item.apply(receiver, args), handlerProxy);
+    return item;
+  },
+});
+
+class Iter extends Iterator {
+  [Symbol.iterator]() {
+    return this;
+  }
+  next() {
+    return { done: false, value: 0 };
+  }
+}
+const iter = new Iter();
+const proxy = new Proxy(iter, handlerProxy);
+const wrap = Iterator.from(proxy);
+
+assertEq(
+  log.join('\n'),
+  `get: Symbol(Symbol.iterator)
+get: next
+getPrototypeOf: undefined`
+);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/proxy-wrap-next.js
@@ -0,0 +1,30 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const log = [];
+const handlerProxy = new Proxy({}, {
+  get: (target, key, receiver) => (...args) => {
+    log.push(`${key}: ${args[1].toString()}`);
+
+    const item = Reflect[key](...args);
+    if (typeof item === 'function')
+      return item.bind(receiver);
+    return item;
+  },
+});
+const iter = new Proxy({
+  next: () => ({ done: false, value: 0 }),
+}, handlerProxy);
+
+const wrap = Iterator.from(iter);
+// Call next multiple times. Should not call `get` on proxy.
+wrap.next();
+wrap.next();
+wrap.next();
+
+assertEq(
+  log.join('\n'),
+  `get: Symbol(Symbol.iterator)
+get: next`
+);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/proxy-wrap-return.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const log = [];
+const handlerProxy = new Proxy({}, {
+  get: (target, key, receiver) => (...args) => {
+    log.push(`${key}: ${args[1].toString()}`);
+
+    const item = Reflect[key](...args);
+    if (typeof item === 'function')
+      return item.bind(receiver);
+    return item;
+  },
+});
+const iter = new Proxy({
+  next: () => ({ done: false, value: 0 }),
+  return: (value) => ({ done: true, value }),
+}, handlerProxy);
+
+const wrap = Iterator.from(iter);
+wrap.return();
+wrap.return();
+
+assertEq(
+  log.join('\n'),
+  `get: Symbol(Symbol.iterator)
+get: next
+get: return
+get: return`
+);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/proxy-wrap-throw.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const log = [];
+const handlerProxy = new Proxy({}, {
+  get: (target, key, receiver) => (...args) => {
+    log.push(`${key}: ${args[1].toString()}`);
+
+    const item = Reflect[key](...args);
+    if (typeof item === 'function')
+      return item.bind(receiver);
+    return item;
+  },
+});
+const iter = new Proxy({
+  next: () => ({ done: false, value: 0 }),
+  throw: (value) => ({ done: true, value }),
+}, handlerProxy);
+
+const wrap = Iterator.from(iter);
+wrap.throw();
+wrap.throw();
+
+assertEq(
+  log.join('\n'),
+  `get: Symbol(Symbol.iterator)
+get: next
+get: throw
+get: throw`
+);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/return-iterator-if-iterable.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+/*---
+  Iterator.from returns O if it is iterable, an iterator, and an instance of Iterator.
+---*/
+
+class TestIterator extends Iterator {
+  [Symbol.iterator]() {
+    return this;
+  }
+
+  next() {
+    return { done: false, value: this.value++ };
+  }
+
+  value = 0;
+}
+
+const iter = new TestIterator();
+assertEq(iter, Iterator.from(iter));
+
+const arrayIter = [1, 2, 3][Symbol.iterator]();
+assertEq(arrayIter, Iterator.from(arrayIter));
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/return-wrapper-if-not-iterable.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+/*---
+  Iterator.from returns an iterator wrapper if O is not an iterable.
+---*/
+
+class TestIterator {
+  next() {
+    return { done: false, value: 0 };
+  }
+}
+
+const iter = new TestIterator();
+assertEq(
+  Symbol.iterator in iter,
+  false,
+  'iter is not an iterable.'
+);
+
+const wrapper = Iterator.from(iter);
+assertEq(iter !== wrapper, true);
+assertEq(
+  Symbol.iterator in wrapper,
+  true,
+  'wrapper is an iterable.'
+);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/return-wrapper-if-not-iterator-instance.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+/*---
+  Iterator.from returns an iterator wrapper if O is not an instance of Iterator.
+---*/
+
+class TestIterator {
+  [Symbol.iterator]() {
+    return this;
+  }
+
+  next() {
+    return { done: false, value: 0 };
+  }
+}
+
+const iter = new TestIterator();
+assertEq(iter instanceof Iterator, false);
+
+const wrapper = Iterator.from(iter);
+assertEq(iter !== wrapper, true);
+assertEq(wrapper instanceof Iterator, true);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/wrap-method-with-non-wrap-this-throws.js
@@ -0,0 +1,42 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+// All methods on %WrapForValidIteratorPrototype% require an [[Iterated]]
+// internal slot on the `this` object.
+
+class TestIterator {
+  next() {
+    return {
+      done: false,
+      value: 0,
+    };
+  }
+}
+
+const nextMethod = Iterator.from(new TestIterator()).next;
+assertThrowsInstanceOf(() => nextMethod.call(undefined), TypeError);
+assertThrowsInstanceOf(() => nextMethod.call(null), TypeError);
+assertThrowsInstanceOf(() => nextMethod.call(0), TypeError);
+assertThrowsInstanceOf(() => nextMethod.call(false), TypeError);
+assertThrowsInstanceOf(() => nextMethod.call('test'), TypeError);
+assertThrowsInstanceOf(() => nextMethod.call(Object(1)), TypeError);
+assertThrowsInstanceOf(() => nextMethod.call({}), TypeError);
+
+const returnMethod = Iterator.from(new TestIterator()).next;
+assertThrowsInstanceOf(() => returnMethod.call(undefined), TypeError);
+assertThrowsInstanceOf(() => returnMethod.call(null), TypeError);
+assertThrowsInstanceOf(() => returnMethod.call(0), TypeError);
+assertThrowsInstanceOf(() => returnMethod.call(false), TypeError);
+assertThrowsInstanceOf(() => returnMethod.call('test'), TypeError);
+assertThrowsInstanceOf(() => returnMethod.call(Object(1)), TypeError);
+assertThrowsInstanceOf(() => returnMethod.call({}), TypeError);
+
+const throwMethod = Iterator.from(new TestIterator()).next;
+assertThrowsInstanceOf(() => throwMethod.call(undefined), TypeError);
+assertThrowsInstanceOf(() => throwMethod.call(null), TypeError);
+assertThrowsInstanceOf(() => throwMethod.call(0), TypeError);
+assertThrowsInstanceOf(() => throwMethod.call(false), TypeError);
+assertThrowsInstanceOf(() => throwMethod.call('test'), TypeError);
+assertThrowsInstanceOf(() => throwMethod.call(Object(1)), TypeError);
+assertThrowsInstanceOf(() => throwMethod.call({}), TypeError);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/wrap-new-global.js
@@ -0,0 +1,9 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const otherGlobal = newGlobal({newCompartment: true});
+
+const iter = [1, 2, 3].values();
+assertEq(iter, Iterator.from(iter));
+assertEq(iter !== otherGlobal.Iterator.from(iter), true);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/wrap-next-forwards-value.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+class Iter {
+  next(value) {
+    this.v = value;
+    return { done: false, value };
+  }
+}
+
+const iter = new Iter();
+const wrap = Iterator.from(iter);
+assertEq(iter !== wrap, true);
+
+assertEq(iter.v, undefined);
+wrap.next(1);
+assertEq(iter.v, 1);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/wrap-next-not-object-throws.js
@@ -0,0 +1,14 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+const iter = (value) => Iterator.from({
+  next: () => value,
+});
+
+assertThrowsInstanceOf(() => iter(undefined).next(), TypeError);
+assertThrowsInstanceOf(() => iter(null).next(), TypeError);
+assertThrowsInstanceOf(() => iter(0).next(), TypeError);
+assertThrowsInstanceOf(() => iter(false).next(), TypeError);
+assertThrowsInstanceOf(() => iter('test').next(), TypeError);
+assertThrowsInstanceOf(() => iter(Symbol('')).next(), TypeError);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/wrap-return-closes-iterator.js
@@ -0,0 +1,33 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+class Iter {
+  next() {
+    if (this.closed)
+      return { done: true, value: undefined };
+    return { done: false, value: 0 };
+  }
+
+  return(value) {
+    this.closed = true;
+    return { done: true, value };
+  }
+}
+
+const iter = new Iter();
+const wrap = Iterator.from(iter);
+assertEq(iter.closed, undefined);
+
+let result = wrap.next();
+assertEq(result.done, false);
+assertEq(result.value, 0);
+
+result = wrap.return(1);
+assertEq(result.done, true);
+assertEq(result.value, 1);
+
+assertEq(iter.closed, true);
+result = wrap.next();
+assertEq(result.done, true);
+assertEq(result.value, undefined);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Iterator/from/wrap-throw.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Iterator')) -- Iterator is not enabled unconditionally
+class Iter {
+  next() {
+    return { done: false, value: 0 };
+  }
+}
+
+const iter = new Iter();
+const wrap = Iterator.from(iter);
+
+assertThrowsInstanceOf(() => wrap.throw(new Error()), Error);
+assertThrows(() => wrap.throw());
+assertThrows(() => wrap.throw(1));
+
+class IterThrowNull {
+  next() {
+    return { done: false, value: 0 };
+  }
+  throw = null;
+}
+
+const iterNull = new IterThrowNull();
+const wrapNull = Iterator.from(iter);
+
+assertThrowsInstanceOf(() => wrapNull.throw(new Error()), Error);
+assertThrows(() => wrapNull.throw());
+assertThrows(() => wrapNull.throw(1));
+
+class IterWithThrow {
+  next() {
+    return { done: false, value: 0 };
+  }
+
+  throw(value) {
+    return value;
+  }
+}
+
+const iterWithThrow = new IterWithThrow();
+const wrapWithThrow = Iterator.from(iterWithThrow);
+
+assertEq(wrapWithThrow.throw(1), 1);
+
+if (typeof reportCompare === 'function')
+  reportCompare(0, 0);
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -106,16 +106,17 @@ class GlobalObject : public NativeObject
     STRING_ITERATOR_PROTO,
     REGEXP_STRING_ITERATOR_PROTO,
     GENERATOR_OBJECT_PROTO,
     ASYNC_ITERATOR_PROTO,
     ASYNC_FROM_SYNC_ITERATOR_PROTO,
     ASYNC_GENERATOR_PROTO,
     MAP_ITERATOR_PROTO,
     SET_ITERATOR_PROTO,
+    WRAP_FOR_VALID_ITERATOR_PROTO,
     MODULE_PROTO,
     IMPORT_ENTRY_PROTO,
     EXPORT_ENTRY_PROTO,
     REQUESTED_MODULE_PROTO,
     REGEXP_STATICS,
     RUNTIME_CODEGEN_ENABLED,
     INTRINSICS,
     FOR_OF_PIC_CHAIN,
@@ -721,16 +722,23 @@ class GlobalObject : public NativeObject
   static JSObject* getOrCreatePromiseConstructor(JSContext* cx,
                                                  Handle<GlobalObject*> global) {
     if (!ensureConstructor(cx, global, JSProto_Promise)) {
       return nullptr;
     }
     return &global->getConstructor(JSProto_Promise).toObject();
   }
 
+  static NativeObject* getOrCreateWrapForValidIteratorPrototype(
+      JSContext* cx, Handle<GlobalObject*> global) {
+    return MaybeNativeObject(getOrCreateObject(cx, global,
+                                               WRAP_FOR_VALID_ITERATOR_PROTO,
+                                               initWrapForValidIteratorProto));
+  }
+
   static NativeObject* getIntrinsicsHolder(JSContext* cx,
                                            Handle<GlobalObject*> global);
 
   bool maybeExistingIntrinsicValue(PropertyName* name, Value* vp) {
     Value slot = getReservedSlot(INTRINSICS);
     // If we're in the self-hosting compartment itself, the
     // intrinsics-holder isn't initialized at this point.
     if (slot.isUndefined()) {
@@ -825,16 +833,18 @@ class GlobalObject : public NativeObject
   // Implemented in vm/Iteration.cpp.
   static bool initIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
   static bool initArrayIteratorProto(JSContext* cx,
                                      Handle<GlobalObject*> global);
   static bool initStringIteratorProto(JSContext* cx,
                                       Handle<GlobalObject*> global);
   static bool initRegExpStringIteratorProto(JSContext* cx,
                                             Handle<GlobalObject*> global);
+  static bool initWrapForValidIteratorProto(JSContext*,
+                                            Handle<GlobalObject*> global);
 
   // Implemented in vm/AsyncIteration.cpp.
   static bool initAsyncIteratorProto(JSContext* cx,
                                      Handle<GlobalObject*> global);
   static bool initAsyncFromSyncIteratorProto(JSContext* cx,
                                              Handle<GlobalObject*> global);
 
   // Implemented in builtin/MapObject.cpp.
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.cpp
@@ -1500,17 +1500,18 @@ bool js::SuppressDeletedElement(JSContex
 
   Rooted<JSLinearString*> str(cx, IdToString(cx, id));
   if (!str) {
     return false;
   }
   return SuppressDeletedPropertyHelper(cx, obj, str);
 }
 
-static const JSFunctionSpec iterator_static_methods[] = {JS_FS_END};
+static const JSFunctionSpec iterator_static_methods[] = {
+    JS_SELF_HOSTED_FN("from", "IteratorFrom", 1, 0), JS_FS_END};
 
 static const JSFunctionSpec iterator_proto_methods[] = {
     JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0), JS_FS_END};
 
 static const JSPropertySpec iterator_proto_properties[] = {
     JS_STRING_SYM_PS(toStringTag, js_Iterator_str, JSPROP_READONLY),
     JS_PS_END,
 };
@@ -1665,8 +1666,59 @@ const JSClass IteratorObject::class_ = {
 };
 
 const JSClass IteratorObject::protoClass_ = {
     js_Iterator_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
     JS_NULL_CLASS_OPS,
     &IteratorObjectClassSpec,
 };
+
+// Set up WrapForValidIteratorObject class and its prototype.
+static const JSFunctionSpec wrap_for_valid_iterator_methods[] = {
+    JS_SELF_HOSTED_FN("next", "WrapForValidIteratorNext", 1, 0),
+    JS_SELF_HOSTED_FN("return", "WrapForValidIteratorReturn", 1, 0),
+    JS_SELF_HOSTED_FN("throw", "WrapForValidIteratorThrow", 1, 0),
+    JS_FS_END,
+};
+
+static const JSClass WrapForValidIteratorPrototypeClass = {
+    "Wrap For Valid Iterator", 0};
+
+const JSClass WrapForValidIteratorObject::class_ = {
+    "Wrap For Valid Iterator",
+    JSCLASS_HAS_RESERVED_SLOTS(WrapForValidIteratorObject::SlotCount),
+};
+
+/* static */
+bool GlobalObject::initWrapForValidIteratorProto(JSContext* cx,
+                                                 Handle<GlobalObject*> global) {
+  if (global->getReservedSlot(WRAP_FOR_VALID_ITERATOR_PROTO).isObject()) {
+    return true;
+  }
+
+  RootedObject iteratorProto(
+      cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
+  if (!iteratorProto) {
+    return false;
+  }
+
+  const JSClass* cls = &WrapForValidIteratorPrototypeClass;
+  RootedObject proto(
+      cx, GlobalObject::createBlankPrototypeInheriting(cx, cls, iteratorProto));
+  if (!proto || !DefinePropertiesAndFunctions(
+                    cx, proto, nullptr, wrap_for_valid_iterator_methods)) {
+    return false;
+  }
+
+  global->setReservedSlot(WRAP_FOR_VALID_ITERATOR_PROTO, ObjectValue(*proto));
+  return true;
+}
+
+WrapForValidIteratorObject* js::NewWrapForValidIterator(JSContext* cx) {
+  RootedObject proto(cx, GlobalObject::getOrCreateWrapForValidIteratorPrototype(
+                             cx, cx->global()));
+  if (!proto) {
+    return nullptr;
+  }
+
+  return NewObjectWithGivenProto<WrapForValidIteratorObject>(cx, proto);
+}
--- a/js/src/vm/Iteration.h
+++ b/js/src/vm/Iteration.h
@@ -9,16 +9,17 @@
 
 /*
  * JavaScript iterators.
  */
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MemoryReporting.h"
 
+#include "builtin/SelfHostingDefines.h"
 #include "gc/Barrier.h"
 #include "vm/ReceiverGuard.h"
 #include "vm/Stack.h"
 
 namespace js {
 
 class PlainObject;
 class PropertyIteratorObject;
@@ -437,11 +438,28 @@ enum class IteratorKind { Sync, Async };
  * Iterator Helpers proposal 2.1.3.
  */
 class IteratorObject : public NativeObject {
  public:
   static const JSClass class_;
   static const JSClass protoClass_;
 };
 
+/*
+ * Wrapper for iterators created via Iterator.from.
+ * Iterator Helpers proposal 2.1.3.3.1.1.
+ */
+class WrapForValidIteratorObject : public NativeObject {
+ public:
+  static const JSClass class_;
+
+  enum { IteratedSlot, SlotCount };
+
+  static_assert(
+      IteratedSlot == ITERATED_SLOT,
+      "IteratedSlot must match self-hosting define for iterated object slot.");
+};
+
+WrapForValidIteratorObject* NewWrapForValidIterator(JSContext* cx);
+
 } /* namespace js */
 
 #endif /* vm_Iteration_h */
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2055,16 +2055,30 @@ static bool intrinsic_ToBigInt(JSContext
   BigInt* res = ToBigInt(cx, args[0]);
   if (!res) {
     return false;
   }
   args.rval().setBigInt(res);
   return true;
 }
 
+static bool intrinsic_NewWrapForValidIterator(JSContext* cx, unsigned argc,
+                                              Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  MOZ_ASSERT(args.length() == 0);
+
+  JSObject* obj = NewWrapForValidIterator(cx);
+  if (!obj) {
+    return false;
+  }
+
+  args.rval().setObject(*obj);
+  return true;
+}
+
 // The self-hosting global isn't initialized with the normal set of builtins.
 // Instead, individual C++-implemented functions that're required by
 // self-hosted code are defined as global functions. Accessing these
 // functions via a content compartment's builtins would be unsafe, because
 // content script might have changed the builtins' prototypes' members.
 // Installing the whole set of builtins in the self-hosting compartment, OTOH,
 // would be wasteful: it increases memory usage and initialization time for
 // self-hosting compartment.
@@ -2204,16 +2218,19 @@ static const JSFunctionSpec intrinsic_fu
                     intrinsic_GuardToBuiltin<SetIteratorObject>, 1, 0,
                     IntrinsicGuardToSetIterator),
     JS_INLINABLE_FN("GuardToStringIterator",
                     intrinsic_GuardToBuiltin<StringIteratorObject>, 1, 0,
                     IntrinsicGuardToStringIterator),
     JS_INLINABLE_FN("GuardToRegExpStringIterator",
                     intrinsic_GuardToBuiltin<RegExpStringIteratorObject>, 1, 0,
                     IntrinsicGuardToRegExpStringIterator),
+    JS_INLINABLE_FN("GuardToWrapForValidIterator",
+                    intrinsic_GuardToBuiltin<WrapForValidIteratorObject>, 1, 0,
+                    IntrinsicGuardToWrapForValidIterator),
 
     JS_FN("_CreateMapIterationResultPair",
           intrinsic_CreateMapIterationResultPair, 0, 0),
     JS_INLINABLE_FN("_GetNextMapEntryForIterator",
                     intrinsic_GetNextMapEntryForIterator, 2, 0,
                     IntrinsicGetNextMapEntryForIterator),
     JS_FN("CallMapIteratorMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<MapIteratorObject>>, 2, 0),
@@ -2511,16 +2528,18 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4,
           0),
     JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0),
 
     JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0),
 
     JS_FN("ToBigInt", intrinsic_ToBigInt, 1, 0),
 
+    JS_FN("NewWrapForValidIterator", intrinsic_NewWrapForValidIterator, 0, 0),
+
     JS_FS_END};
 
 void js::FillSelfHostingCompileOptions(CompileOptions& options) {
   /*
    * In self-hosting mode, scripts use JSOp::GetIntrinsic instead of
    * JSOp::GetName or JSOp::GetGName to access unbound variables.
    * JSOp::GetIntrinsic does a name lookup on a special object, whose
    * properties are filled in lazily upon first access for a given global.