Commit 163b5d70 authored by Michael Achenbach's avatar Michael Achenbach Committed by Commit Bot

Revert "[esnext] load `iterator.next` only once at beginning of iteration"

This reverts commit bf4cc9ee.

Reason for revert: Breaks windows with msvc and linux with gcc
https://build.chromium.org/p/client.v8/builders/V8%20Win64%20-%20msvc/builds/841
https://build.chromium.org/p/client.v8/builders/V8%20Linux%20gcc%204.8/builds/17265

Original change's description:
> [esnext] load `iterator.next` only once at beginning of iteration
> 
> https://github.com/tc39/ecma262/pull/988 gained concensus during the
> september 2017 TC39 meetings. This moves the load of the "next" method
> to the very beginning of the iteration protocol, rather than during
> each iteration step.
> 
> This impacts:
> 
> - yield*
> - for-of loops
> - spread arguments
> - array spreads
> 
> In the v8 implementation, this also affects async iteration versions of
> these things (the sole exception being the Async-From-Sync iterator,
> which requires a few more changes to work with this, likely done in a
> followup patch).
> 
> This change introduces a new AST node, ResolvedProperty, which can be used
> as a callee by Call nodes to produce the same bytecode as Property calls,
> without observably re-loading the property. This is used in several
> AST-desugarings involving the iteration protocol.
> 
> BUG=v8:6861, v8:5699
> R=​rmcilroy@chromium.org, neis@chromium.org, adamk@chromium.org
> 
> Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng
> Change-Id: Ib81106a0182687fc5efea0bc32302ad06376773b
> Reviewed-on: https://chromium-review.googlesource.com/687997
> Commit-Queue: Caitlin Potter <caitp@igalia.com>
> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
> Reviewed-by: Adam Klein <adamk@chromium.org>
> Reviewed-by: Georg Neis <neis@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#50452}

TBR=rmcilroy@chromium.org,adamk@chromium.org,neis@chromium.org,caitp@igalia.com,caitp@chromium.org

Change-Id: I1797c0d596dfd6850d6f0f505f591a7a990dd1f1
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: v8:6861, v8:5699
Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng
Reviewed-on: https://chromium-review.googlesource.com/857616Reviewed-by: 's avatarMichael Achenbach <machenbach@chromium.org>
Commit-Queue: Michael Achenbach <machenbach@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50454}
parent 5c56e27d
...@@ -195,11 +195,6 @@ void AstNumberingVisitor::VisitProperty(Property* node) { ...@@ -195,11 +195,6 @@ void AstNumberingVisitor::VisitProperty(Property* node) {
Visit(node->obj()); Visit(node->obj());
} }
void AstNumberingVisitor::VisitResolvedProperty(ResolvedProperty* node) {
Visit(node->object());
Visit(node->property());
}
void AstNumberingVisitor::VisitAssignment(Assignment* node) { void AstNumberingVisitor::VisitAssignment(Assignment* node) {
Visit(node->target()); Visit(node->target());
Visit(node->value()); Visit(node->value());
...@@ -256,7 +251,6 @@ void AstNumberingVisitor::VisitForInStatement(ForInStatement* node) { ...@@ -256,7 +251,6 @@ void AstNumberingVisitor::VisitForInStatement(ForInStatement* node) {
void AstNumberingVisitor::VisitForOfStatement(ForOfStatement* node) { void AstNumberingVisitor::VisitForOfStatement(ForOfStatement* node) {
Visit(node->assign_iterator()); // Not part of loop. Visit(node->assign_iterator()); // Not part of loop.
Visit(node->assign_next());
node->set_first_suspend_id(suspend_count_); node->set_first_suspend_id(suspend_count_);
Visit(node->next_result()); Visit(node->next_result());
Visit(node->result_done()); Visit(node->result_done());
......
...@@ -392,14 +392,6 @@ void AstTraversalVisitor<Subclass>::VisitProperty(Property* expr) { ...@@ -392,14 +392,6 @@ void AstTraversalVisitor<Subclass>::VisitProperty(Property* expr) {
RECURSE_EXPRESSION(Visit(expr->key())); RECURSE_EXPRESSION(Visit(expr->key()));
} }
template <class Subclass>
void AstTraversalVisitor<Subclass>::VisitResolvedProperty(
ResolvedProperty* expr) {
PROCESS_EXPRESSION(expr);
RECURSE_EXPRESSION(VisitVariableProxy(expr->object()));
RECURSE_EXPRESSION(VisitVariableProxy(expr->property()));
}
template <class Subclass> template <class Subclass>
void AstTraversalVisitor<Subclass>::VisitCall(Call* expr) { void AstTraversalVisitor<Subclass>::VisitCall(Call* expr) {
PROCESS_EXPRESSION(expr); PROCESS_EXPRESSION(expr);
......
...@@ -805,10 +805,6 @@ Call::CallType Call::GetCallType() const { ...@@ -805,10 +805,6 @@ Call::CallType Call::GetCallType() const {
} }
} }
if (expression()->IsResolvedProperty()) {
return RESOLVED_PROPERTY_CALL;
}
return OTHER_CALL; return OTHER_CALL;
} }
......
...@@ -94,7 +94,6 @@ namespace internal { ...@@ -94,7 +94,6 @@ namespace internal {
V(Literal) \ V(Literal) \
V(NativeFunctionLiteral) \ V(NativeFunctionLiteral) \
V(Property) \ V(Property) \
V(ResolvedProperty) \
V(RewritableExpression) \ V(RewritableExpression) \
V(Spread) \ V(Spread) \
V(SuperCallReference) \ V(SuperCallReference) \
...@@ -591,13 +590,11 @@ class ForInStatement final : public ForEachStatement { ...@@ -591,13 +590,11 @@ class ForInStatement final : public ForEachStatement {
class ForOfStatement final : public ForEachStatement { class ForOfStatement final : public ForEachStatement {
public: public:
void Initialize(Statement* body, Variable* iterator, void Initialize(Statement* body, Variable* iterator,
Expression* assign_iterator, Expression* assign_next, Expression* assign_iterator, Expression* next_result,
Expression* next_result, Expression* result_done, Expression* result_done, Expression* assign_each) {
Expression* assign_each) {
ForEachStatement::Initialize(body); ForEachStatement::Initialize(body);
iterator_ = iterator; iterator_ = iterator;
assign_iterator_ = assign_iterator; assign_iterator_ = assign_iterator;
assign_next_ = assign_next;
next_result_ = next_result; next_result_ = next_result;
result_done_ = result_done; result_done_ = result_done;
assign_each_ = assign_each; assign_each_ = assign_each;
...@@ -612,9 +609,6 @@ class ForOfStatement final : public ForEachStatement { ...@@ -612,9 +609,6 @@ class ForOfStatement final : public ForEachStatement {
return assign_iterator_; return assign_iterator_;
} }
// iteratorRecord.next = iterator.next
Expression* assign_next() const { return assign_next_; }
// result = iterator.next() // with type check // result = iterator.next() // with type check
Expression* next_result() const { Expression* next_result() const {
return next_result_; return next_result_;
...@@ -630,12 +624,6 @@ class ForOfStatement final : public ForEachStatement { ...@@ -630,12 +624,6 @@ class ForOfStatement final : public ForEachStatement {
return assign_each_; return assign_each_;
} }
void set_assign_iterator(Expression* e) { assign_iterator_ = e; }
void set_assign_next(Expression* e) { assign_next_ = e; }
void set_next_result(Expression* e) { next_result_ = e; }
void set_result_done(Expression* e) { result_done_ = e; }
void set_assign_each(Expression* e) { assign_each_ = e; }
private: private:
friend class AstNodeFactory; friend class AstNodeFactory;
...@@ -649,7 +637,6 @@ class ForOfStatement final : public ForEachStatement { ...@@ -649,7 +637,6 @@ class ForOfStatement final : public ForEachStatement {
Variable* iterator_; Variable* iterator_;
Expression* assign_iterator_; Expression* assign_iterator_;
Expression* assign_next_;
Expression* next_result_; Expression* next_result_;
Expression* result_done_; Expression* result_done_;
Expression* assign_each_; Expression* assign_each_;
...@@ -1620,25 +1607,6 @@ class Property final : public Expression { ...@@ -1620,25 +1607,6 @@ class Property final : public Expression {
Expression* key_; Expression* key_;
}; };
// ResolvedProperty pairs a receiver field with a value field. It allows Call
// to support arbitrary receivers while still taking advantage of TypeFeedback.
class ResolvedProperty final : public Expression {
public:
VariableProxy* object() const { return object_; }
VariableProxy* property() const { return property_; }
void set_object(VariableProxy* e) { object_ = e; }
void set_property(VariableProxy* e) { property_ = e; }
private:
friend class AstNodeFactory;
ResolvedProperty(VariableProxy* obj, VariableProxy* property, int pos)
: Expression(pos, kResolvedProperty), object_(obj), property_(property) {}
VariableProxy* object_;
VariableProxy* property_;
};
class Call final : public Expression { class Call final : public Expression {
public: public:
...@@ -1665,7 +1633,6 @@ class Call final : public Expression { ...@@ -1665,7 +1633,6 @@ class Call final : public Expression {
NAMED_SUPER_PROPERTY_CALL, NAMED_SUPER_PROPERTY_CALL,
KEYED_SUPER_PROPERTY_CALL, KEYED_SUPER_PROPERTY_CALL,
SUPER_CALL, SUPER_CALL,
RESOLVED_PROPERTY_CALL,
OTHER_CALL OTHER_CALL
}; };
...@@ -2137,6 +2104,7 @@ class YieldStar final : public Suspend { ...@@ -2137,6 +2104,7 @@ class YieldStar final : public Suspend {
// - One for awaiting the iterator result yielded by the delegated iterator // - One for awaiting the iterator result yielded by the delegated iterator
// (await_delegated_iterator_output_suspend_id) // (await_delegated_iterator_output_suspend_id)
int await_iterator_close_suspend_id() const { int await_iterator_close_suspend_id() const {
DCHECK_NE(-1, await_iterator_close_suspend_id_);
return await_iterator_close_suspend_id_; return await_iterator_close_suspend_id_;
} }
void set_await_iterator_close_suspend_id(int id) { void set_await_iterator_close_suspend_id(int id) {
...@@ -2144,6 +2112,7 @@ class YieldStar final : public Suspend { ...@@ -2144,6 +2112,7 @@ class YieldStar final : public Suspend {
} }
int await_delegated_iterator_output_suspend_id() const { int await_delegated_iterator_output_suspend_id() const {
DCHECK_NE(-1, await_delegated_iterator_output_suspend_id_);
return await_delegated_iterator_output_suspend_id_; return await_delegated_iterator_output_suspend_id_;
} }
void set_await_delegated_iterator_output_suspend_id(int id) { void set_await_delegated_iterator_output_suspend_id(int id) {
...@@ -3028,12 +2997,6 @@ class AstNodeFactory final BASE_EMBEDDED { ...@@ -3028,12 +2997,6 @@ class AstNodeFactory final BASE_EMBEDDED {
return new (zone_) Property(obj, key, pos); return new (zone_) Property(obj, key, pos);
} }
ResolvedProperty* NewResolvedProperty(VariableProxy* obj,
VariableProxy* property,
int pos = kNoSourcePosition) {
return new (zone_) ResolvedProperty(obj, property, pos);
}
Call* NewCall(Expression* expression, ZoneList<Expression*>* arguments, Call* NewCall(Expression* expression, ZoneList<Expression*>* arguments,
int pos, Call::PossiblyEval possibly_eval = Call::NOT_EVAL) { int pos, Call::PossiblyEval possibly_eval = Call::NOT_EVAL) {
return new (zone_) Call(expression, arguments, pos, possibly_eval); return new (zone_) Call(expression, arguments, pos, possibly_eval);
......
...@@ -326,7 +326,6 @@ void CallPrinter::VisitProperty(Property* node) { ...@@ -326,7 +326,6 @@ void CallPrinter::VisitProperty(Property* node) {
} }
} }
void CallPrinter::VisitResolvedProperty(ResolvedProperty* node) {}
void CallPrinter::VisitCall(Call* node) { void CallPrinter::VisitCall(Call* node) {
bool was_found = false; bool was_found = false;
...@@ -1250,14 +1249,6 @@ void AstPrinter::VisitProperty(Property* node) { ...@@ -1250,14 +1249,6 @@ void AstPrinter::VisitProperty(Property* node) {
} }
} }
void AstPrinter::VisitResolvedProperty(ResolvedProperty* node) {
EmbeddedVector<char, 128> buf;
SNPrintF(buf, "RESOLVED-PROPERTY");
IndentedScope indent(this, buf.start(), node->position());
PrintIndentedVisit("RECEIVER", node->object());
PrintIndentedVisit("PROPERTY", node->property());
}
void AstPrinter::VisitCall(Call* node) { void AstPrinter::VisitCall(Call* node) {
EmbeddedVector<char, 128> buf; EmbeddedVector<char, 128> buf;
......
...@@ -232,9 +232,10 @@ void BaseCollectionsAssembler::AddConstructorEntriesFromIterable( ...@@ -232,9 +232,10 @@ void BaseCollectionsAssembler::AddConstructorEntriesFromIterable(
TNode<Object> add_func = GetAddFunction(variant, context, collection); TNode<Object> add_func = GetAddFunction(variant, context, collection);
IteratorBuiltinsAssembler iterator_assembler(this->state()); IteratorBuiltinsAssembler iterator_assembler(this->state());
IteratorRecord iterator = iterator_assembler.GetIterator(context, iterable); TNode<Object> iterator =
CAST(iterator_assembler.GetIterator(context, iterable));
CSA_ASSERT(this, Word32BinaryNot(IsUndefined(iterator.object))); CSA_ASSERT(this, Word32BinaryNot(IsUndefined(iterator)));
TNode<Object> fast_iterator_result_map = TNode<Object> fast_iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX); LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
......
...@@ -11,10 +11,9 @@ namespace internal { ...@@ -11,10 +11,9 @@ namespace internal {
using compiler::Node; using compiler::Node;
IteratorRecord IteratorBuiltinsAssembler::GetIterator(Node* context, Node* IteratorBuiltinsAssembler::GetIterator(Node* context, Node* object,
Node* object, Label* if_exception,
Label* if_exception, Variable* exception) {
Variable* exception) {
Node* method = GetProperty(context, object, factory()->iterator_symbol()); Node* method = GetProperty(context, object, factory()->iterator_symbol());
GotoIfException(method, if_exception, exception); GotoIfException(method, if_exception, exception);
...@@ -22,9 +21,9 @@ IteratorRecord IteratorBuiltinsAssembler::GetIterator(Node* context, ...@@ -22,9 +21,9 @@ IteratorRecord IteratorBuiltinsAssembler::GetIterator(Node* context,
Node* iterator = CallJS(callable, context, method, object); Node* iterator = CallJS(callable, context, method, object);
GotoIfException(iterator, if_exception, exception); GotoIfException(iterator, if_exception, exception);
Label get_next(this), if_notobject(this, Label::kDeferred); Label done(this), if_notobject(this, Label::kDeferred);
GotoIf(TaggedIsSmi(iterator), &if_notobject); GotoIf(TaggedIsSmi(iterator), &if_notobject);
Branch(IsJSReceiver(iterator), &get_next, &if_notobject); Branch(IsJSReceiver(iterator), &done, &if_notobject);
BIND(&if_notobject); BIND(&if_notobject);
{ {
...@@ -35,21 +34,24 @@ IteratorRecord IteratorBuiltinsAssembler::GetIterator(Node* context, ...@@ -35,21 +34,24 @@ IteratorRecord IteratorBuiltinsAssembler::GetIterator(Node* context,
Unreachable(); Unreachable();
} }
BIND(&get_next); BIND(&done);
Node* const next = GetProperty(context, iterator, factory()->next_string()); return iterator;
GotoIfException(next, if_exception, exception);
return IteratorRecord{TNode<JSReceiver>::UncheckedCast(iterator),
TNode<Object>::UncheckedCast(next)};
} }
Node* IteratorBuiltinsAssembler::IteratorStep( Node* IteratorBuiltinsAssembler::IteratorStep(Node* context, Node* iterator,
Node* context, const IteratorRecord& iterator, Label* if_done, Label* if_done,
Node* fast_iterator_result_map, Label* if_exception, Variable* exception) { Node* fast_iterator_result_map,
Label* if_exception,
Variable* exception) {
DCHECK_NOT_NULL(if_done); DCHECK_NOT_NULL(if_done);
// IteratorNext
Node* next_method = GetProperty(context, iterator, factory()->next_string());
GotoIfException(next_method, if_exception, exception);
// 1. a. Let result be ? Invoke(iterator, "next", « »). // 1. a. Let result be ? Invoke(iterator, "next", « »).
Callable callable = CodeFactory::Call(isolate()); Callable callable = CodeFactory::Call(isolate());
Node* result = CallJS(callable, context, iterator.next, iterator.object); Node* result = CallJS(callable, context, next_method, iterator);
GotoIfException(result, if_exception, exception); GotoIfException(result, if_exception, exception);
// 3. If Type(result) is not Object, throw a TypeError exception. // 3. If Type(result) is not Object, throw a TypeError exception.
...@@ -127,20 +129,20 @@ Node* IteratorBuiltinsAssembler::IteratorValue(Node* context, Node* result, ...@@ -127,20 +129,20 @@ Node* IteratorBuiltinsAssembler::IteratorValue(Node* context, Node* result,
return var_value.value(); return var_value.value();
} }
void IteratorBuiltinsAssembler::IteratorCloseOnException( void IteratorBuiltinsAssembler::IteratorCloseOnException(Node* context,
Node* context, const IteratorRecord& iterator, Label* if_exception, Node* iterator,
Variable* exception) { Label* if_exception,
Variable* exception) {
// Perform ES #sec-iteratorclose when an exception occurs. This simpler // Perform ES #sec-iteratorclose when an exception occurs. This simpler
// algorithm does not include redundant steps which are never reachable from // algorithm does not include redundant steps which are never reachable from
// the spec IteratorClose algorithm. // the spec IteratorClose algorithm.
DCHECK_NOT_NULL(if_exception); DCHECK_NOT_NULL(if_exception);
DCHECK_NOT_NULL(exception); DCHECK_NOT_NULL(exception);
CSA_ASSERT(this, IsNotTheHole(exception->value())); CSA_ASSERT(this, IsNotTheHole(exception->value()));
CSA_ASSERT(this, IsJSReceiver(iterator.object)); CSA_ASSERT(this, IsJSReceiver(iterator));
// Let return be ? GetMethod(iterator, "return"). // Let return be ? GetMethod(iterator, "return").
Node* method = Node* method = GetProperty(context, iterator, factory()->return_string());
GetProperty(context, iterator.object, factory()->return_string());
GotoIfException(method, if_exception, exception); GotoIfException(method, if_exception, exception);
// If return is undefined, return Completion(completion). // If return is undefined, return Completion(completion).
...@@ -150,7 +152,7 @@ void IteratorBuiltinsAssembler::IteratorCloseOnException( ...@@ -150,7 +152,7 @@ void IteratorBuiltinsAssembler::IteratorCloseOnException(
// Let innerResult be Call(return, iterator, « »). // Let innerResult be Call(return, iterator, « »).
// If an exception occurs, the original exception remains bound // If an exception occurs, the original exception remains bound
Node* inner_result = Node* inner_result =
CallJS(CodeFactory::Call(isolate()), context, method, iterator.object); CallJS(CodeFactory::Call(isolate()), context, method, iterator);
GotoIfException(inner_result, if_exception, nullptr); GotoIfException(inner_result, if_exception, nullptr);
// (If completion.[[Type]] is throw) return Completion(completion). // (If completion.[[Type]] is throw) return Completion(completion).
...@@ -158,8 +160,9 @@ void IteratorBuiltinsAssembler::IteratorCloseOnException( ...@@ -158,8 +160,9 @@ void IteratorBuiltinsAssembler::IteratorCloseOnException(
} }
} }
void IteratorBuiltinsAssembler::IteratorCloseOnException( void IteratorBuiltinsAssembler::IteratorCloseOnException(Node* context,
Node* context, const IteratorRecord& iterator, Variable* exception) { Node* iterator,
Variable* exception) {
Label rethrow(this, Label::kDeferred); Label rethrow(this, Label::kDeferred);
IteratorCloseOnException(context, iterator, &rethrow, exception); IteratorCloseOnException(context, iterator, &rethrow, exception);
......
...@@ -19,17 +19,16 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler { ...@@ -19,17 +19,16 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler {
// https://tc39.github.io/ecma262/#sec-getiterator --- never used for // https://tc39.github.io/ecma262/#sec-getiterator --- never used for
// @@asyncIterator. // @@asyncIterator.
IteratorRecord GetIterator(Node* context, Node* object, Node* GetIterator(Node* context, Node* object, Label* if_exception = nullptr,
Label* if_exception = nullptr, Variable* exception = nullptr);
Variable* exception = nullptr);
// https://tc39.github.io/ecma262/#sec-iteratorstep // https://tc39.github.io/ecma262/#sec-iteratorstep
// Returns `false` if the iterator is done, otherwise returns an // Returns `false` if the iterator is done, otherwise returns an
// iterator result. // iterator result.
// `fast_iterator_result_map` refers to the map for the JSIteratorResult // `fast_iterator_result_map` refers to the map for the JSIteratorResult
// object, loaded from the native context. // object, loaded from the native context.
Node* IteratorStep(Node* context, const IteratorRecord& iterator, Node* IteratorStep(Node* context, Node* iterator, Label* if_done,
Label* if_done, Node* fast_iterator_result_map = nullptr, Node* fast_iterator_result_map = nullptr,
Label* if_exception = nullptr, Label* if_exception = nullptr,
Variable* exception = nullptr); Variable* exception = nullptr);
...@@ -43,9 +42,9 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler { ...@@ -43,9 +42,9 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler {
Variable* exception = nullptr); Variable* exception = nullptr);
// https://tc39.github.io/ecma262/#sec-iteratorclose // https://tc39.github.io/ecma262/#sec-iteratorclose
void IteratorCloseOnException(Node* context, const IteratorRecord& iterator, void IteratorCloseOnException(Node* context, Node* iterator,
Label* if_exception, Variable* exception); Label* if_exception, Variable* exception);
void IteratorCloseOnException(Node* context, const IteratorRecord& iterator, void IteratorCloseOnException(Node* context, Node* iterator,
Variable* exception); Variable* exception);
}; };
......
...@@ -1807,9 +1807,8 @@ TF_BUILTIN(PerformNativePromiseThen, PromiseBuiltinsAssembler) { ...@@ -1807,9 +1807,8 @@ TF_BUILTIN(PerformNativePromiseThen, PromiseBuiltinsAssembler) {
} }
Node* PromiseBuiltinsAssembler::PerformPromiseAll( Node* PromiseBuiltinsAssembler::PerformPromiseAll(
Node* context, Node* constructor, Node* capability, Node* context, Node* constructor, Node* capability, Node* iterator,
const IteratorRecord& iterator, Label* if_exception, Label* if_exception, Variable* var_exception) {
Variable* var_exception) {
IteratorBuiltinsAssembler iter_assembler(state()); IteratorBuiltinsAssembler iter_assembler(state());
Label close_iterator(this); Label close_iterator(this);
...@@ -2015,7 +2014,7 @@ TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler) { ...@@ -2015,7 +2014,7 @@ TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler) {
// Let iterator be GetIterator(iterable). // Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability). // IfAbruptRejectPromise(iterator, promiseCapability).
Node* const iterable = Parameter(Descriptor::kIterable); Node* const iterable = Parameter(Descriptor::kIterable);
IteratorRecord iterator = iter_assembler.GetIterator( Node* const iterator = iter_assembler.GetIterator(
context, iterable, &reject_promise, &var_exception); context, iterable, &reject_promise, &var_exception);
// Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability). // Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability).
...@@ -2152,7 +2151,7 @@ TF_BUILTIN(PromiseRace, PromiseBuiltinsAssembler) { ...@@ -2152,7 +2151,7 @@ TF_BUILTIN(PromiseRace, PromiseBuiltinsAssembler) {
// Let iterator be GetIterator(iterable). // Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability). // IfAbruptRejectPromise(iterator, promiseCapability).
Node* const iterable = Parameter(Descriptor::kIterable); Node* const iterable = Parameter(Descriptor::kIterable);
IteratorRecord iterator = iter_assembler.GetIterator( Node* const iterator = iter_assembler.GetIterator(
context, iterable, &reject_promise, &var_exception); context, iterable, &reject_promise, &var_exception);
// Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability). // Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability).
......
...@@ -157,7 +157,7 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -157,7 +157,7 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* CreateThrowerFunction(Node* reason, Node* native_context); Node* CreateThrowerFunction(Node* reason, Node* native_context);
Node* PerformPromiseAll(Node* context, Node* constructor, Node* capability, Node* PerformPromiseAll(Node* context, Node* constructor, Node* capability,
const IteratorRecord& record, Label* if_exception, Node* iterator, Label* if_exception,
Variable* var_exception); Variable* var_exception);
Node* IncrementSmiCell(Node* cell, Label* if_overflow = nullptr); Node* IncrementSmiCell(Node* cell, Label* if_overflow = nullptr);
......
...@@ -71,17 +71,6 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol }; ...@@ -71,17 +71,6 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol };
promise_default_resolve_handler_symbol, \ promise_default_resolve_handler_symbol, \
PromiseDefaultResolveHandlerSymbol) PromiseDefaultResolveHandlerSymbol)
// Returned from IteratorBuiltinsAssembler::GetIterator(). Struct is declared
// here to simplify use in other generated builtins.
struct IteratorRecord {
public:
// iteratorRecord.[[Iterator]]
compiler::TNode<JSReceiver> object;
// iteratorRecord.[[NextMethod]]
compiler::TNode<Object> next;
};
// Provides JavaScript-specific "macro-assembler" functionality on top of the // Provides JavaScript-specific "macro-assembler" functionality on top of the
// CodeAssembler. By factoring the JavaScript-isms out of the CodeAssembler, // CodeAssembler. By factoring the JavaScript-isms out of the CodeAssembler,
// it's possible to add JavaScript-specific useful CodeAssembler "macros" // it's possible to add JavaScript-specific useful CodeAssembler "macros"
......
This diff is collapsed.
...@@ -151,24 +151,9 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> { ...@@ -151,24 +151,9 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
void BuildAwait(int suspend_id); void BuildAwait(int suspend_id);
void BuildGetIterator(Expression* iterable, IteratorType hint); void BuildGetIterator(Expression* iterable, IteratorType hint);
// Create an IteratorRecord with pre-allocated registers holding the next
// method and iterator object.
IteratorRecord BuildGetIteratorRecord(Expression* iterable,
Register iterator_next,
Register iterator_object,
IteratorType hint);
// Create an IteratorRecord allocating new registers to hold the next method
// and iterator object.
IteratorRecord BuildGetIteratorRecord(Expression* iterable, IteratorRecord BuildGetIteratorRecord(Expression* iterable,
IteratorType hint); IteratorType hint);
void BuildIteratorNext(const IteratorRecord& iterator, Register next_result); void BuildIteratorNext(const IteratorRecord& iterator, Register next_result);
void BuildIteratorClose(const IteratorRecord& iterator, int suspend_id = -1);
void BuildCallIteratorMethod(Register iterator, const AstRawString* method,
RegisterList receiver_and_args,
BytecodeLabel* if_called,
BytecodeLabels* if_notcalled);
void BuildArrayLiteralSpread(Spread* spread, Register array); void BuildArrayLiteralSpread(Spread* spread, Register array);
......
...@@ -1889,11 +1889,13 @@ void Parser::DeclareFunctionNameVar(const AstRawString* function_name, ...@@ -1889,11 +1889,13 @@ void Parser::DeclareFunctionNameVar(const AstRawString* function_name,
// !%_IsJSReceiver(result = Await(iterator.next())) && // !%_IsJSReceiver(result = Await(iterator.next())) &&
// %ThrowIteratorResultNotAnObject(result) // %ThrowIteratorResultNotAnObject(result)
// [endif] // [endif]
Expression* Parser::BuildIteratorNextResult(VariableProxy* iterator, Expression* Parser::BuildIteratorNextResult(Expression* iterator,
VariableProxy* next,
Variable* result, IteratorType type, Variable* result, IteratorType type,
int pos) { int pos) {
Expression* next_property = factory()->NewResolvedProperty(iterator, next); Expression* next_literal = factory()->NewStringLiteral(
ast_value_factory()->next_string(), kNoSourcePosition);
Expression* next_property =
factory()->NewProperty(iterator, next_literal, kNoSourcePosition);
ZoneList<Expression*>* next_arguments = ZoneList<Expression*>* next_arguments =
new (zone()) ZoneList<Expression*>(0, zone()); new (zone()) ZoneList<Expression*>(0, zone());
Expression* next_call = Expression* next_call =
...@@ -2096,7 +2098,6 @@ Statement* Parser::InitializeForOfStatement( ...@@ -2096,7 +2098,6 @@ Statement* Parser::InitializeForOfStatement(
auto avfactory = ast_value_factory(); auto avfactory = ast_value_factory();
Variable* iterator = NewTemporary(avfactory->dot_iterator_string()); Variable* iterator = NewTemporary(avfactory->dot_iterator_string());
Variable* next = NewTemporary(avfactory->empty_string());
Variable* result = NewTemporary(avfactory->dot_result_string()); Variable* result = NewTemporary(avfactory->dot_result_string());
Variable* completion = NewTemporary(avfactory->empty_string()); Variable* completion = NewTemporary(avfactory->empty_string());
...@@ -2109,17 +2110,6 @@ Statement* Parser::InitializeForOfStatement( ...@@ -2109,17 +2110,6 @@ Statement* Parser::InitializeForOfStatement(
iterable->position()); iterable->position());
} }
Expression* assign_next;
{
assign_next = factory()->NewAssignment(
Token::ASSIGN, factory()->NewVariableProxy(next),
factory()->NewProperty(factory()->NewVariableProxy(iterator),
factory()->NewStringLiteral(
avfactory->next_string(), kNoSourcePosition),
kNoSourcePosition),
kNoSourcePosition);
}
// [if (IteratorType == kNormal)] // [if (IteratorType == kNormal)]
// !%_IsJSReceiver(result = iterator.next()) && // !%_IsJSReceiver(result = iterator.next()) &&
// %ThrowIteratorResultNotAnObject(result) // %ThrowIteratorResultNotAnObject(result)
...@@ -2129,10 +2119,9 @@ Statement* Parser::InitializeForOfStatement( ...@@ -2129,10 +2119,9 @@ Statement* Parser::InitializeForOfStatement(
// [endif] // [endif]
Expression* next_result; Expression* next_result;
{ {
VariableProxy* iterator_proxy = factory()->NewVariableProxy(iterator); Expression* iterator_proxy = factory()->NewVariableProxy(iterator);
VariableProxy* next_proxy = factory()->NewVariableProxy(next); next_result =
next_result = BuildIteratorNextResult(iterator_proxy, next_proxy, result, BuildIteratorNextResult(iterator_proxy, result, type, next_result_pos);
type, next_result_pos);
} }
// result.done // result.done
...@@ -2202,8 +2191,8 @@ Statement* Parser::InitializeForOfStatement( ...@@ -2202,8 +2191,8 @@ Statement* Parser::InitializeForOfStatement(
body = block; body = block;
} }
for_of->Initialize(body, iterator, assign_iterator, assign_next, next_result, for_of->Initialize(body, iterator, assign_iterator, next_result, result_done,
result_done, assign_each); assign_each);
return finalize ? FinalizeForOfStatement(for_of, completion, type, nopos) return finalize ? FinalizeForOfStatement(for_of, completion, type, nopos)
: for_of; : for_of;
} }
......
...@@ -402,14 +402,13 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) { ...@@ -402,14 +402,13 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
Expression* RewriteDestructuringAssignment(Assignment* assignment); Expression* RewriteDestructuringAssignment(Assignment* assignment);
// [if (IteratorType == kAsync)] // [if (IteratorType == kAsync)]
// !%_IsJSReceiver(result = Await(next.[[Call]](iterator, « »)) && // !%_IsJSReceiver(result = Await(iterator.next()) &&
// %ThrowIteratorResultNotAnObject(result) // %ThrowIteratorResultNotAnObject(result)
// [else] // [else]
// !%_IsJSReceiver(result = next.[[Call]](iterator, « »)) && // !%_IsJSReceiver(result = iterator.next()) &&
// %ThrowIteratorResultNotAnObject(result) // %ThrowIteratorResultNotAnObject(result)
// [endif] // [endif]
Expression* BuildIteratorNextResult(VariableProxy* iterator, Expression* BuildIteratorNextResult(Expression* iterator, Variable* result,
VariableProxy* next, Variable* result,
IteratorType type, int pos); IteratorType type, int pos);
// Initialize the components of a for-in / for-of statement. // Initialize the components of a for-in / for-of statement.
......
...@@ -445,11 +445,6 @@ void PatternRewriter::VisitArrayLiteral(ArrayLiteral* node, ...@@ -445,11 +445,6 @@ void PatternRewriter::VisitArrayLiteral(ArrayLiteral* node,
auto iterator = CreateTempVar(factory()->NewGetIterator( auto iterator = CreateTempVar(factory()->NewGetIterator(
factory()->NewVariableProxy(temp), current_value_, IteratorType::kNormal, factory()->NewVariableProxy(temp), current_value_, IteratorType::kNormal,
current_value_->position())); current_value_->position()));
auto next = CreateTempVar(factory()->NewProperty(
factory()->NewVariableProxy(iterator),
factory()->NewStringLiteral(ast_value_factory()->next_string(),
kNoSourcePosition),
kNoSourcePosition));
auto done = auto done =
CreateTempVar(factory()->NewBooleanLiteral(false, kNoSourcePosition)); CreateTempVar(factory()->NewBooleanLiteral(false, kNoSourcePosition));
auto result = CreateTempVar(); auto result = CreateTempVar();
...@@ -530,8 +525,7 @@ void PatternRewriter::VisitArrayLiteral(ArrayLiteral* node, ...@@ -530,8 +525,7 @@ void PatternRewriter::VisitArrayLiteral(ArrayLiteral* node,
next_block->statements()->Add( next_block->statements()->Add(
factory()->NewExpressionStatement( factory()->NewExpressionStatement(
parser_->BuildIteratorNextResult( parser_->BuildIteratorNextResult(
factory()->NewVariableProxy(iterator), factory()->NewVariableProxy(iterator), result,
factory()->NewVariableProxy(next), result,
IteratorType::kNormal, kNoSourcePosition), IteratorType::kNormal, kNoSourcePosition),
kNoSourcePosition), kNoSourcePosition),
zone()); zone());
...@@ -605,7 +599,6 @@ void PatternRewriter::VisitArrayLiteral(ArrayLiteral* node, ...@@ -605,7 +599,6 @@ void PatternRewriter::VisitArrayLiteral(ArrayLiteral* node,
// result = IteratorNext(iterator); // result = IteratorNext(iterator);
Statement* get_next = factory()->NewExpressionStatement( Statement* get_next = factory()->NewExpressionStatement(
parser_->BuildIteratorNextResult(factory()->NewVariableProxy(iterator), parser_->BuildIteratorNextResult(factory()->NewVariableProxy(iterator),
factory()->NewVariableProxy(next),
result, IteratorType::kNormal, nopos), result, IteratorType::kNormal, nopos),
nopos); nopos);
...@@ -763,7 +756,6 @@ NOT_A_PATTERN(ImportCallExpression) ...@@ -763,7 +756,6 @@ NOT_A_PATTERN(ImportCallExpression)
NOT_A_PATTERN(Literal) NOT_A_PATTERN(Literal)
NOT_A_PATTERN(NativeFunctionLiteral) NOT_A_PATTERN(NativeFunctionLiteral)
NOT_A_PATTERN(RegExpLiteral) NOT_A_PATTERN(RegExpLiteral)
NOT_A_PATTERN(ResolvedProperty)
NOT_A_PATTERN(ReturnStatement) NOT_A_PATTERN(ReturnStatement)
NOT_A_PATTERN(SloppyBlockFunctionStatement) NOT_A_PATTERN(SloppyBlockFunctionStatement)
NOT_A_PATTERN(Spread) NOT_A_PATTERN(Spread)
......
...@@ -9,10 +9,10 @@ function testFunction() { ...@@ -9,10 +9,10 @@ function testFunction() {
var arr = |_|[1]; var arr = |_|[1];
var all = |_|[]; var all = |_|[];
for (var |_|k in |_|arr) { all.|C|push(k); } for (var |_|k in |_|arr) { all.|C|push(k); }
for (var |C|k of |_|arr) { all.|C|push(k); } for (var |_|k of |_|arr) { all.|C|push(k); }
for (var |_|k in |_|obj) { all.|C|push(k); } for (var |_|k in |_|obj) { all.|C|push(k); }
for (let |_|k in |_|arr) { all.|C|push(k); } for (let |_|k in |_|arr) { all.|C|push(k); }
for (let |C|k of |_|arr) { all.|C|push(k); } for (let |_|k of |_|arr) { all.|C|push(k); }
for (let |_|k in |_|obj) { all.|C|push(k); } for (let |_|k in |_|obj) { all.|C|push(k); }
var iterable = |_|{ var iterable = |_|{
...@@ -28,9 +28,9 @@ function testFunction() { ...@@ -28,9 +28,9 @@ function testFunction() {
};|R| };|R|
} }
}; };
for (var |C|k of |_|iterable) { all.|C|push(k); } for (var |_|k of |_|iterable) { all.|C|push(k); }
|_|iterable.i = 0; |_|iterable.i = 0;
for (let |C|k of |_|iterable) { all.|C|push(k); } for (let |_|k of |_|iterable) { all.|C|push(k); }
|R|} |R|}
(anonymous) (expr.js:0:0) (anonymous) (expr.js:0:0)
......
...@@ -95,10 +95,10 @@ function testForLoop() { ...@@ -95,10 +95,10 @@ function testForLoop() {
|R|} |R|}
function testForOfLoop() { function testForOfLoop() {
for (var |C|k of |_|[]) {} for (var |_|k of |_|[]) {}
for (var |C|k of |_|[1]) |_|k; for (var |_|k of |_|[1]) |_|k;
var a = |_|[]; var a = |_|[];
for (var |C|k of |_|a) {} for (var |_|k of |_|a) {}
|R|} |R|}
function testForInLoop() { function testForInLoop() {
......
...@@ -37,8 +37,6 @@ ...@@ -37,8 +37,6 @@
var r2 = testMax(1, 2); var r2 = testMax(1, 2);
// .next() is only loaded once during the iteration prologue (see assertEquals(3, called);
// https://github.com/tc39/ecma262/pull/988/ and v8:6861)
assertEquals(1, called);
assertEquals(2, r2); assertEquals(2, r2);
})(); })();
...@@ -220,11 +220,13 @@ assertThrows('fold(sum, 0, unreachable({}))', TypeError); ...@@ -220,11 +220,13 @@ assertThrows('fold(sum, 0, unreachable({}))', TypeError);
assertThrows('fold(sum, 0, unreachable(false))', TypeError); assertThrows('fold(sum, 0, unreachable(false))', TypeError);
assertThrows('fold(sum, 0, unreachable(37))', TypeError); assertThrows('fold(sum, 0, unreachable(37))', TypeError);
// "next" is looked up only once during the iteration prologue (see // "next" is looked up each time.
// https://github.com/tc39/ecma262/pull/988) assertThrows('fold(sum, 0, remove_next_after(integers_until(10), 5))',
assertEquals(45, fold(sum, 0, remove_next_after(integers_until(10), 5))); TypeError);
// It is not called at any other time.
assertEquals(45, assertEquals(45,
fold(sum, 0, remove_next_after(integers_until(10), 10))); fold(sum, 0, remove_next_after(integers_until(10), 10)));
// It is not looked up too many times.
assertEquals(45, assertEquals(45,
fold(sum, 0, poison_next_after(integers_until(10), 10))); fold(sum, 0, poison_next_after(integers_until(10), 10)));
......
...@@ -376,11 +376,6 @@ testSpreadCallsStrict(); ...@@ -376,11 +376,6 @@ testSpreadCallsStrict();
a[3] = 4; a[3] = 4;
var called = 0; var called = 0;
// .next method is only accessed during iteration prologue (see
// https://github.com/tc39/ecma262/pull/988)
let ArrayIteratorPrototype = Array.prototype[Symbol.iterator]().__proto__;
let ArrayIteratorPrototypeNextDescriptor =
Object.getOwnPropertyDescriptor(ArrayIteratorPrototype, 'next');
Object.defineProperty(Array.prototype, 2, { Object.defineProperty(Array.prototype, 2, {
get: function() { get: function() {
var ai = a[Symbol.iterator](); var ai = a[Symbol.iterator]();
...@@ -389,8 +384,7 @@ testSpreadCallsStrict(); ...@@ -389,8 +384,7 @@ testSpreadCallsStrict();
get: function() { get: function() {
called++; called++;
return original_next; return original_next;
}, }
configurable: true
}); });
return 3; return 3;
}, },
...@@ -398,10 +392,8 @@ testSpreadCallsStrict(); ...@@ -398,10 +392,8 @@ testSpreadCallsStrict();
}); });
assertEquals(10, sum(...a)); assertEquals(10, sum(...a));
assertEquals(0, called); assertEquals(2, called);
Object.defineProperty(ArrayIteratorPrototype, 'next',
ArrayIteratorPrototypeNextDescriptor);
Object.defineProperty(Array.prototype, 2, {}); Object.defineProperty(Array.prototype, 2, {});
})(); })();
...@@ -438,9 +430,9 @@ testSpreadCallsStrict(); ...@@ -438,9 +430,9 @@ testSpreadCallsStrict();
countArgs(...a); countArgs(...a);
// .next method is only accessed during iteration prologue (see // should be called 4 times; 3 for the values, 1 for the final
// https://github.com/tc39/ecma262/pull/988) // {value: undefined, done: true} pair
assertEquals(1, called); assertEquals(4, called);
})(); })();
(function testArrayIteratorPrototypeModified() { (function testArrayIteratorPrototypeModified() {
......
...@@ -48,9 +48,7 @@ ...@@ -48,9 +48,7 @@
var r2 = testArgumentsPoint(1, 2); var r2 = testArgumentsPoint(1, 2);
// .next() is only loaded once during the iteration prologue (see assertEquals(3, called);
// https://github.com/tc39/ecma262/pull/988/ and v8:6861)
assertEquals(1, called);
assertInstanceof(r2, ArgumentsPoint); assertInstanceof(r2, ArgumentsPoint);
assertInstanceof(r2, Point); assertInstanceof(r2, Point);
assertEquals(r2.x, 1); assertEquals(r2.x, 1);
......
...@@ -341,30 +341,16 @@ function TestTypedArray(constr, elementSize, typicalElement) { ...@@ -341,30 +341,16 @@ function TestTypedArray(constr, elementSize, typicalElement) {
// Modified %ArrayIteratorPrototype%.next() method is honoured (v8:5699) // Modified %ArrayIteratorPrototype%.next() method is honoured (v8:5699)
const ArrayIteratorPrototype = Object.getPrototypeOf([][Symbol.iterator]()); const ArrayIteratorPrototype = Object.getPrototypeOf([][Symbol.iterator]());
const ArrayIteratorPrototypeNextDescriptor =
Object.getOwnPropertyDescriptor(ArrayIteratorPrototype, 'next');
const ArrayIteratorPrototypeNext = ArrayIteratorPrototype.next; const ArrayIteratorPrototypeNext = ArrayIteratorPrototype.next;
ArrayIteratorPrototype.next = function() { ArrayIteratorPrototype.next = function() {
return { done: true }; return { done: true };
}; };
genArr = new constr([1, 2, 3]); genArr = new constr([1, 2, 3]);
assertEquals(0, genArr.length); assertEquals(0, genArr.length);
ArrayIteratorPrototype.next = ArrayIteratorPrototypeNext; ArrayIteratorPrototype.next = ArrayIteratorPrototypeNext;
// Modified %ArrayIteratorPrototype%.next() is only loaded during the iterator // Modified %ArrayIteratorPrototype%.next() during iteration is honoured as
// prologue. // well.
let nextMethod = ArrayIteratorPrototypeNext;
let getNextCount = 0;
Object.defineProperty(ArrayIteratorPrototype, 'next', {
get() {
getNextCount++;
return nextMethod;
},
set(v) { nextMethod = v; },
configurable: true
});
genArr = new constr(Object.defineProperty([1, , 3], 1, { genArr = new constr(Object.defineProperty([1, , 3], 1, {
get() { get() {
ArrayIteratorPrototype.next = function() { ArrayIteratorPrototype.next = function() {
...@@ -373,13 +359,9 @@ function TestTypedArray(constr, elementSize, typicalElement) { ...@@ -373,13 +359,9 @@ function TestTypedArray(constr, elementSize, typicalElement) {
return 2; return 2;
} }
})); }));
Object.defineProperty(ArrayIteratorPrototype, 'next', assertEquals(2, genArr.length);
ArrayIteratorPrototypeNextDescriptor);
assertEquals(1, getNextCount);
assertEquals(3, genArr.length);
assertEquals(1, genArr[0]); assertEquals(1, genArr[0]);
assertEquals(2, genArr[1]); assertEquals(2, genArr[1]);
assertEquals(3, genArr[2]);
ArrayIteratorPrototype.next = ArrayIteratorPrototypeNext; ArrayIteratorPrototype.next = ArrayIteratorPrototypeNext;
} }
......
...@@ -423,8 +423,19 @@ ...@@ -423,8 +423,19 @@
'built-ins/Proxy/ownKeys/return-duplicate-symbol-entries-throws': [FAIL], 'built-ins/Proxy/ownKeys/return-duplicate-symbol-entries-throws': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=6861 # https://bugs.chromium.org/p/v8/issues/detail?id=6861
'language/statements/for-of/iterator-next-reference': [FAIL],
'language/expressions/async-generator/named-yield-star-async-next': [FAIL],
'language/expressions/async-generator/yield-star-async-next': [FAIL],
'language/expressions/class/async-gen-method-yield-star-async-next': [FAIL],
'language/expressions/class/async-gen-method-static-yield-star-async-next': [FAIL],
'language/expressions/object/method-definition/async-gen-yield-star-async-next': [FAIL],
'language/statements/async-generator/yield-star-async-next': [FAIL],
'language/statements/class/async-gen-method-yield-star-async-next': [FAIL],
'language/statements/class/async-gen-method-static-yield-star-async-next': [FAIL],
'language/expressions/object/method-definition/async-gen-yield-star-sync-next': [FAIL], 'language/expressions/object/method-definition/async-gen-yield-star-sync-next': [FAIL],
'language/expressions/class/async-gen-method-static-yield-star-sync-next': [FAIL], 'language/expressions/class/async-gen-method-static-yield-star-sync-next': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=6861
'language/expressions/async-generator/yield-star-sync-next': [FAIL], 'language/expressions/async-generator/yield-star-sync-next': [FAIL],
'language/statements/class/async-gen-method-static-yield-star-sync-next': [FAIL], 'language/statements/class/async-gen-method-static-yield-star-sync-next': [FAIL],
'language/expressions/async-generator/named-yield-star-sync-next': [FAIL], 'language/expressions/async-generator/named-yield-star-sync-next': [FAIL],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment