[ignition] Move suspend_id assignment to bytecode generation
Instead of building suspend_ids in the AST numbering, collect suspend counts in the parser and assigning suspend ids during bytecode generation. Bug: v8:7178 Change-Id: I53421442afddc894db789fb9d0d3e3cc10e32ff0 Reviewed-on: https://chromium-review.googlesource.com/817598 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org> Cr-Commit-Position: refs/heads/master@{#50830}
This commit is contained in:
parent
476a45766c
commit
d7fda25256
@ -16,7 +16,7 @@ class AstNumberingVisitor final : public AstVisitor<AstNumberingVisitor> {
|
||||
public:
|
||||
AstNumberingVisitor(uintptr_t stack_limit, Zone* zone,
|
||||
Compiler::EagerInnerFunctionLiterals* eager_literals)
|
||||
: zone_(zone), eager_literals_(eager_literals), suspend_count_(0) {
|
||||
: zone_(zone), eager_literals_(eager_literals) {
|
||||
InitializeAstVisitor(stack_limit);
|
||||
}
|
||||
|
||||
@ -40,7 +40,6 @@ class AstNumberingVisitor final : public AstVisitor<AstNumberingVisitor> {
|
||||
|
||||
Zone* zone_;
|
||||
Compiler::EagerInnerFunctionLiterals* eager_literals_;
|
||||
int suspend_count_;
|
||||
FunctionKind function_kind_;
|
||||
|
||||
DEFINE_AST_VISITOR_SUBCLASS_MEMBERS();
|
||||
@ -110,19 +109,12 @@ void AstNumberingVisitor::VisitReturnStatement(ReturnStatement* node) {
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitSuspend(Suspend* node) {
|
||||
node->set_suspend_id(suspend_count_);
|
||||
suspend_count_++;
|
||||
Visit(node->expression());
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitYield(Yield* node) { VisitSuspend(node); }
|
||||
|
||||
void AstNumberingVisitor::VisitYieldStar(YieldStar* node) {
|
||||
node->set_suspend_id(suspend_count_++);
|
||||
if (IsAsyncGeneratorFunction(function_kind_)) {
|
||||
node->set_await_iterator_close_suspend_id(suspend_count_++);
|
||||
node->set_await_delegated_iterator_output_suspend_id(suspend_count_++);
|
||||
}
|
||||
Visit(node->expression());
|
||||
}
|
||||
|
||||
@ -166,17 +158,13 @@ void AstNumberingVisitor::VisitWithStatement(WithStatement* node) {
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitDoWhileStatement(DoWhileStatement* node) {
|
||||
node->set_first_suspend_id(suspend_count_);
|
||||
Visit(node->body());
|
||||
Visit(node->cond());
|
||||
node->set_suspend_count(suspend_count_ - node->first_suspend_id());
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitWhileStatement(WhileStatement* node) {
|
||||
node->set_first_suspend_id(suspend_count_);
|
||||
Visit(node->cond());
|
||||
Visit(node->body());
|
||||
node->set_suspend_count(suspend_count_ - node->first_suspend_id());
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) {
|
||||
@ -207,7 +195,10 @@ void AstNumberingVisitor::VisitAssignment(Assignment* node) {
|
||||
|
||||
void AstNumberingVisitor::VisitCompoundAssignment(CompoundAssignment* node) {
|
||||
VisitBinaryOperation(node->binary_operation());
|
||||
VisitAssignment(node);
|
||||
// We don't need to recurse down the assignment version since we already did
|
||||
// the binop.
|
||||
DCHECK_EQ(node->target(), node->binary_operation()->left());
|
||||
DCHECK_EQ(node->value(), node->binary_operation()->right());
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitBinaryOperation(BinaryOperation* node) {
|
||||
@ -248,21 +239,17 @@ void AstNumberingVisitor::VisitImportCallExpression(
|
||||
|
||||
void AstNumberingVisitor::VisitForInStatement(ForInStatement* node) {
|
||||
Visit(node->enumerable()); // Not part of loop.
|
||||
node->set_first_suspend_id(suspend_count_);
|
||||
Visit(node->each());
|
||||
Visit(node->body());
|
||||
node->set_suspend_count(suspend_count_ - node->first_suspend_id());
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitForOfStatement(ForOfStatement* node) {
|
||||
Visit(node->assign_iterator()); // Not part of loop.
|
||||
Visit(node->assign_next());
|
||||
node->set_first_suspend_id(suspend_count_);
|
||||
Visit(node->next_result());
|
||||
Visit(node->result_done());
|
||||
Visit(node->assign_each());
|
||||
Visit(node->body());
|
||||
node->set_suspend_count(suspend_count_ - node->first_suspend_id());
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitConditional(Conditional* node) {
|
||||
@ -273,8 +260,10 @@ void AstNumberingVisitor::VisitConditional(Conditional* node) {
|
||||
|
||||
void AstNumberingVisitor::VisitIfStatement(IfStatement* node) {
|
||||
Visit(node->condition());
|
||||
Visit(node->then_statement());
|
||||
if (node->HasElseStatement()) {
|
||||
if (!node->condition()->ToBooleanIsFalse()) {
|
||||
Visit(node->then_statement());
|
||||
}
|
||||
if (node->HasElseStatement() && !node->condition()->ToBooleanIsTrue()) {
|
||||
Visit(node->else_statement());
|
||||
}
|
||||
}
|
||||
@ -289,11 +278,9 @@ void AstNumberingVisitor::VisitSwitchStatement(SwitchStatement* node) {
|
||||
|
||||
void AstNumberingVisitor::VisitForStatement(ForStatement* node) {
|
||||
if (node->init() != nullptr) Visit(node->init()); // Not part of loop.
|
||||
node->set_first_suspend_id(suspend_count_);
|
||||
if (node->cond() != nullptr) Visit(node->cond());
|
||||
if (node->next() != nullptr) Visit(node->next());
|
||||
Visit(node->body());
|
||||
node->set_suspend_count(suspend_count_ - node->first_suspend_id());
|
||||
}
|
||||
|
||||
void AstNumberingVisitor::VisitClassLiteral(ClassLiteral* node) {
|
||||
@ -391,8 +378,6 @@ bool AstNumberingVisitor::Renumber(FunctionLiteral* node) {
|
||||
VisitDeclarations(scope->declarations());
|
||||
VisitStatements(node->body());
|
||||
|
||||
node->set_suspend_count(suspend_count_);
|
||||
|
||||
return !HasStackOverflow();
|
||||
}
|
||||
|
||||
|
@ -437,21 +437,12 @@ class IterationStatement : public BreakableStatement {
|
||||
|
||||
ZoneList<const AstRawString*>* labels() const { return labels_; }
|
||||
|
||||
int suspend_count() const { return suspend_count_; }
|
||||
int first_suspend_id() const { return first_suspend_id_; }
|
||||
void set_suspend_count(int suspend_count) { suspend_count_ = suspend_count; }
|
||||
void set_first_suspend_id(int first_suspend_id) {
|
||||
first_suspend_id_ = first_suspend_id;
|
||||
}
|
||||
|
||||
protected:
|
||||
IterationStatement(ZoneList<const AstRawString*>* labels, int pos,
|
||||
NodeType type)
|
||||
: BreakableStatement(TARGET_FOR_ANONYMOUS, pos, type),
|
||||
labels_(labels),
|
||||
body_(nullptr),
|
||||
suspend_count_(0),
|
||||
first_suspend_id_(0) {}
|
||||
body_(nullptr) {}
|
||||
void Initialize(Statement* body) { body_ = body; }
|
||||
|
||||
static const uint8_t kNextBitFieldIndex =
|
||||
@ -460,8 +451,6 @@ class IterationStatement : public BreakableStatement {
|
||||
private:
|
||||
ZoneList<const AstRawString*>* labels_;
|
||||
Statement* body_;
|
||||
int suspend_count_;
|
||||
int first_suspend_id_;
|
||||
};
|
||||
|
||||
|
||||
@ -2096,11 +2085,6 @@ class Suspend : public Expression {
|
||||
return OnAbruptResumeField::decode(bit_field_);
|
||||
}
|
||||
|
||||
int suspend_id() const { return suspend_id_; }
|
||||
void set_suspend_id(int id) { suspend_id_ = id; }
|
||||
|
||||
inline bool IsInitialYield() const { return suspend_id_ == 0 && IsYield(); }
|
||||
|
||||
private:
|
||||
friend class AstNodeFactory;
|
||||
friend class Yield;
|
||||
@ -2109,11 +2093,10 @@ class Suspend : public Expression {
|
||||
|
||||
Suspend(NodeType node_type, Expression* expression, int pos,
|
||||
OnAbruptResume on_abrupt_resume)
|
||||
: Expression(pos, node_type), suspend_id_(-1), expression_(expression) {
|
||||
: Expression(pos, node_type), expression_(expression) {
|
||||
bit_field_ |= OnAbruptResumeField::encode(on_abrupt_resume);
|
||||
}
|
||||
|
||||
int suspend_id_;
|
||||
Expression* expression_;
|
||||
|
||||
class OnAbruptResumeField
|
||||
@ -2128,47 +2111,11 @@ class Yield final : public Suspend {
|
||||
};
|
||||
|
||||
class YieldStar final : public Suspend {
|
||||
public:
|
||||
// In addition to the normal suspend for yield*, a yield* in an async
|
||||
// generator has 2 additional suspends:
|
||||
// - One for awaiting the iterator result of closing the generator when
|
||||
// resumed with a "throw" completion, and a throw method is not present
|
||||
// on the delegated iterator (await_iterator_close_suspend_id)
|
||||
// - One for awaiting the iterator result yielded by the delegated iterator
|
||||
// (await_delegated_iterator_output_suspend_id)
|
||||
int await_iterator_close_suspend_id() const {
|
||||
return await_iterator_close_suspend_id_;
|
||||
}
|
||||
void set_await_iterator_close_suspend_id(int id) {
|
||||
await_iterator_close_suspend_id_ = id;
|
||||
}
|
||||
|
||||
int await_delegated_iterator_output_suspend_id() const {
|
||||
return await_delegated_iterator_output_suspend_id_;
|
||||
}
|
||||
void set_await_delegated_iterator_output_suspend_id(int id) {
|
||||
await_delegated_iterator_output_suspend_id_ = id;
|
||||
}
|
||||
|
||||
inline int suspend_count() const {
|
||||
if (await_iterator_close_suspend_id_ != -1) {
|
||||
DCHECK_NE(-1, await_delegated_iterator_output_suspend_id_);
|
||||
return 3;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AstNodeFactory;
|
||||
|
||||
YieldStar(Expression* expression, int pos)
|
||||
: Suspend(kYieldStar, expression, pos,
|
||||
Suspend::OnAbruptResume::kNoControl),
|
||||
await_iterator_close_suspend_id_(-1),
|
||||
await_delegated_iterator_output_suspend_id_(-1) {}
|
||||
|
||||
int await_iterator_close_suspend_id_;
|
||||
int await_delegated_iterator_output_suspend_id_;
|
||||
Suspend::OnAbruptResume::kNoControl) {}
|
||||
};
|
||||
|
||||
class Await final : public Suspend {
|
||||
|
@ -909,8 +909,6 @@ void AstPrinter::VisitSwitchStatement(SwitchStatement* node) {
|
||||
|
||||
void AstPrinter::VisitDoWhileStatement(DoWhileStatement* node) {
|
||||
IndentedScope indent(this, "DO", node->position());
|
||||
PrintIndented("SUSPEND COUNT");
|
||||
Print(" %d\n", node->suspend_count());
|
||||
PrintLabelsIndented(node->labels());
|
||||
PrintIndentedVisit("BODY", node->body());
|
||||
PrintIndentedVisit("COND", node->cond());
|
||||
@ -919,8 +917,6 @@ void AstPrinter::VisitDoWhileStatement(DoWhileStatement* node) {
|
||||
|
||||
void AstPrinter::VisitWhileStatement(WhileStatement* node) {
|
||||
IndentedScope indent(this, "WHILE", node->position());
|
||||
PrintIndented("SUSPEND COUNT");
|
||||
Print(" %d\n", node->suspend_count());
|
||||
PrintLabelsIndented(node->labels());
|
||||
PrintIndentedVisit("COND", node->cond());
|
||||
PrintIndentedVisit("BODY", node->body());
|
||||
@ -929,8 +925,6 @@ void AstPrinter::VisitWhileStatement(WhileStatement* node) {
|
||||
|
||||
void AstPrinter::VisitForStatement(ForStatement* node) {
|
||||
IndentedScope indent(this, "FOR", node->position());
|
||||
PrintIndented("SUSPEND COUNT");
|
||||
Print(" %d\n", node->suspend_count());
|
||||
PrintLabelsIndented(node->labels());
|
||||
if (node->init()) PrintIndentedVisit("INIT", node->init());
|
||||
if (node->cond()) PrintIndentedVisit("COND", node->cond());
|
||||
@ -941,8 +935,6 @@ void AstPrinter::VisitForStatement(ForStatement* node) {
|
||||
|
||||
void AstPrinter::VisitForInStatement(ForInStatement* node) {
|
||||
IndentedScope indent(this, "FOR IN", node->position());
|
||||
PrintIndented("SUSPEND COUNT");
|
||||
Print(" %d\n", node->suspend_count());
|
||||
PrintIndentedVisit("FOR", node->each());
|
||||
PrintIndentedVisit("IN", node->enumerable());
|
||||
PrintIndentedVisit("BODY", node->body());
|
||||
@ -951,8 +943,6 @@ void AstPrinter::VisitForInStatement(ForInStatement* node) {
|
||||
|
||||
void AstPrinter::VisitForOfStatement(ForOfStatement* node) {
|
||||
IndentedScope indent(this, "FOR OF", node->position());
|
||||
PrintIndented("SUSPEND COUNT");
|
||||
Print(" %d\n", node->suspend_count());
|
||||
PrintIndentedVisit("INIT", node->assign_iterator());
|
||||
PrintIndentedVisit("NEXT", node->next_result());
|
||||
PrintIndentedVisit("DONE", node->result_done());
|
||||
@ -1208,21 +1198,21 @@ void AstPrinter::VisitCompoundAssignment(CompoundAssignment* node) {
|
||||
|
||||
void AstPrinter::VisitYield(Yield* node) {
|
||||
EmbeddedVector<char, 128> buf;
|
||||
SNPrintF(buf, "YIELD id %d", node->suspend_id());
|
||||
SNPrintF(buf, "YIELD");
|
||||
IndentedScope indent(this, buf.start(), node->position());
|
||||
Visit(node->expression());
|
||||
}
|
||||
|
||||
void AstPrinter::VisitYieldStar(YieldStar* node) {
|
||||
EmbeddedVector<char, 128> buf;
|
||||
SNPrintF(buf, "YIELD_STAR id %d", node->suspend_id());
|
||||
SNPrintF(buf, "YIELD_STAR");
|
||||
IndentedScope indent(this, buf.start(), node->position());
|
||||
Visit(node->expression());
|
||||
}
|
||||
|
||||
void AstPrinter::VisitAwait(Await* node) {
|
||||
EmbeddedVector<char, 128> buf;
|
||||
SNPrintF(buf, "AWAIT id %d", node->suspend_id());
|
||||
SNPrintF(buf, "AWAIT");
|
||||
IndentedScope indent(this, buf.start(), node->position());
|
||||
Visit(node->expression());
|
||||
}
|
||||
|
@ -878,6 +878,7 @@ BytecodeGenerator::BytecodeGenerator(
|
||||
execution_result_(nullptr),
|
||||
incoming_new_target_or_generator_(),
|
||||
generator_jump_table_(nullptr),
|
||||
suspend_count_(0),
|
||||
loop_depth_(0),
|
||||
catch_prediction_(HandlerTable::UNCAUGHT) {
|
||||
DCHECK_EQ(closure_scope(), closure_scope()->GetClosureScope());
|
||||
@ -1112,18 +1113,6 @@ void BytecodeGenerator::AllocateTopLevelRegisters() {
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeGenerator::VisitIterationHeader(IterationStatement* stmt,
|
||||
LoopBuilder* loop_builder) {
|
||||
VisitIterationHeader(stmt->first_suspend_id(), stmt->suspend_count(),
|
||||
loop_builder);
|
||||
}
|
||||
|
||||
void BytecodeGenerator::VisitIterationHeader(int first_suspend_id,
|
||||
int suspend_count,
|
||||
LoopBuilder* loop_builder) {
|
||||
loop_builder->LoopHeader();
|
||||
}
|
||||
|
||||
void BytecodeGenerator::BuildGeneratorPrologue() {
|
||||
DCHECK_GT(info()->literal()->suspend_count(), 0);
|
||||
DCHECK(generator_object().is_valid());
|
||||
@ -1467,11 +1456,11 @@ void BytecodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
|
||||
if (stmt->cond()->ToBooleanIsFalse()) {
|
||||
VisitIterationBody(stmt, &loop_builder);
|
||||
} else if (stmt->cond()->ToBooleanIsTrue()) {
|
||||
VisitIterationHeader(stmt, &loop_builder);
|
||||
loop_builder.LoopHeader();
|
||||
VisitIterationBody(stmt, &loop_builder);
|
||||
loop_builder.JumpToHeader(loop_depth_);
|
||||
} else {
|
||||
VisitIterationHeader(stmt, &loop_builder);
|
||||
loop_builder.LoopHeader();
|
||||
VisitIterationBody(stmt, &loop_builder);
|
||||
builder()->SetExpressionAsStatementPosition(stmt->cond());
|
||||
BytecodeLabels loop_backbranch(zone());
|
||||
@ -1490,7 +1479,7 @@ void BytecodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
VisitIterationHeader(stmt, &loop_builder);
|
||||
loop_builder.LoopHeader();
|
||||
if (!stmt->cond()->ToBooleanIsTrue()) {
|
||||
builder()->SetExpressionAsStatementPosition(stmt->cond());
|
||||
BytecodeLabels loop_body(zone());
|
||||
@ -1514,7 +1503,7 @@ void BytecodeGenerator::VisitForStatement(ForStatement* stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
VisitIterationHeader(stmt, &loop_builder);
|
||||
loop_builder.LoopHeader();
|
||||
if (stmt->cond() && !stmt->cond()->ToBooleanIsTrue()) {
|
||||
builder()->SetExpressionAsStatementPosition(stmt->cond());
|
||||
BytecodeLabels loop_body(zone());
|
||||
@ -1632,7 +1621,7 @@ void BytecodeGenerator::VisitForInStatement(ForInStatement* stmt) {
|
||||
// The loop
|
||||
{
|
||||
LoopBuilder loop_builder(builder(), block_coverage_builder_, stmt);
|
||||
VisitIterationHeader(stmt, &loop_builder);
|
||||
loop_builder.LoopHeader();
|
||||
builder()->SetExpressionAsStatementPosition(stmt->each());
|
||||
builder()->ForInContinue(index, cache_length);
|
||||
loop_builder.BreakIfFalse(ToBooleanMode::kAlreadyBoolean);
|
||||
@ -1656,7 +1645,7 @@ void BytecodeGenerator::VisitForOfStatement(ForOfStatement* stmt) {
|
||||
VisitForEffect(stmt->assign_iterator());
|
||||
VisitForEffect(stmt->assign_next());
|
||||
|
||||
VisitIterationHeader(stmt, &loop_builder);
|
||||
loop_builder.LoopHeader();
|
||||
builder()->SetExpressionAsStatementPosition(stmt->next_result());
|
||||
VisitForEffect(stmt->next_result());
|
||||
TypeHint type_hint = VisitForAccumulatorValue(stmt->result_done());
|
||||
@ -2825,11 +2814,12 @@ void BytecodeGenerator::VisitCompoundAssignment(CompoundAssignment* expr) {
|
||||
VisitAssignment(expr);
|
||||
}
|
||||
|
||||
// Suspends the generator to resume at |suspend_id|, with output stored in the
|
||||
// accumulator. When the generator is resumed, the sent value is loaded in the
|
||||
// accumulator.
|
||||
void BytecodeGenerator::BuildSuspendPoint(int suspend_id,
|
||||
Expression* suspend_expr) {
|
||||
// Suspends the generator to resume at the next suspend_id, with output stored
|
||||
// in the accumulator. When the generator is resumed, the sent value is loaded
|
||||
// in the accumulator.
|
||||
void BytecodeGenerator::BuildSuspendPoint(Expression* suspend_expr) {
|
||||
const int suspend_id = suspend_count_++;
|
||||
|
||||
RegisterList registers = register_allocator()->AllLiveRegisters();
|
||||
|
||||
// Save context, registers, and state. This bytecode then returns the value
|
||||
@ -2849,7 +2839,8 @@ void BytecodeGenerator::VisitYield(Yield* expr) {
|
||||
builder()->SetExpressionPosition(expr);
|
||||
VisitForAccumulatorValue(expr->expression());
|
||||
|
||||
if (!expr->IsInitialYield()) {
|
||||
// If this is not the first yield
|
||||
if (suspend_count_ > 0) {
|
||||
if (IsAsyncGeneratorFunction(function_kind())) {
|
||||
// AsyncGenerator yields (with the exception of the initial yield)
|
||||
// delegate work to the AsyncGeneratorYield stub, which Awaits the operand
|
||||
@ -2875,7 +2866,7 @@ void BytecodeGenerator::VisitYield(Yield* expr) {
|
||||
}
|
||||
}
|
||||
|
||||
BuildSuspendPoint(expr->suspend_id(), expr);
|
||||
BuildSuspendPoint(expr);
|
||||
// At this point, the generator has been resumed, with the received value in
|
||||
// the accumulator.
|
||||
|
||||
@ -3014,10 +3005,16 @@ void BytecodeGenerator::VisitYieldStar(YieldStar* expr) {
|
||||
// visible to the user, and we therefore neither pass the block coverage
|
||||
// builder nor the expression.
|
||||
//
|
||||
// YieldStar in AsyncGenerator functions includes 3 suspend points, rather
|
||||
// than 1. These are documented in the YieldStar AST node.
|
||||
// In addition to the normal suspend for yield*, a yield* in an async
|
||||
// generator has 2 additional suspends:
|
||||
// - One for awaiting the iterator result of closing the generator when
|
||||
// resumed with a "throw" completion, and a throw method is not
|
||||
// present on the delegated iterator
|
||||
// - One for awaiting the iterator result yielded by the delegated
|
||||
// iterator
|
||||
|
||||
LoopBuilder loop(builder(), nullptr, nullptr);
|
||||
VisitIterationHeader(expr->suspend_id(), expr->suspend_count(), &loop);
|
||||
loop.LoopHeader();
|
||||
|
||||
{
|
||||
BytecodeLabels after_switch(zone());
|
||||
@ -3071,8 +3068,7 @@ void BytecodeGenerator::VisitYieldStar(YieldStar* expr) {
|
||||
// If there is no "throw" method, perform IteratorClose, and finally
|
||||
// throw a TypeError.
|
||||
no_throw_method.Bind(builder());
|
||||
BuildIteratorClose(iterator, expr->await_iterator_close_suspend_id(),
|
||||
expr);
|
||||
BuildIteratorClose(iterator, expr);
|
||||
builder()->CallRuntime(Runtime::kThrowThrowMethodMissing);
|
||||
}
|
||||
|
||||
@ -3081,7 +3077,7 @@ void BytecodeGenerator::VisitYieldStar(YieldStar* expr) {
|
||||
|
||||
if (iterator_type == IteratorType::kAsync) {
|
||||
// Await the result of the method invocation.
|
||||
BuildAwait(expr->await_delegated_iterator_output_suspend_id(), expr);
|
||||
BuildAwait(expr);
|
||||
}
|
||||
|
||||
// Check that output is an object.
|
||||
@ -3121,7 +3117,7 @@ void BytecodeGenerator::VisitYieldStar(YieldStar* expr) {
|
||||
.CallRuntime(Runtime::kInlineAsyncGeneratorYield, args);
|
||||
}
|
||||
|
||||
BuildSuspendPoint(expr->suspend_id(), expr);
|
||||
BuildSuspendPoint(expr);
|
||||
builder()->StoreAccumulatorInRegister(input);
|
||||
builder()
|
||||
->CallRuntime(Runtime::kInlineGeneratorGetResumeMode,
|
||||
@ -3157,7 +3153,7 @@ void BytecodeGenerator::VisitYieldStar(YieldStar* expr) {
|
||||
builder()->LoadAccumulatorWithRegister(output_value);
|
||||
}
|
||||
|
||||
void BytecodeGenerator::BuildAwait(int suspend_id, Expression* await_expr) {
|
||||
void BytecodeGenerator::BuildAwait(Expression* await_expr) {
|
||||
// Rather than HandlerTable::UNCAUGHT, async functions use
|
||||
// HandlerTable::ASYNC_AWAIT to communicate that top-level exceptions are
|
||||
// transformed into promise rejections. This is necessary to prevent emitting
|
||||
@ -3201,7 +3197,7 @@ void BytecodeGenerator::BuildAwait(int suspend_id, Expression* await_expr) {
|
||||
builder()->CallJSRuntime(await_builtin_context_index, args);
|
||||
}
|
||||
|
||||
BuildSuspendPoint(suspend_id, await_expr);
|
||||
BuildSuspendPoint(await_expr);
|
||||
|
||||
Register input = register_allocator()->NewRegister();
|
||||
Register resume_mode = register_allocator()->NewRegister();
|
||||
@ -3229,7 +3225,7 @@ void BytecodeGenerator::BuildAwait(int suspend_id, Expression* await_expr) {
|
||||
void BytecodeGenerator::VisitAwait(Await* expr) {
|
||||
builder()->SetExpressionPosition(expr);
|
||||
VisitForAccumulatorValue(expr->expression());
|
||||
BuildAwait(expr->suspend_id(), expr);
|
||||
BuildAwait(expr);
|
||||
BuildIncrementBlockCoverageCounterIfEnabled(expr,
|
||||
SourceRangeKind::kContinuation);
|
||||
}
|
||||
@ -4116,7 +4112,7 @@ void BytecodeGenerator::BuildCallIteratorMethod(Register iterator,
|
||||
}
|
||||
|
||||
void BytecodeGenerator::BuildIteratorClose(const IteratorRecord& iterator,
|
||||
int suspend_id, Expression* expr) {
|
||||
Expression* expr) {
|
||||
RegisterAllocationScope register_scope(this);
|
||||
BytecodeLabels done(zone());
|
||||
BytecodeLabel if_called;
|
||||
@ -4127,9 +4123,8 @@ void BytecodeGenerator::BuildIteratorClose(const IteratorRecord& iterator,
|
||||
builder()->Bind(&if_called);
|
||||
|
||||
if (iterator.type() == IteratorType::kAsync) {
|
||||
DCHECK_GE(suspend_id, 0);
|
||||
DCHECK_NOT_NULL(expr);
|
||||
BuildAwait(suspend_id, expr);
|
||||
BuildAwait(expr);
|
||||
}
|
||||
|
||||
builder()->JumpIfJSReceiver(done.New());
|
||||
|
@ -146,9 +146,9 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
void BuildNewLocalWithContext(Scope* scope);
|
||||
|
||||
void BuildGeneratorPrologue();
|
||||
void BuildSuspendPoint(int suspend_id, Expression* suspend_expr);
|
||||
void BuildSuspendPoint(Expression* suspend_expr);
|
||||
|
||||
void BuildAwait(int suspend_id, Expression* await_expr);
|
||||
void BuildAwait(Expression* await_expr);
|
||||
|
||||
void BuildGetIterator(Expression* iterable, IteratorType hint);
|
||||
|
||||
@ -164,7 +164,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
IteratorRecord BuildGetIteratorRecord(Expression* iterable,
|
||||
IteratorType hint);
|
||||
void BuildIteratorNext(const IteratorRecord& iterator, Register next_result);
|
||||
void BuildIteratorClose(const IteratorRecord& iterator, int suspend_id = -1,
|
||||
void BuildIteratorClose(const IteratorRecord& iterator,
|
||||
Expression* expr = nullptr);
|
||||
void BuildCallIteratorMethod(Register iterator, const AstRawString* method,
|
||||
RegisterList receiver_and_args,
|
||||
@ -213,11 +213,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
BytecodeLabels* end_labels,
|
||||
int coverage_slot);
|
||||
|
||||
// Visit the header/body of a loop iteration.
|
||||
void VisitIterationHeader(IterationStatement* stmt,
|
||||
LoopBuilder* loop_builder);
|
||||
void VisitIterationHeader(int first_suspend_id, int suspend_count,
|
||||
LoopBuilder* loop_builder);
|
||||
// Visit the body of a loop iteration.
|
||||
void VisitIterationBody(IterationStatement* stmt, LoopBuilder* loop_builder);
|
||||
|
||||
// Visit a statement and switch scopes, the context is in the accumulator.
|
||||
@ -345,6 +341,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
Register incoming_new_target_or_generator_;
|
||||
|
||||
BytecodeJumpTable* generator_jump_table_;
|
||||
int suspend_count_;
|
||||
int loop_depth_;
|
||||
|
||||
HandlerTable::CatchPrediction catch_prediction_;
|
||||
|
@ -47,10 +47,6 @@ void BreakableControlFlowBuilder::EmitJumpIfNull(BytecodeLabels* sites) {
|
||||
|
||||
LoopBuilder::~LoopBuilder() {
|
||||
DCHECK(continue_labels_.empty() || continue_labels_.is_bound());
|
||||
// Restore the parent jump table.
|
||||
if (generator_jump_table_location_ != nullptr) {
|
||||
*generator_jump_table_location_ = parent_generator_jump_table_;
|
||||
}
|
||||
}
|
||||
|
||||
void LoopBuilder::LoopHeader() {
|
||||
@ -62,26 +58,6 @@ void LoopBuilder::LoopHeader() {
|
||||
builder()->Bind(&loop_header_);
|
||||
}
|
||||
|
||||
void LoopBuilder::LoopHeaderInGenerator(
|
||||
BytecodeJumpTable** generator_jump_table, int first_resume_id,
|
||||
int resume_count) {
|
||||
// Bind all the resume points that are inside the loop to be at the loop
|
||||
// header.
|
||||
for (int id = first_resume_id; id < first_resume_id + resume_count; ++id) {
|
||||
builder()->Bind(*generator_jump_table, id);
|
||||
}
|
||||
|
||||
// Create the loop header.
|
||||
LoopHeader();
|
||||
|
||||
// Create a new jump table for after the loop header for only these
|
||||
// resume points.
|
||||
generator_jump_table_location_ = generator_jump_table;
|
||||
parent_generator_jump_table_ = *generator_jump_table;
|
||||
*generator_jump_table =
|
||||
builder()->AllocateJumpTable(resume_count, first_resume_id);
|
||||
}
|
||||
|
||||
void LoopBuilder::LoopBody() {
|
||||
if (block_coverage_builder_ != nullptr) {
|
||||
block_coverage_builder_->IncrementBlockCounter(block_coverage_body_slot_);
|
||||
|
@ -105,9 +105,7 @@ class V8_EXPORT_PRIVATE LoopBuilder final : public BreakableControlFlowBuilder {
|
||||
LoopBuilder(BytecodeArrayBuilder* builder,
|
||||
BlockCoverageBuilder* block_coverage_builder, AstNode* node)
|
||||
: BreakableControlFlowBuilder(builder, block_coverage_builder, node),
|
||||
continue_labels_(builder->zone()),
|
||||
generator_jump_table_location_(nullptr),
|
||||
parent_generator_jump_table_(nullptr) {
|
||||
continue_labels_(builder->zone()) {
|
||||
if (block_coverage_builder_ != nullptr) {
|
||||
set_needs_continuation_counter();
|
||||
block_coverage_body_slot_ =
|
||||
@ -118,8 +116,6 @@ class V8_EXPORT_PRIVATE LoopBuilder final : public BreakableControlFlowBuilder {
|
||||
~LoopBuilder();
|
||||
|
||||
void LoopHeader();
|
||||
void LoopHeaderInGenerator(BytecodeJumpTable** parent_generator_jump_table,
|
||||
int first_resume_id, int resume_count);
|
||||
void LoopBody();
|
||||
void JumpToHeader(int loop_depth);
|
||||
void BindContinueTarget();
|
||||
@ -138,13 +134,6 @@ class V8_EXPORT_PRIVATE LoopBuilder final : public BreakableControlFlowBuilder {
|
||||
// jumps from checking the loop condition to the header for do-while loops.
|
||||
BytecodeLabels continue_labels_;
|
||||
|
||||
// While we're in the loop, we want to have a different jump table for
|
||||
// generator switch statements. We restore it at the end of the loop.
|
||||
// TODO(leszeks): Storing a pointer to the BytecodeGenerator's jump table
|
||||
// field is ugly, figure out a better way to do this.
|
||||
BytecodeJumpTable** generator_jump_table_location_;
|
||||
BytecodeJumpTable* parent_generator_jump_table_;
|
||||
|
||||
int block_coverage_body_slot_;
|
||||
};
|
||||
|
||||
|
@ -398,6 +398,9 @@ class ParserBase {
|
||||
}
|
||||
BailoutReason dont_optimize_reason() { return dont_optimize_reason_; }
|
||||
|
||||
void AddSuspend() { suspend_count_++; }
|
||||
int suspend_count() const { return suspend_count_; }
|
||||
|
||||
FunctionKind kind() const { return scope()->function_kind(); }
|
||||
FunctionState* outer() const { return outer_function_state_; }
|
||||
|
||||
@ -481,6 +484,9 @@ class ParserBase {
|
||||
// A reason, if any, why this function should not be optimized.
|
||||
BailoutReason dont_optimize_reason_;
|
||||
|
||||
// How many suspends are needed for this function.
|
||||
int suspend_count_;
|
||||
|
||||
// Record whether the next (=== immediately following) function literal is
|
||||
// preceded by a parenthesis / exclamation mark. Also record the previous
|
||||
// state.
|
||||
@ -1403,6 +1409,7 @@ class ParserBase {
|
||||
// In async generators, if there is an explicit operand to the return
|
||||
// statement, await the operand.
|
||||
expr = factory()->NewAwait(expr, kNoSourcePosition);
|
||||
function_state_->AddSuspend();
|
||||
}
|
||||
if (is_async_function()) {
|
||||
return factory()->NewAsyncReturnStatement(expr, pos, end_pos);
|
||||
@ -1562,6 +1569,7 @@ ParserBase<Impl>::FunctionState::FunctionState(
|
||||
non_patterns_to_rewrite_(0, scope->zone()),
|
||||
reported_errors_(16, scope->zone()),
|
||||
dont_optimize_reason_(BailoutReason::kNoReason),
|
||||
suspend_count_(0),
|
||||
next_function_is_likely_called_(false),
|
||||
previous_function_was_likely_called_(false),
|
||||
contains_function_or_eval_(false) {
|
||||
@ -3042,6 +3050,12 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseYieldExpression(
|
||||
if (delegating) {
|
||||
ExpressionT yieldstar = factory()->NewYieldStar(expression, pos);
|
||||
impl()->RecordSuspendSourceRange(yieldstar, PositionAfterSemicolon());
|
||||
function_state_->AddSuspend();
|
||||
if (IsAsyncGeneratorFunction(function_state_->kind())) {
|
||||
// iterator_close and delegated_iterator_output suspend ids.
|
||||
function_state_->AddSuspend();
|
||||
function_state_->AddSuspend();
|
||||
}
|
||||
return yieldstar;
|
||||
}
|
||||
|
||||
@ -3050,6 +3064,7 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseYieldExpression(
|
||||
ExpressionT yield =
|
||||
factory()->NewYield(expression, pos, Suspend::kOnExceptionThrow);
|
||||
impl()->RecordSuspendSourceRange(yield, PositionAfterSemicolon());
|
||||
function_state_->AddSuspend();
|
||||
return yield;
|
||||
}
|
||||
|
||||
@ -3236,6 +3251,7 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseUnaryExpression(
|
||||
MessageTemplate::kInvalidDestructuringTarget);
|
||||
|
||||
ExpressionT expr = factory()->NewAwait(value, await_pos);
|
||||
function_state_->AddSuspend();
|
||||
impl()->RecordSuspendSourceRange(expr, PositionAfterSemicolon());
|
||||
return expr;
|
||||
} else {
|
||||
@ -4351,6 +4367,7 @@ ParserBase<Impl>::ParseArrowFunctionLiteral(
|
||||
|
||||
StatementListT body = impl()->NullStatementList();
|
||||
int expected_property_count = -1;
|
||||
int suspend_count = 0;
|
||||
int function_literal_id = GetNextFunctionLiteralId();
|
||||
|
||||
FunctionKind kind = formal_parameters.scope->function_kind();
|
||||
@ -4436,6 +4453,7 @@ ParserBase<Impl>::ParseArrowFunctionLiteral(
|
||||
impl()->CheckConflictingVarDeclarations(formal_parameters.scope, CHECK_OK);
|
||||
|
||||
impl()->RewriteDestructuringAssignments();
|
||||
suspend_count = function_state.suspend_count();
|
||||
}
|
||||
|
||||
FunctionLiteralT function_literal = factory()->NewFunctionLiteral(
|
||||
@ -4447,6 +4465,7 @@ ParserBase<Impl>::ParseArrowFunctionLiteral(
|
||||
formal_parameters.scope->start_position(), has_braces,
|
||||
function_literal_id, produced_preparsed_scope_data);
|
||||
|
||||
function_literal->set_suspend_count(suspend_count);
|
||||
function_literal->set_function_token_position(
|
||||
formal_parameters.scope->start_position());
|
||||
|
||||
@ -5649,6 +5668,14 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseTryStatement(
|
||||
template <typename Impl>
|
||||
typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseForStatement(
|
||||
ZoneList<const AstRawString*>* labels, bool* ok) {
|
||||
// Either a standard for loop
|
||||
// for (<init>; <cond>; <next>) { ... }
|
||||
// or a for-each loop
|
||||
// for (<each> of|in <iterable>) { ... }
|
||||
//
|
||||
// We parse a declaration/expression after the 'for (' and then read the first
|
||||
// expression/declaration before we know if this is a for or a for-each.
|
||||
|
||||
int stmt_pos = peek_position();
|
||||
ForInfo for_info(this);
|
||||
|
||||
|
@ -742,6 +742,7 @@ FunctionLiteral* Parser::DoParseProgram(ParseInfo* info) {
|
||||
result = factory()->NewScriptOrEvalFunctionLiteral(
|
||||
scope, body, function_state.expected_property_count(),
|
||||
parameter_count);
|
||||
result->set_suspend_count(function_state.suspend_count());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1831,6 +1832,8 @@ void Parser::ParseAndRewriteAsyncGeneratorFunctionBody(
|
||||
|
||||
// Don't create iterator result for async generators, as the resume methods
|
||||
// will create it.
|
||||
// TODO(leszeks): This will create another suspend point, which is unnecessary
|
||||
// if there is already an unconditional return in the body.
|
||||
Statement* final_return = BuildReturnStatement(
|
||||
factory()->NewUndefinedLiteral(kNoSourcePosition), kNoSourcePosition);
|
||||
try_block->statements()->Add(final_return, zone());
|
||||
@ -1900,6 +1903,7 @@ Expression* Parser::BuildIteratorNextResult(VariableProxy* iterator,
|
||||
Expression* next_call =
|
||||
factory()->NewCall(next_property, next_arguments, kNoSourcePosition);
|
||||
if (type == IteratorType::kAsync) {
|
||||
function_state_->AddSuspend();
|
||||
next_call = factory()->NewAwait(next_call, pos);
|
||||
}
|
||||
Expression* result_proxy = factory()->NewVariableProxy(result);
|
||||
@ -2681,6 +2685,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
|
||||
|
||||
ZoneList<Statement*>* body = nullptr;
|
||||
int expected_property_count = -1;
|
||||
int suspend_count = -1;
|
||||
int num_parameters = -1;
|
||||
int function_length = -1;
|
||||
bool has_duplicate_parameters = false;
|
||||
@ -2747,10 +2752,10 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
|
||||
if (should_preparse) {
|
||||
scope->AnalyzePartially(&previous_zone_ast_node_factory);
|
||||
} else {
|
||||
body = ParseFunction(function_name, pos, kind, function_type, scope,
|
||||
&num_parameters, &function_length,
|
||||
&has_duplicate_parameters, &expected_property_count,
|
||||
arguments_for_wrapped_function, CHECK_OK);
|
||||
body = ParseFunction(
|
||||
function_name, pos, kind, function_type, scope, &num_parameters,
|
||||
&function_length, &has_duplicate_parameters, &expected_property_count,
|
||||
&suspend_count, arguments_for_wrapped_function, CHECK_OK);
|
||||
}
|
||||
|
||||
DCHECK_EQ(should_preparse, temp_zoned_);
|
||||
@ -2808,6 +2813,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
|
||||
function_length, duplicate_parameters, function_type, eager_compile_hint,
|
||||
pos, true, function_literal_id, produced_preparsed_scope_data);
|
||||
function_literal->set_function_token_position(function_token_pos);
|
||||
function_literal->set_suspend_count(suspend_count);
|
||||
|
||||
if (should_infer_name) {
|
||||
DCHECK_NOT_NULL(fni_);
|
||||
@ -3175,6 +3181,7 @@ Expression* Parser::BuildInitialYield(int pos, FunctionKind kind) {
|
||||
// The position of the yield is important for reporting the exception
|
||||
// caused by calling the .throw method on a generator suspended at the
|
||||
// initial yield (i.e. right after generator instantiation).
|
||||
function_state_->AddSuspend();
|
||||
return factory()->NewYield(yield_result, scope()->start_position(),
|
||||
Suspend::kOnExceptionThrow);
|
||||
}
|
||||
@ -3184,6 +3191,7 @@ ZoneList<Statement*>* Parser::ParseFunction(
|
||||
FunctionLiteral::FunctionType function_type,
|
||||
DeclarationScope* function_scope, int* num_parameters, int* function_length,
|
||||
bool* has_duplicate_parameters, int* expected_property_count,
|
||||
int* suspend_count,
|
||||
ZoneList<const AstRawString*>* arguments_for_wrapped_function, bool* ok) {
|
||||
ParsingModeScope mode(this, allow_lazy_ ? PARSE_LAZILY : PARSE_EAGERLY);
|
||||
|
||||
@ -3268,6 +3276,7 @@ ZoneList<Statement*>* Parser::ParseFunction(
|
||||
!classifier()->is_valid_formal_parameter_list_without_duplicates();
|
||||
|
||||
*expected_property_count = function_state.expected_property_count();
|
||||
*suspend_count = function_state.suspend_count();
|
||||
return body;
|
||||
}
|
||||
|
||||
@ -4070,6 +4079,7 @@ void Parser::BuildIteratorClose(ZoneList<Statement*>* statements,
|
||||
Expression* call =
|
||||
factory()->NewCallRuntime(Runtime::kInlineCall, args, nopos);
|
||||
if (type == IteratorType::kAsync) {
|
||||
function_state_->AddSuspend();
|
||||
call = factory()->NewAwait(call, nopos);
|
||||
}
|
||||
Expression* output_proxy = factory()->NewVariableProxy(var_output);
|
||||
@ -4288,6 +4298,7 @@ void Parser::BuildIteratorCloseForCompletion(ZoneList<Statement*>* statements,
|
||||
factory()->NewCallRuntime(Runtime::kInlineCall, args, nopos);
|
||||
|
||||
if (type == IteratorType::kAsync) {
|
||||
function_state_->AddSuspend();
|
||||
call = factory()->NewAwait(call, nopos);
|
||||
}
|
||||
|
||||
@ -4315,6 +4326,7 @@ void Parser::BuildIteratorCloseForCompletion(ZoneList<Statement*>* statements,
|
||||
Expression* call =
|
||||
factory()->NewCallRuntime(Runtime::kInlineCall, args, nopos);
|
||||
if (type == IteratorType::kAsync) {
|
||||
function_state_->AddSuspend();
|
||||
call = factory()->NewAwait(call, nopos);
|
||||
}
|
||||
|
||||
|
@ -506,7 +506,7 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
|
||||
FunctionLiteral::FunctionType function_type,
|
||||
DeclarationScope* function_scope, int* num_parameters,
|
||||
int* function_length, bool* has_duplicate_parameters,
|
||||
int* expected_property_count,
|
||||
int* expected_property_count, int* suspend_count,
|
||||
ZoneList<const AstRawString*>* arguments_for_wrapped_function, bool* ok);
|
||||
|
||||
void ThrowPendingError(Isolate* isolate, Handle<Script> script);
|
||||
|
@ -304,6 +304,7 @@ class PreParserExpression {
|
||||
int position() const { return kNoSourcePosition; }
|
||||
void set_function_token_position(int position) {}
|
||||
void set_scope(Scope* scope) {}
|
||||
void set_suspend_count(int suspend_count) {}
|
||||
|
||||
private:
|
||||
enum Type {
|
||||
|
@ -597,7 +597,7 @@ bytecodes: [
|
||||
B(Star), R(13),
|
||||
B(Mov), R(0), R(12),
|
||||
B(CallJSRuntime), U8(%async_generator_await_uncaught), R(12), U8(2),
|
||||
/* 49 E> */ B(SuspendGenerator), R(0), R(0), U8(12), U8(2),
|
||||
/* 49 E> */ B(SuspendGenerator), R(0), R(0), U8(12), U8(1),
|
||||
B(ResumeGenerator), R(0), R(0), U8(12),
|
||||
B(Star), R(12),
|
||||
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(0), U8(1),
|
||||
@ -615,7 +615,7 @@ bytecodes: [
|
||||
B(Star), R(13),
|
||||
B(Mov), R(0), R(12),
|
||||
B(CallJSRuntime), U8(%async_generator_await_uncaught), R(12), U8(2),
|
||||
/* 49 E> */ B(SuspendGenerator), R(0), R(0), U8(12), U8(3),
|
||||
/* 49 E> */ B(SuspendGenerator), R(0), R(0), U8(12), U8(2),
|
||||
B(ResumeGenerator), R(0), R(0), U8(12),
|
||||
B(Star), R(12),
|
||||
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(0), U8(1),
|
||||
@ -637,7 +637,7 @@ bytecodes: [
|
||||
B(Star), R(16),
|
||||
B(Mov), R(0), R(14),
|
||||
B(InvokeIntrinsic), U8(Runtime::k_AsyncGeneratorYield), R(14), U8(3),
|
||||
/* 49 E> */ B(SuspendGenerator), R(0), R(0), U8(14), U8(1),
|
||||
/* 49 E> */ B(SuspendGenerator), R(0), R(0), U8(14), U8(3),
|
||||
B(ResumeGenerator), R(0), R(0), U8(14),
|
||||
B(Star), R(8),
|
||||
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(0), U8(1),
|
||||
@ -719,9 +719,9 @@ bytecodes: [
|
||||
]
|
||||
constant pool: [
|
||||
Smi [30],
|
||||
Smi [310],
|
||||
Smi [201],
|
||||
Smi [251],
|
||||
Smi [310],
|
||||
Smi [360],
|
||||
Smi [15],
|
||||
Smi [7],
|
||||
|
Loading…
Reference in New Issue
Block a user