[torque] Allow atomarStatements in otherwise statements

In the process:
- Convert TryLabelStatements into TryLabelExpressions
- Change TryLabelExpressions to support only single label blocks and de-sugar
  try/labels into nested try/label statements. This allows the code in a label
  block to goto subsequent labels in the same try/label statement.
- Make otherwise expressions either take IdentifierExpressions which get
  converted into simple label names OR atomarStatements, which make useful
  non-label operations, like 'break' and 'continue', useful together with
  otherwise. Non-label otherwise statements get de-sugared into try/label
  blocks.

Bug: v8:7793
Change-Id: Ie56ede6306e2a3182f6aa1bb8750ed418bda01db
Reviewed-on: https://chromium-review.googlesource.com/c/1266997
Commit-Queue: Daniel Clifford <danno@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56447}
This commit is contained in:
Daniel Clifford 2018-10-08 15:50:46 +02:00 committed by Commit Bot
parent 3efbaf8eb1
commit 6f5600e256
9 changed files with 230 additions and 114 deletions

View File

@ -968,21 +968,19 @@ macro ToIndex(input: Object, context: Context): Number
}
macro GetLengthProperty(context: Context, o: Object): Number {
let length: Object;
try {
try {
return (Cast<JSArray>(o) otherwise CheckArgs).length;
}
label CheckArgs {
const a: JSArgumentsObjectWithLength =
Cast<JSArgumentsObjectWithLength>(context, o) otherwise Slow;
return Cast<Smi>(length = a.length) otherwise ToLength;
}
return (Cast<JSArray>(o) otherwise CheckArgs).length;
}
label CheckArgs {
const a: JSArgumentsObjectWithLength =
Cast<JSArgumentsObjectWithLength>(context, o) otherwise Slow;
const length: Object = a.length;
return Cast<Smi>(length) otherwise goto ToLength(length);
}
label Slow deferred {
return ToLength_Inline(context, GetProperty(context, o, kLengthString));
goto ToLength(GetProperty(context, o, kLengthString));
}
label ToLength deferred {
label ToLength(length: Object) deferred {
return ToLength_Inline(context, length);
}
}

View File

@ -30,7 +30,9 @@ namespace torque {
V(ElementAccessExpression) \
V(AssignmentExpression) \
V(IncrementDecrementExpression) \
V(AssumeTypeImpossibleExpression)
V(AssumeTypeImpossibleExpression) \
V(StatementExpression) \
V(TryLabelExpression)
#define AST_TYPE_EXPRESSION_NODE_KIND_LIST(V) \
V(BasicTypeExpression) \
@ -52,7 +54,6 @@ namespace torque {
V(TailCallStatement) \
V(VarDeclarationStatement) \
V(GotoStatement) \
V(TryLabelStatement)
#define AST_DECLARATION_NODE_KIND_LIST(V) \
V(TypeDeclaration) \
@ -553,15 +554,22 @@ struct LabelBlock : AstNode {
Statement* body;
};
struct TryLabelStatement : Statement {
DEFINE_AST_NODE_LEAF_BOILERPLATE(TryLabelStatement)
TryLabelStatement(SourcePosition pos, Statement* try_block,
std::vector<LabelBlock*> label_blocks)
: Statement(kKind, pos),
try_block(try_block),
label_blocks(std::move(label_blocks)) {}
Statement* try_block;
std::vector<LabelBlock*> label_blocks;
struct StatementExpression : Expression {
DEFINE_AST_NODE_LEAF_BOILERPLATE(StatementExpression)
StatementExpression(SourcePosition pos, Statement* statement)
: Expression(kKind, pos), statement(statement) {}
Statement* statement;
};
struct TryLabelExpression : Expression {
DEFINE_AST_NODE_LEAF_BOILERPLATE(TryLabelExpression)
TryLabelExpression(SourcePosition pos, Expression* try_expression,
LabelBlock* label_block)
: Expression(kKind, pos),
try_expression(try_expression),
label_block(label_block) {}
Expression* try_expression;
LabelBlock* label_block;
};
struct BlockStatement : Statement {

View File

@ -402,14 +402,15 @@ void DeclarationVisitor::Visit(ForLoopStatement* stmt) {
if (stmt->action) Visit(*stmt->action);
}
void DeclarationVisitor::Visit(TryLabelStatement* stmt) {
// Activate a new scope to declare handler labels, they should not be
// visible outside the label block.
void DeclarationVisitor::Visit(TryLabelExpression* stmt) {
// Activate a new scope to declare the handler's label parameters, they should
// not be visible outside the label block.
{
Declarations::NodeScopeActivator scope(declarations(), stmt);
// Declare labels
for (LabelBlock* block : stmt->label_blocks) {
// Declare label
{
LabelBlock* block = stmt->label_block;
CurrentSourcePosition::Scope scope(block->pos);
Label* shared_label =
declarations()->DeclareLabel(block->label, block->body);
@ -432,18 +433,16 @@ void DeclarationVisitor::Visit(TryLabelStatement* stmt) {
shared_label->AddVariable(DeclareVariable(p, type, false));
++i;
}
}
if (global_context_.verbose()) {
std::cout << " declaring label " << block->label << "\n";
if (global_context_.verbose()) {
std::cout << " declaring label " << block->label << "\n";
}
}
}
Visit(stmt->try_block);
Visit(stmt->try_expression);
}
for (LabelBlock* block : stmt->label_blocks) {
Visit(block->body);
}
Visit(stmt->label_block->body);
}
void DeclarationVisitor::GenerateHeader(std::string& file_name) {
@ -516,6 +515,10 @@ void DeclarationVisitor::Visit(IdentifierExpression* expr) {
}
}
void DeclarationVisitor::Visit(StatementExpression* expr) {
Visit(expr->statement);
}
void DeclarationVisitor::Visit(CallExpression* expr) {
Visit(&expr->callee);
for (Expression* arg : expr->arguments) Visit(arg);

View File

@ -139,7 +139,8 @@ class DeclarationVisitor : public FileVisitor {
void Visit(AssumeTypeImpossibleExpression* expr) { Visit(expr->expression); }
void Visit(TryLabelStatement* stmt);
void Visit(TryLabelExpression* stmt);
void Visit(StatementExpression* stmt);
void GenerateHeader(std::string& file_name);
private:

View File

@ -80,6 +80,7 @@ void ImplementationVisitor::BeginModuleFile(Module* module) {
DashifyString(module->name()) + "-gen.h\"";
}
source << "\n";
source << "#include \"src/objects/arguments.h\"\n";
source << "#include \"src/builtins/builtins-utils-gen.h\"\n";
source << "#include \"src/builtins/builtins.h\"\n";
source << "#include \"src/code-factory.h\"\n";
@ -1081,19 +1082,19 @@ const Type* ImplementationVisitor::Visit(ForOfLoopStatement* stmt) {
return TypeOracle::GetVoidType();
}
const Type* ImplementationVisitor::Visit(TryLabelStatement* stmt) {
VisitResult ImplementationVisitor::Visit(TryLabelExpression* expr) {
Block* done_block = assembler().NewBlock();
const Type* try_result = TypeOracle::GetNeverType();
std::vector<Label*> labels;
VisitResult try_result;
Label* label = nullptr;
// Output labels for the goto handlers and for the merge after the try.
{
// Activate a new scope to see handler labels
Declarations::NodeScopeActivator scope(declarations(), stmt);
for (LabelBlock* block : stmt->label_blocks) {
Declarations::NodeScopeActivator scope(declarations(), expr);
{
LabelBlock* block = expr->label_block;
CurrentSourcePosition::Scope source_position(block->pos);
Label* label = declarations()->LookupLabel(block->label);
labels.push_back(label);
label = declarations()->LookupLabel(block->label);
Declarations::NodeScopeActivator scope(declarations(), block->body);
Stack<const Type*> label_input_stack = assembler().CurrentStack();
@ -1108,42 +1109,48 @@ const Type* ImplementationVisitor::Visit(TryLabelStatement* stmt) {
// Visit try
{
StackScope stack_scope(this);
try_result = Visit(stmt->try_block);
try_result = Visit(expr->try_expression);
if (try_result.type() != TypeOracle::GetNeverType()) {
try_result = stack_scope.Yield(try_result);
assembler().Goto(done_block);
}
}
if (try_result != TypeOracle::GetNeverType()) {
}
if (label->IsUsed()) {
// Visit and output the code for the label block. If the label block falls
// through, then the try must not return a value. Also, if the try doesn't
// fall through, but the label does, then overall the try-label block
// returns type void.
GenerateLabelBind(label);
const Type* label_result;
{
StackScope stack_scope(this);
label_result = Visit(expr->label_block->body);
}
if (!try_result.type()->IsVoidOrNever() && label_result->IsVoid()) {
ReportError(
"otherwise clauses cannot fall through in a non-void expression");
}
if (label_result != TypeOracle::GetNeverType()) {
assembler().Goto(done_block);
}
}
// Make sure that each label clause is actually used. It's not just a friendly
// thing to do, it will cause problems downstream in the compiler if there are
// bound labels that are never jumped to.
auto label_iterator = stmt->label_blocks.begin();
for (auto label : labels) {
CurrentSourcePosition::Scope scope((*label_iterator)->pos);
if (!label->IsUsed()) {
std::stringstream s;
s << "label ";
s << (*label_iterator)->label;
s << " has a handler block but is never referred to in try block";
ReportError(s.str());
if (label_result->IsVoid() && try_result.type()->IsNever()) {
try_result =
VisitResult(TypeOracle::GetVoidType(), try_result.stack_range());
}
label_iterator++;
}
// Visit and output the code for each label block, one-by-one.
std::vector<Statement*> bodies;
for (LabelBlock* block : stmt->label_blocks) bodies.push_back(block->body);
if (GenerateLabeledStatementBlocks(bodies, labels, done_block)) {
try_result = TypeOracle::GetVoidType();
}
if (!try_result->IsNever()) {
if (!try_result.type()->IsNever()) {
assembler().Bind(done_block);
}
return try_result;
}
VisitResult ImplementationVisitor::Visit(StatementExpression* expr) {
return VisitResult{Visit(expr->statement), assembler().TopRange(0)};
}
const Type* ImplementationVisitor::Visit(BreakStatement* stmt) {
Block* break_block = global_context_.GetCurrentBreak();
if (break_block == nullptr) {

View File

@ -177,8 +177,9 @@ class ImplementationVisitor : public FileVisitor {
VisitResult Visit(StringLiteralExpression* expr);
VisitResult Visit(NumberLiteralExpression* expr);
VisitResult Visit(AssumeTypeImpossibleExpression* expr);
VisitResult Visit(TryLabelExpression* expr);
VisitResult Visit(StatementExpression* expr);
const Type* Visit(TryLabelStatement* stmt);
const Type* Visit(ReturnStatement* stmt);
const Type* Visit(GotoStatement* stmt);
const Type* Visit(IfStatement* stmt);

View File

@ -201,14 +201,50 @@ void CheckNotDeferredStatement(Statement* statement) {
}
}
Expression* MakeCall(const std::string& callee, bool is_operator,
const std::vector<TypeExpression*>& generic_arguments,
const std::vector<Expression*>& arguments,
const std::vector<Statement*>& otherwise) {
std::vector<std::string> labels;
// All IdentifierExpressions are treated as label names and can be directly
// used as labels identifiers. All other statements in a call's otherwise
// must create intermediate Labels for the otherwise's statement code.
size_t label_id = 0;
std::vector<LabelBlock*> temp_labels;
for (auto* statement : otherwise) {
if (auto* e = ExpressionStatement::DynamicCast(statement)) {
if (auto* id = IdentifierExpression::DynamicCast(e->expression)) {
if (id->generic_arguments.size() != 0) {
ReportError("An otherwise label cannot have generic parameters");
}
labels.push_back(id->name);
continue;
}
}
auto label_name = std::string("_label") + std::to_string(label_id++);
labels.push_back(label_name);
auto* label_block =
MakeNode<LabelBlock>(label_name, ParameterList::Empty(), statement);
temp_labels.push_back(label_block);
}
// Create nested try-label expression for all of the temporary Labels that
// were created.
Expression* result = MakeNode<CallExpression>(
callee, false, generic_arguments, arguments, labels);
for (auto* label : temp_labels) {
result = MakeNode<TryLabelExpression>(result, label);
}
return result;
}
base::Optional<ParseResult> MakeCall(ParseResultIterator* child_results) {
auto callee = child_results->NextAs<std::string>();
auto generic_args = child_results->NextAs<TypeList>();
auto args = child_results->NextAs<std::vector<Expression*>>();
auto labels = child_results->NextAs<std::vector<std::string>>();
Expression* result =
MakeNode<CallExpression>(callee, false, generic_args, args, labels);
return ParseResult{result};
auto otherwise = child_results->NextAs<std::vector<Statement*>>();
return ParseResult{MakeCall(callee, false, generic_args, args, otherwise)};
}
base::Optional<ParseResult> MakeBinaryOperator(
@ -216,20 +252,17 @@ base::Optional<ParseResult> MakeBinaryOperator(
auto left = child_results->NextAs<Expression*>();
auto op = child_results->NextAs<std::string>();
auto right = child_results->NextAs<Expression*>();
Expression* result = MakeNode<CallExpression>(
op, true, TypeList{}, std::vector<Expression*>{left, right},
std::vector<std::string>{});
return ParseResult{result};
return ParseResult{MakeCall(op, true, TypeList{},
std::vector<Expression*>{left, right},
std::vector<Statement*>{})};
}
base::Optional<ParseResult> MakeUnaryOperator(
ParseResultIterator* child_results) {
auto op = child_results->NextAs<std::string>();
auto e = child_results->NextAs<Expression*>();
Expression* result = MakeNode<CallExpression>(op, true, TypeList{},
std::vector<Expression*>{e},
std::vector<std::string>{});
return ParseResult{result};
return ParseResult{MakeCall(op, true, TypeList{}, std::vector<Expression*>{e},
std::vector<Statement*>{})};
}
template <bool has_varargs>
@ -584,10 +617,11 @@ base::Optional<ParseResult> MakeTypeswitchStatement(
}
BlockStatement* case_block;
if (i < cases.size() - 1) {
value = MakeNode<CallExpression>(
"Cast", false, std::vector<TypeExpression*>{cases[i].type},
std::vector<Expression*>{value},
std::vector<std::string>{"_NextCase"});
value =
MakeCall("Cast", false, std::vector<TypeExpression*>{cases[i].type},
std::vector<Expression*>{value},
std::vector<Statement*>{MakeNode<ExpressionStatement>(
MakeNode<IdentifierExpression>("_NextCase"))});
case_block = MakeNode<BlockStatement>();
} else {
case_block = current_block;
@ -599,9 +633,11 @@ base::Optional<ParseResult> MakeTypeswitchStatement(
case_block->statements.push_back(cases[i].block);
if (i < cases.size() - 1) {
BlockStatement* next_block = MakeNode<BlockStatement>();
current_block->statements.push_back(MakeNode<TryLabelStatement>(
case_block, std::vector<LabelBlock*>{MakeNode<LabelBlock>(
"_NextCase", ParameterList::Empty(), next_block)}));
current_block->statements.push_back(
MakeNode<ExpressionStatement>(MakeNode<TryLabelExpression>(
MakeNode<StatementExpression>(case_block),
MakeNode<LabelBlock>("_NextCase", ParameterList::Empty(),
next_block))));
current_block = next_block;
}
accumulated_types =
@ -692,13 +728,16 @@ base::Optional<ParseResult> MakeBlockStatement(
return ParseResult{result};
}
base::Optional<ParseResult> MakeTryLabelStatement(
base::Optional<ParseResult> MakeTryLabelExpression(
ParseResultIterator* child_results) {
auto try_block = child_results->NextAs<Statement*>();
CheckNotDeferredStatement(try_block);
Statement* result = try_block;
auto label_blocks = child_results->NextAs<std::vector<LabelBlock*>>();
Statement* result =
MakeNode<TryLabelStatement>(try_block, std::move(label_blocks));
for (auto block : label_blocks) {
result = MakeNode<ExpressionStatement>(MakeNode<TryLabelExpression>(
MakeNode<StatementExpression>(result), block));
}
return ParseResult{result};
}
@ -1028,10 +1067,10 @@ struct TorqueGrammar : Grammar {
Sequence({Token("labels"),
NonemptyList<LabelAndTypes>(&labelParameter, Token(","))}))};
// Result: std::vector<std::string>
Symbol* optionalOtherwise{TryOrDefault<std::vector<std::string>>(
// Result: std::vector<Statement*>
Symbol* optionalOtherwise{TryOrDefault<std::vector<Statement*>>(
Sequence({Token("otherwise"),
NonemptyList<std::string>(&identifier, Token(","))}))};
NonemptyList<Statement*>(&atomarStatement, Token(","))}))};
// Result: NameAndTypeExpression
Symbol nameAndType = {
@ -1212,28 +1251,27 @@ struct TorqueGrammar : Grammar {
expression},
MakeVarDeclarationStatement)};
// Disallow ambiguous dangling else by only allowing an {atomarStatement} as
// a then-clause. Result: Statement*
// Result: Statement*
Symbol atomarStatement = {
Rule({&block}),
Rule({expression, Token(";")}, MakeExpressionStatement),
Rule({Token("return"), Optional<Expression*>(expression), Token(";")},
Rule({expression}, MakeExpressionStatement),
Rule({Token("return"), Optional<Expression*>(expression)},
MakeReturnStatement),
Rule({Token("tail"), &callExpression, Token(";")}, MakeTailCallStatement),
Rule({Token("break"), Token(";")}, MakeBreakStatement),
Rule({Token("continue"), Token(";")}, MakeContinueStatement),
Rule({Token("tail"), &callExpression}, MakeTailCallStatement),
Rule({Token("break")}, MakeBreakStatement),
Rule({Token("continue")}, MakeContinueStatement),
Rule({Token("goto"), &identifier,
TryOrDefault<std::vector<Expression*>>(&argumentList), Token(";")},
TryOrDefault<std::vector<Expression*>>(&argumentList)},
MakeGotoStatement),
Rule({OneOf({"debug", "unreachable"}), Token(";")}, MakeDebugStatement)};
Rule({OneOf({"debug", "unreachable"})}, MakeDebugStatement)};
// Result: Statement*
Symbol statement = {
Rule({&atomarStatement}),
Rule({&block}),
Rule({&atomarStatement, Token(";")}),
Rule({&varDeclaration, Token(";")}),
Rule({&varDeclarationWithInitialization, Token(";")}),
Rule({Token("if"), CheckIf(Token("constexpr")), Token("("), expression,
Token(")"), &atomarStatement,
Token(")"), &statement,
Optional<Statement*>(Sequence({Token("else"), &statement}))},
MakeIfStatement),
Rule(
@ -1244,21 +1282,19 @@ struct TorqueGrammar : Grammar {
},
MakeTypeswitchStatement),
Rule({Token("try"), &block, NonemptyList<LabelBlock*>(&labelBlock)},
MakeTryLabelStatement),
MakeTryLabelExpression),
Rule({OneOf({"assert", "check"}), Token("("), &expressionWithSource,
Token(")"), Token(";")},
MakeAssertStatement),
Rule({Token("while"), Token("("), expression, Token(")"),
&atomarStatement},
Rule({Token("while"), Token("("), expression, Token(")"), &statement},
MakeWhileStatement),
Rule({Token("for"), Token("("), &varDeclaration, Token("of"), expression,
Optional<RangeExpression>(&rangeSpecifier), Token(")"),
&atomarStatement},
Optional<RangeExpression>(&rangeSpecifier), Token(")"), &statement},
MakeForOfLoopStatement),
Rule({Token("for"), Token("("),
Optional<Statement*>(&varDeclarationWithInitialization), Token(";"),
Optional<Expression*>(expression), Token(";"),
Optional<Expression*>(expression), Token(")"), &atomarStatement},
Optional<Expression*>(expression), Token(")"), &statement},
MakeForLoopStatement)};
// Result: TypeswitchCase

View File

@ -271,6 +271,21 @@ TEST(TestLogicalOperators) {
ft.Call();
}
TEST(TestOtherwiseAndLabels) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate, 0);
TestBuiltinsFromDSLAssembler m(asm_tester.state());
{
m.TestOtherwiseWithCode1();
m.TestOtherwiseWithCode2();
m.TestOtherwiseWithCode3();
m.TestForwardLabel();
m.Return(m.UndefinedConstant());
}
FunctionTester ft(asm_tester.GenerateCode(), 0);
ft.Call();
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -154,9 +154,9 @@ module test {
check(GenericMacroTest<Object>(Null) == Null);
check(GenericMacroTest<Object>(False) == False);
check(GenericMacroTest<Object>(True) == True);
check(GenericMacroTestWithLabels<Smi>(0) otherwise Fail == Undefined);
check(GenericMacroTestWithLabels<Smi>(0) otherwise Fail == Undefined);
check(GenericMacroTestWithLabels<Object>(smi0) otherwise Fail == smi0);
check((GenericMacroTestWithLabels<Smi>(0) otherwise Fail) == Undefined);
check((GenericMacroTestWithLabels<Smi>(0) otherwise Fail) == Undefined);
check((GenericMacroTestWithLabels<Object>(smi0) otherwise Fail) == smi0);
try {
GenericMacroTestWithLabels<Object>(False) otherwise Expected;
}
@ -541,4 +541,51 @@ module test {
check(!TestOrAnd2(false, false, false));
check(!TestOrAnd3(false, false, false));
}
macro TestCall(i: Smi): Smi
labels A {
if (i < 5) return i;
goto A;
}
macro TestOtherwiseWithCode1() {
let v: Smi = 0;
let s: Smi = 1;
try {
TestCall(10) otherwise goto B(++s);
}
label B(v1: Smi) {
v = v1;
}
assert(v == 2);
}
macro TestOtherwiseWithCode2() {
let s: Smi = 0;
for (let i: Smi = 0; i < 10; ++i) {
TestCall(i) otherwise break;
++s;
}
assert(s == 5);
}
macro TestOtherwiseWithCode3() {
let s: Smi = 0;
for (let i: Smi = 0; i < 10; ++i) {
s += TestCall(i) otherwise break;
}
assert(s == 10);
}
macro TestForwardLabel() {
try {
goto A;
}
label A {
goto B(5);
}
label B(b: Smi) {
assert(b == 5);
}
}
}