[torque] Indexed class field initialization

Indexed fields in classes can now be initialized using iterators
and a spread syntax, e.g.:

  class Foo {
    length: Smi;
    elements[length]: Object;
  }

  new Foo{length: 5, elements: ...iter};

where iter implements Torque's iterator protocol. This protocol
requires the definition of a method with the following signature:

  Next(): <type> labels NoMore;

Where <type> is the Torque type of the values to be iterated.
In the case of indexed field initialization, the type must be
the field's type or a subtype thereof.

Field initialization with spread is desugared into a loop that
calls the spread iterator's Next method and assigns each
returned value in order to the corresponding indexed field
element.

The general machinery for the spread syntax has been added to
the ast and parser, however, it can currently only be used in
the specific context of indexed field initialization. Spread
operators used in any other context will cause an error.

Bug: v8:7793
Change-Id: If071e61db8166573c28d13318879c88ba96f6d98
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1550407
Commit-Queue: Daniel Clifford <danno@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60639}
This commit is contained in:
Daniel Clifford 2019-04-04 19:23:05 +02:00 committed by Commit Bot
parent 75ae54c55b
commit 62a3280563
8 changed files with 236 additions and 20 deletions

View File

@ -243,6 +243,20 @@ macro NewJSArray(implicit context: Context)(): JSArray {
};
}
struct HoleIterator {
Next(): Object labels NoMore() {
return Hole;
}
}
macro NewJSArray(implicit context: Context)(map: Map, length: Smi): JSArray {
const map = GetFastPackedSmiElementsJSArrayMap();
const i = HoleIterator{};
const elements = new FixedArray{map, length, objects: ...i};
return new
JSArray{map, properties_or_hash: kEmptyFixedArray, elements, length};
}
// A HeapObject with a JSArray map, and either fast packed elements, or fast
// holey elements when the global NoElementsProtector is not invalidated.
transient type FastJSArray extends JSArray;
@ -697,6 +711,8 @@ const kAllFixedArrays: constexpr ExtractFixedArrayFlags
const kFixedArrays: constexpr ExtractFixedArrayFlags
generates 'CodeStubAssembler::ExtractFixedArrayFlag::kFixedArrays';
const kFixedArrayMapRootIndex:
constexpr RootIndex generates 'RootIndex::kFixedArrayMap';
const kFixedCOWArrayMapRootIndex:
constexpr RootIndex generates 'RootIndex::kFixedCOWArrayMap';
const kEmptyFixedArrayRootIndex:
@ -1711,6 +1727,8 @@ UnsafeCast<Object>(o: Object): Object {
return o;
}
const kFixedArrayMap: Map =
%RawDownCast<Map>(LoadRoot(kFixedArrayMapRootIndex));
const kCOWMap: Map = %RawDownCast<Map>(LoadRoot(kFixedCOWArrayMapRootIndex));
const kEmptyFixedArray: FixedArray =
%RawDownCast<FixedArray>(LoadRoot(kEmptyFixedArrayRootIndex));

View File

@ -26,6 +26,7 @@ namespace torque {
V(StructExpression) \
V(LogicalOrExpression) \
V(LogicalAndExpression) \
V(SpreadExpression) \
V(ConditionalExpression) \
V(IdentifierExpression) \
V(StringLiteralExpression) \
@ -323,6 +324,13 @@ struct LogicalAndExpression : Expression {
Expression* right;
};
struct SpreadExpression : Expression {
DEFINE_AST_NODE_LEAF_BOILERPLATE(SpreadExpression)
SpreadExpression(SourcePosition pos, Expression* spreadee)
: Expression(kKind, pos), spreadee(spreadee) {}
Expression* spreadee;
};
struct ConditionalExpression : Expression {
DEFINE_AST_NODE_LEAF_BOILERPLATE(ConditionalExpression)
ConditionalExpression(SourcePosition pos, Expression* condition,

View File

@ -1250,11 +1250,28 @@ VisitResult ImplementationVisitor::Visit(StatementExpression* expr) {
}
InitializerResults ImplementationVisitor::VisitInitializerResults(
const AggregateType* current_aggregate,
const std::vector<NameAndExpression>& initializers) {
InitializerResults result;
for (const NameAndExpression& initializer : initializers) {
result.names.push_back(initializer.name);
result.results.push_back(Visit(initializer.expression));
Expression* e = initializer.expression;
const Field& field =
current_aggregate->LookupField(initializer.name->value);
auto field_index = field.index;
if (SpreadExpression* s = SpreadExpression::DynamicCast(e)) {
if (!field_index) {
ReportError(
"spread expressions can only be used to initialize indexed class "
"fields ('",
initializer.name->value, "' is not)");
}
e = s->spreadee;
} else if (field_index) {
ReportError("the indexed class field '", initializer.name->value,
"' must be initialized with a spread operator");
}
result.field_value_map[field.name_and_type.name] = Visit(e);
}
return result;
}
@ -1273,11 +1290,12 @@ size_t ImplementationVisitor::InitializeAggregateHelper(
}
for (Field f : aggregate_type->fields()) {
if (current == initializer_results.results.size()) {
if (current == initializer_results.field_value_map.size()) {
ReportError("insufficient number of initializers for ",
aggregate_type->name());
}
VisitResult current_value = initializer_results.results[current];
VisitResult current_value =
initializer_results.field_value_map.at(f.name_and_type.name);
Identifier* fieldname = initializer_results.names[current];
if (fieldname->value != f.name_and_type.name) {
CurrentSourcePosition::Scope scope(fieldname->pos);
@ -1285,11 +1303,15 @@ size_t ImplementationVisitor::InitializeAggregateHelper(
"\" instead of \"", fieldname->value, "\"");
}
if (aggregate_type->IsClassType()) {
allocate_result.SetType(aggregate_type);
GenerateCopy(allocate_result);
GenerateImplicitConvert(f.name_and_type.type, current_value);
assembler().Emit(StoreObjectFieldInstruction(
ClassType::cast(aggregate_type), f.name_and_type.name));
if (f.index) {
InitializeFieldFromSpread(allocate_result, f, initializer_results);
} else {
allocate_result.SetType(aggregate_type);
GenerateCopy(allocate_result);
GenerateImplicitConvert(f.name_and_type.type, current_value);
assembler().Emit(StoreObjectFieldInstruction(
ClassType::cast(aggregate_type), f.name_and_type.name));
}
} else {
LocationReference struct_field_ref = LocationReference::VariableAccess(
ProjectStructField(allocate_result, f.name_and_type.name));
@ -1300,17 +1322,113 @@ size_t ImplementationVisitor::InitializeAggregateHelper(
return current;
}
void ImplementationVisitor::InitializeFieldFromSpread(
VisitResult object, const Field& field,
const InitializerResults& initializer_results) {
StackScope stack_scope(this);
VisitResult zero(TypeOracle::GetConstInt31Type(), "0");
const Type* index_type = (*field.index)->name_and_type.type;
VisitResult index = GenerateImplicitConvert(index_type, zero);
Block* post_exit_block = assembler().NewBlock(assembler().CurrentStack());
Block* exit_block = assembler().NewBlock(assembler().CurrentStack());
Block* body_block = assembler().NewBlock(assembler().CurrentStack());
Block* fail_block = assembler().NewBlock(assembler().CurrentStack(), true);
Block* header_block = assembler().NewBlock(assembler().CurrentStack());
assembler().Goto(header_block);
assembler().Bind(header_block);
Arguments compare_arguments;
compare_arguments.parameters.push_back(index);
compare_arguments.parameters.push_back(initializer_results.field_value_map.at(
(*field.index)->name_and_type.name));
GenerateExpressionBranch(
[&]() { return GenerateCall("<", compare_arguments); }, body_block,
exit_block);
assembler().Bind(body_block);
{
VisitResult spreadee =
initializer_results.field_value_map.at(field.name_and_type.name);
LocationReference target = LocationReference::VariableAccess(spreadee);
Binding<LocalLabel> no_more{&LabelBindingsManager::Get(), "_Done",
LocalLabel{fail_block}};
// Call the Next() method of the iterator
Arguments next_arguments;
next_arguments.labels.push_back(&no_more);
Callable* callable = LookupMethod("Next", target, next_arguments, {});
VisitResult next_result =
GenerateCall(callable, target, next_arguments, {}, false);
Arguments assign_arguments;
assign_arguments.parameters.push_back(object);
assign_arguments.parameters.push_back(index);
assign_arguments.parameters.push_back(next_result);
GenerateCall("[]=", assign_arguments);
// Increment the indexed field index.
LocationReference index_ref = LocationReference::VariableAccess(index);
Arguments increment_arguments;
VisitResult one = {TypeOracle::GetConstInt31Type(), "1"};
increment_arguments.parameters = {index, one};
VisitResult assignment_value = GenerateCall("+", increment_arguments);
GenerateAssignToLocation(index_ref, assignment_value);
}
assembler().Goto(header_block);
assembler().Bind(fail_block);
assembler().Emit(AbortInstruction(AbortInstruction::Kind::kUnreachable));
assembler().Bind(exit_block);
assembler().Goto(post_exit_block);
assembler().Bind(post_exit_block);
}
void ImplementationVisitor::InitializeAggregate(
const AggregateType* aggregate_type, VisitResult allocate_result,
const InitializerResults& initializer_results) {
size_t consumed_initializers = InitializeAggregateHelper(
aggregate_type, allocate_result, initializer_results);
if (consumed_initializers != initializer_results.results.size()) {
if (consumed_initializers != initializer_results.field_value_map.size()) {
ReportError("more initializers than fields present in ",
aggregate_type->name());
}
}
VisitResult ImplementationVisitor::AddVariableObjectSize(
VisitResult object_size, const ClassType* current_class,
const InitializerResults& initializer_results) {
while (current_class != nullptr) {
auto current_field = current_class->fields().begin();
while (current_field != current_class->fields().end()) {
if (current_field->index) {
if (!current_field->name_and_type.type->IsSubtypeOf(
TypeOracle::GetObjectType())) {
ReportError(
"allocating objects containing indexed fields of non-object "
"types is not yet supported");
}
VisitResult index_field_size =
VisitResult(TypeOracle::GetConstInt31Type(), "kTaggedSize");
VisitResult initializer_value = initializer_results.field_value_map.at(
(*current_field->index)->name_and_type.name);
VisitResult index_intptr_size =
GenerateCall("Convert", {{initializer_value}, {}},
{TypeOracle::GetIntPtrType()}, false);
VisitResult variable_size = GenerateCall(
"*", {{index_intptr_size, index_field_size}, {}}, {}, false);
object_size =
GenerateCall("+", {{object_size, variable_size}, {}}, {}, false);
}
++current_field;
}
current_class = current_class->GetSuperClass();
}
return object_size;
}
VisitResult ImplementationVisitor::Visit(NewExpression* expr) {
StackScope stack_scope(this);
const Type* type = Declarations::GetType(expr->type);
@ -1327,21 +1445,27 @@ VisitResult ImplementationVisitor::Visit(NewExpression* expr) {
}
InitializerResults initializer_results =
VisitInitializerResults(expr->initializers);
VisitInitializerResults(class_type, expr->initializers);
// Output the code to generate an uninitialized object of the class size in
// the GC heap.
VisitResult allocate_result;
if (class_type->IsExtern()) {
if (initializer_results.results.size() == 0) {
const Field& map_field = class_type->LookupField("map");
if (map_field.offset != 0) {
ReportError(
"external classes initializers must have a map as first parameter");
}
VisitResult object_map = initializer_results.results[0];
VisitResult object_map =
initializer_results.field_value_map[map_field.name_and_type.name];
Arguments size_arguments;
size_arguments.parameters.push_back(object_map);
VisitResult object_size = GenerateCall("%GetAllocationBaseSize",
size_arguments, {class_type}, false);
object_size =
AddVariableObjectSize(object_size, class_type, initializer_results);
Arguments allocate_arguments;
allocate_arguments.parameters.push_back(object_size);
allocate_result =
@ -1432,6 +1556,12 @@ const Type* ImplementationVisitor::Visit(ForLoopStatement* stmt) {
return TypeOracle::GetVoidType();
}
VisitResult ImplementationVisitor::Visit(SpreadExpression* expr) {
ReportError(
"spread operators are only currently supported in indexed class field "
"initialization expressions");
}
void ImplementationVisitor::GenerateImplementation(const std::string& dir,
Namespace* nspace) {
std::string new_source(nspace->source());
@ -1706,10 +1836,12 @@ VisitResult ImplementationVisitor::Visit(StructExpression* expr) {
ReportError(*raw_type, " is not a struct but used like one");
}
InitializerResults initialization_results =
ImplementationVisitor::VisitInitializerResults(expr->initializers);
const StructType* struct_type = StructType::cast(raw_type);
InitializerResults initialization_results =
ImplementationVisitor::VisitInitializerResults(struct_type,
expr->initializers);
// Push uninitialized 'this'
VisitResult result = TemporaryUninitializedStruct(
struct_type, "it's not initialized in the struct " + struct_type->name());
@ -2331,9 +2463,8 @@ void ImplementationVisitor::GenerateBranch(const VisitResult& condition,
assembler().Branch(true_block, false_block);
}
void ImplementationVisitor::GenerateExpressionBranch(Expression* expression,
Block* true_block,
Block* false_block) {
void ImplementationVisitor::GenerateExpressionBranch(
VisitResultGenerator generator, Block* true_block, Block* false_block) {
// Conditional expressions can either explicitly return a bit
// type, or they can be backed by macros that don't return but
// take a true and false label. By declaring the labels before
@ -2345,7 +2476,7 @@ void ImplementationVisitor::GenerateExpressionBranch(Expression* expression,
Binding<LocalLabel> false_binding{&LabelBindingsManager::Get(),
kFalseLabelName, LocalLabel{false_block}};
StackScope stack_scope(this);
VisitResult expression_result = Visit(expression);
VisitResult expression_result = generator();
if (!expression_result.type()->IsNever()) {
expression_result = stack_scope.Yield(
GenerateImplicitConvert(TypeOracle::GetBoolType(), expression_result));
@ -2353,6 +2484,13 @@ void ImplementationVisitor::GenerateExpressionBranch(Expression* expression,
}
}
void ImplementationVisitor::GenerateExpressionBranch(Expression* expression,
Block* true_block,
Block* false_block) {
GenerateExpressionBranch([&]() { return this->Visit(expression); },
true_block, false_block);
}
VisitResult ImplementationVisitor::GenerateImplicitConvert(
const Type* destination_type, VisitResult source) {
StackScope scope(this);

View File

@ -141,7 +141,7 @@ class LocationReference {
struct InitializerResults {
std::vector<Identifier*> names;
std::vector<VisitResult> results;
NameValueMap field_value_map;
};
template <class T>
@ -262,12 +262,20 @@ class ImplementationVisitor : public FileVisitor {
const Type* Visit(Statement* stmt);
InitializerResults VisitInitializerResults(
const AggregateType* aggregate,
const std::vector<NameAndExpression>& expressions);
void InitializeFieldFromSpread(VisitResult object, const Field& field,
const InitializerResults& initializer_results);
size_t InitializeAggregateHelper(
const AggregateType* aggregate_type, VisitResult allocate_result,
const InitializerResults& initializer_results);
VisitResult AddVariableObjectSize(
VisitResult object_size, const ClassType* current_class,
const InitializerResults& initializer_results);
void InitializeAggregate(const AggregateType* aggregate_type,
VisitResult allocate_result,
const InitializerResults& initializer_results);
@ -328,6 +336,7 @@ class ImplementationVisitor : public FileVisitor {
VisitResult Visit(TryLabelExpression* expr);
VisitResult Visit(StatementExpression* expr);
VisitResult Visit(NewExpression* expr);
VisitResult Visit(SpreadExpression* expr);
const Type* Visit(ReturnStatement* stmt);
const Type* Visit(GotoStatement* stmt);
@ -501,6 +510,9 @@ class ImplementationVisitor : public FileVisitor {
void GenerateBranch(const VisitResult& condition, Block* true_block,
Block* false_block);
typedef std::function<VisitResult()> VisitResultGenerator;
void GenerateExpressionBranch(VisitResultGenerator, Block* true_block,
Block* false_block);
void GenerateExpressionBranch(Expression* expression, Block* true_block,
Block* false_block);

View File

@ -315,6 +315,13 @@ base::Optional<ParseResult> MakeUnaryOperator(
std::vector<Statement*>{})};
}
base::Optional<ParseResult> MakeSpreadExpression(
ParseResultIterator* child_results) {
auto spreadee = child_results->NextAs<Expression*>();
Expression* result = MakeNode<SpreadExpression>(spreadee);
return ParseResult{result};
}
template <bool has_varargs>
base::Optional<ParseResult> MakeParameterListFromTypes(
ParseResultIterator* child_results) {
@ -1450,6 +1457,7 @@ struct TorqueGrammar : Grammar {
Symbol unaryExpression = {
Rule({&primaryExpression}),
Rule({OneOf({"+", "-", "!", "~"}), &unaryExpression}, MakeUnaryOperator),
Rule({Token("..."), &unaryExpression}, MakeSpreadExpression),
Rule({&incrementDecrementOperator, &locationExpression},
MakeIncrementDecrementExpressionPrefix),
Rule({&locationExpression, &incrementDecrementOperator},

View File

@ -532,6 +532,8 @@ class VisitResult {
base::Optional<StackRange> stack_range_;
};
typedef std::map<std::string, VisitResult> NameValueMap;
VisitResult ProjectStructField(VisitResult structure,
const std::string& fieldname);

View File

@ -455,6 +455,23 @@ TEST(TestInternalClass) {
ft.Call();
}
TEST(TestNewFixedArrayFromSpread) {
CcTest::InitializeVM();
Isolate* isolate(CcTest::i_isolate());
i::HandleScope scope(isolate);
Handle<Context> context =
Utils::OpenHandle(*v8::Isolate::GetCurrent()->GetCurrentContext());
CodeAssemblerTester asm_tester(isolate);
TestTorqueAssembler m(asm_tester.state());
{
m.TestNewFixedArrayFromSpread(
m.UncheckedCast<Context>(m.HeapConstant(context)));
m.Return(m.UndefinedConstant());
}
FunctionTester ft(asm_tester.GenerateCode(), 0);
ft.Call();
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -845,4 +845,17 @@ namespace test {
y.a = Undefined;
const copy = x;
}
struct TestIterator {
Next(): Object labels NoMore {
if (this.count-- == 0) goto NoMore;
return Hole;
}
count: Smi;
}
macro TestNewFixedArrayFromSpread(implicit context: Context)(): Object {
const i = TestIterator{count: 5};
return new FixedArray{map: kFixedArrayMap, length: 5, objects: ...i};
}
}