[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:
parent
75ae54c55b
commit
62a3280563
@ -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));
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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},
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user