[torque] implement initialization of bitfield structs

This change allows Torque code to initialize bitfield structs, using the
same syntax as struct initialization. It also moves the definition of
the JSPromise flags to Torque as an example usage.

Bug: v8:7793
Change-Id: I3d5e49aa22139ffb4b8ea9f308dd36a2d22b2c1b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2148176
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67338}
This commit is contained in:
Seth Brenith 2020-04-22 10:05:44 -07:00 committed by Commit Bot
parent c948f08f9b
commit 28a1532643
17 changed files with 216 additions and 105 deletions

View File

@ -886,6 +886,9 @@ extern macro SmiUntag(Smi): intptr;
macro SmiUntag<T: type>(value: SmiTagged<T>): T {
return %RawDownCast<T>(Unsigned(SmiToInt32(Convert<Smi>(value))));
}
macro SmiTag<T : type extends uint31>(value: T): SmiTagged<T> {
return %RawDownCast<SmiTagged<T>>(SmiFromUint32(value));
}
extern macro SmiToInt32(Smi): int32;
extern macro TaggedIndexToIntPtr(TaggedIndex): intptr;
extern macro IntPtrToTaggedIndex(intptr): TaggedIndex;

View File

@ -90,6 +90,10 @@ FromConstexpr<LanguageModeSmi, constexpr LanguageMode>(
c: constexpr LanguageMode): LanguageModeSmi {
return %RawDownCast<LanguageModeSmi>(SmiConstant(c));
}
FromConstexpr<PromiseState, constexpr PromiseState>(c: constexpr PromiseState):
PromiseState {
return %RawDownCast<PromiseState>(Int32Constant(c));
}
macro Convert<To: type, From: type>(i: From): To {
return i;

View File

@ -28,9 +28,13 @@ namespace promise {
@export
macro PromiseInit(promise: JSPromise): void {
assert(PromiseState::kPending == 0);
promise.reactions_or_result = kZero;
promise.flags = 0;
promise.flags = SmiTag(JSPromiseFlags{
status: PromiseState::kPending,
has_handler: false,
handled_hint: false,
async_task_id: 0
});
promise_internal::ZeroOutEmbedderOffsets(promise);
}
@ -46,7 +50,12 @@ namespace promise {
promise.properties_or_hash = kEmptyFixedArray;
promise.elements = kEmptyFixedArray;
promise.reactions_or_result = kZero;
promise.flags = 0;
promise.flags = SmiTag(JSPromiseFlags{
status: PromiseState::kPending,
has_handler: false,
handled_hint: false,
async_task_id: 0
});
return promise;
}
@ -111,7 +120,6 @@ namespace promise {
transitioning macro NewJSPromise(implicit context: Context)(
status: constexpr PromiseState, result: JSAny): JSPromise {
assert(status != PromiseState::kPending);
assert(kJSPromiseStatusShift == 0);
const instance = InnerNewJSPromise();
instance.reactions_or_result = result;

View File

@ -7419,25 +7419,39 @@ TNode<UintPtrT> CodeStubAssembler::DecodeWord(SloppyTNode<WordT> word,
TNode<Word32T> CodeStubAssembler::UpdateWord32(TNode<Word32T> word,
TNode<Uint32T> value,
uint32_t shift, uint32_t mask) {
uint32_t shift, uint32_t mask,
bool starts_as_zero) {
DCHECK_EQ((mask >> shift) << shift, mask);
// Ensure the {value} fits fully in the mask.
CSA_ASSERT(this, Uint32LessThanOrEqual(value, Uint32Constant(mask >> shift)));
TNode<Word32T> encoded_value = Word32Shl(value, Int32Constant(shift));
TNode<Word32T> inverted_mask = Int32Constant(~mask);
return Word32Or(Word32And(word, inverted_mask), encoded_value);
TNode<Word32T> masked_word;
if (starts_as_zero) {
CSA_ASSERT(this, Word32Equal(Word32And(word, Int32Constant(~mask)), word));
masked_word = word;
} else {
masked_word = Word32And(word, Int32Constant(~mask));
}
return Word32Or(masked_word, encoded_value);
}
TNode<WordT> CodeStubAssembler::UpdateWord(TNode<WordT> word,
TNode<UintPtrT> value,
uint32_t shift, uintptr_t mask) {
uint32_t shift, uintptr_t mask,
bool starts_as_zero) {
DCHECK_EQ((mask >> shift) << shift, mask);
// Ensure the {value} fits fully in the mask.
CSA_ASSERT(this,
UintPtrLessThanOrEqual(value, UintPtrConstant(mask >> shift)));
TNode<WordT> encoded_value = WordShl(value, static_cast<int>(shift));
TNode<IntPtrT> inverted_mask = IntPtrConstant(~static_cast<intptr_t>(mask));
return WordOr(WordAnd(word, inverted_mask), encoded_value);
TNode<WordT> masked_word;
if (starts_as_zero) {
CSA_ASSERT(this, WordEqual(WordAnd(word, UintPtrConstant(~mask)), word));
masked_word = word;
} else {
masked_word = WordAnd(word, UintPtrConstant(~mask));
}
return WordOr(masked_word, encoded_value);
}
void CodeStubAssembler::SetCounter(StatsCounter* counter, int value) {

View File

@ -2827,39 +2827,48 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
// Returns a node that contains the updated values of a |BitField|.
template <typename BitField>
TNode<Word32T> UpdateWord32(TNode<Word32T> word, TNode<Uint32T> value) {
return UpdateWord32(word, value, BitField::kShift, BitField::kMask);
TNode<Word32T> UpdateWord32(TNode<Word32T> word, TNode<Uint32T> value,
bool starts_as_zero = false) {
return UpdateWord32(word, value, BitField::kShift, BitField::kMask,
starts_as_zero);
}
// Returns a node that contains the updated values of a |BitField|.
template <typename BitField>
TNode<WordT> UpdateWord(TNode<WordT> word, TNode<UintPtrT> value) {
return UpdateWord(word, value, BitField::kShift, BitField::kMask);
TNode<WordT> UpdateWord(TNode<WordT> word, TNode<UintPtrT> value,
bool starts_as_zero = false) {
return UpdateWord(word, value, BitField::kShift, BitField::kMask,
starts_as_zero);
}
// Returns a node that contains the updated values of a |BitField|.
template <typename BitField>
TNode<Word32T> UpdateWordInWord32(TNode<Word32T> word,
TNode<UintPtrT> value) {
return UncheckedCast<Uint32T>(TruncateIntPtrToInt32(
Signed(UpdateWord<BitField>(ChangeUint32ToWord(word), value))));
TNode<Word32T> UpdateWordInWord32(TNode<Word32T> word, TNode<UintPtrT> value,
bool starts_as_zero = false) {
return UncheckedCast<Uint32T>(
TruncateIntPtrToInt32(Signed(UpdateWord<BitField>(
ChangeUint32ToWord(word), value, starts_as_zero))));
}
// Returns a node that contains the updated values of a |BitField|.
template <typename BitField>
TNode<WordT> UpdateWord32InWord(TNode<WordT> word, TNode<Uint32T> value) {
return UpdateWord<BitField>(word, ChangeUint32ToWord(value));
TNode<WordT> UpdateWord32InWord(TNode<WordT> word, TNode<Uint32T> value,
bool starts_as_zero = false) {
return UpdateWord<BitField>(word, ChangeUint32ToWord(value),
starts_as_zero);
}
// Returns a node that contains the updated {value} inside {word} starting
// at {shift} and fitting in {mask}.
TNode<Word32T> UpdateWord32(TNode<Word32T> word, TNode<Uint32T> value,
uint32_t shift, uint32_t mask);
uint32_t shift, uint32_t mask,
bool starts_as_zero = false);
// Returns a node that contains the updated {value} inside {word} starting
// at {shift} and fitting in {mask}.
TNode<WordT> UpdateWord(TNode<WordT> word, TNode<UintPtrT> value,
uint32_t shift, uintptr_t mask);
uint32_t shift, uintptr_t mask,
bool starts_as_zero = false);
// Returns true if any of the |T|'s bits in given |word32| are set.
template <typename T>

View File

@ -18,8 +18,8 @@ namespace internal {
TQ_OBJECT_CONSTRUCTORS_IMPL(JSPromise)
BOOL_ACCESSORS(JSPromise, flags, has_handler, kHasHandlerBit)
BOOL_ACCESSORS(JSPromise, flags, handled_hint, kHandledHintBit)
BOOL_ACCESSORS(JSPromise, flags, has_handler, HasHandlerBit::kShift)
BOOL_ACCESSORS(JSPromise, flags, handled_hint, HandledHintBit::kShift)
Object JSPromise::result() const {
DCHECK_NE(Promise::kPending, status());

View File

@ -7,6 +7,7 @@
#include "src/objects/js-objects.h"
#include "src/objects/promise.h"
#include "torque-generated/bit-fields-tq.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
@ -64,15 +65,8 @@ class JSPromise : public TorqueGeneratedJSPromise<JSPromise, JSObject> {
kHeaderSize + v8::Promise::kEmbedderFieldCount * kEmbedderDataSlotSize;
// Flags layout.
// The first two bits store the v8::Promise::PromiseState.
static const int kStatusBits = 2;
static const int kHasHandlerBit = 2;
static const int kHandledHintBit = 3;
using AsyncTaskIdField = base::BitField<int, kHandledHintBit + 1, 22>;
DEFINE_TORQUE_GENERATED_JS_PROMISE_FLAGS()
static const int kStatusShift = 0;
static const int kStatusMask = 0x3;
static const int kHasHandlerMask = 0x4;
STATIC_ASSERT(v8::Promise::kPending == 0);
STATIC_ASSERT(v8::Promise::kFulfilled == 1);
STATIC_ASSERT(v8::Promise::kRejected == 2);

View File

@ -2,40 +2,36 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// JSPromise constants
const kJSPromiseStatusMask: constexpr int31
generates 'JSPromise::kStatusMask';
const kJSPromiseStatusShift: constexpr int31
generates 'JSPromise::kStatusShift';
const kJSPromiseHasHandlerMask: constexpr int31
generates 'JSPromise::kHasHandlerMask';
bitfield struct JSPromiseFlags extends uint31 {
status: PromiseState: 2 bit;
has_handler: bool: 1 bit;
handled_hint: bool: 1 bit;
async_task_id: int32: 22 bit;
}
@generateCppClass
extern class JSPromise extends JSObject {
macro Status(): PromiseState {
StaticAssert(kJSPromiseStatusShift == 0);
const status: int32 = Convert<int32>(this.flags) & kJSPromiseStatusMask;
return Convert<PromiseState>(status);
return this.flags.status;
}
macro SetStatus(status: constexpr PromiseState): void {
assert(this.Status() == PromiseState::kPending);
assert(status != PromiseState::kPending);
const mask: Smi = SmiConstant(status);
this.flags = this.flags | mask;
this.flags.status = status;
}
macro HasHandler(): bool {
return (this.flags & kJSPromiseHasHandlerMask) != 0;
return this.flags.has_handler;
}
macro SetHasHandler(): void {
this.flags |= kJSPromiseHasHandlerMask;
this.flags.has_handler = true;
}
// Smi 0 terminated list of PromiseReaction objects in case the JSPromise was
// not settled yet, otherwise the result.
reactions_or_result: Zero|PromiseReaction|JSAny;
flags: Smi;
flags: SmiTagged<JSPromiseFlags>;
}

View File

@ -5876,13 +5876,13 @@ class StringSharedKey : public HashTableKey {
};
v8::Promise::PromiseState JSPromise::status() const {
int value = flags() & kStatusMask;
int value = flags() & StatusBits::kMask;
DCHECK(value == 0 || value == 1 || value == 2);
return static_cast<v8::Promise::PromiseState>(value);
}
void JSPromise::set_status(Promise::PromiseState status) {
int value = flags() & ~kStatusMask;
int value = flags() & ~StatusBits::kMask;
set_flags(value | status);
}
@ -5900,11 +5900,11 @@ const char* JSPromise::Status(v8::Promise::PromiseState status) {
}
int JSPromise::async_task_id() const {
return AsyncTaskIdField::decode(flags());
return AsyncTaskIdBits::decode(flags());
}
void JSPromise::set_async_task_id(int id) {
set_flags(AsyncTaskIdField::update(flags(), id));
set_flags(AsyncTaskIdBits::update(flags(), id));
}
// static

View File

@ -957,7 +957,8 @@ void CSAGenerator::EmitInstruction(const StoreBitFieldInstruction& instruction,
"CodeStubAssembler(state_)." + encoder + "<" +
GetBitFieldSpecialization(struct_type, instruction.bit_field) +
">(ca_.UncheckedCast<" + struct_word_type + ">(" + bit_field_struct +
"), ca_.UncheckedCast<" + field_word_type + ">(" + value + "))";
"), ca_.UncheckedCast<" + field_word_type + ">(" + value + ")" +
(instruction.starts_as_zero ? ", true" : "") + ")";
if (smi_tagged_type) {
result_expression =

View File

@ -1192,30 +1192,6 @@ VisitResult ImplementationVisitor::Visit(StatementExpression* expr) {
return VisitResult{Visit(expr->statement), assembler().TopRange(0)};
}
void ImplementationVisitor::CheckInitializersWellformed(
const std::string& aggregate_name,
const std::vector<Field>& aggregate_fields,
const std::vector<NameAndExpression>& initializers,
bool ignore_first_field) {
size_t fields_offset = ignore_first_field ? 1 : 0;
size_t fields_size = aggregate_fields.size() - fields_offset;
for (size_t i = 0; i < std::min(fields_size, initializers.size()); i++) {
const std::string& field_name =
aggregate_fields[i + fields_offset].name_and_type.name;
Identifier* found_name = initializers[i].name;
if (field_name != found_name->value) {
Error("Expected field name \"", field_name, "\" instead of \"",
found_name->value, "\"")
.Position(found_name->pos)
.Throw();
}
}
if (fields_size != initializers.size()) {
ReportError("expected ", fields_size, " initializers for ", aggregate_name,
" found ", initializers.size());
}
}
InitializerResults ImplementationVisitor::VisitInitializerResults(
const ClassType* class_type,
const std::vector<NameAndExpression>& initializers) {
@ -1924,8 +1900,9 @@ VisitResult ImplementationVisitor::Visit(StructExpression* expr) {
}
// Compute and check struct type from given struct name and argument types
const StructType* struct_type = TypeVisitor::ComputeTypeForStructExpression(
const Type* type = TypeVisitor::ComputeTypeForStructExpression(
expr->type, term_argument_types);
if (const auto* struct_type = StructType::DynamicCast(type)) {
CheckInitializersWellformed(struct_type->name(), struct_type->fields(),
initializers);
@ -1939,6 +1916,41 @@ VisitResult ImplementationVisitor::Visit(StructExpression* expr) {
}
return stack_scope.Yield(VisitResult(struct_type, struct_range));
} else {
const auto* bitfield_struct_type = BitFieldStructType::cast(type);
CheckInitializersWellformed(bitfield_struct_type->name(),
bitfield_struct_type->fields(), initializers);
// Create a zero and cast it to the desired bitfield struct type.
VisitResult result{TypeOracle::GetConstInt32Type(), "0"};
result = GenerateImplicitConvert(TypeOracle::GetInt32Type(), result);
result = GenerateCall("Unsigned", Arguments{{result}, {}}, {});
result = GenerateCall("%RawDownCast", Arguments{{result}, {}},
{bitfield_struct_type});
// Set each field in the result. If these fields are constexpr, then all of
// this initialization will end up reduced to a single value during TurboFan
// optimization.
auto& fields = bitfield_struct_type->fields();
for (size_t i = 0; i < values.size(); i++) {
values[i] =
GenerateImplicitConvert(fields[i].name_and_type.type, values[i]);
result = GenerateSetBitField(bitfield_struct_type, fields[i], result,
values[i], /*starts_as_zero=*/true);
}
return stack_scope.Yield(result);
}
}
VisitResult ImplementationVisitor::GenerateSetBitField(
const Type* bitfield_struct_type, const BitField& bitfield,
VisitResult bitfield_struct, VisitResult value, bool starts_as_zero) {
GenerateCopy(bitfield_struct);
GenerateCopy(value);
assembler().Emit(
StoreBitFieldInstruction{bitfield_struct_type, bitfield, starts_as_zero});
return VisitResult(bitfield_struct_type, assembler().TopRange(1));
}
LocationReference ImplementationVisitor::GetLocationReference(
@ -2282,13 +2294,11 @@ void ImplementationVisitor::GenerateAssignToLocation(
GenerateFetchFromLocation(reference.bit_field_struct_location());
VisitResult converted_value =
GenerateImplicitConvert(reference.ReferencedType(), assignment_value);
GenerateCopy(bit_field_struct);
GenerateCopy(converted_value);
assembler().Emit(StoreBitFieldInstruction{bit_field_struct.type(),
reference.bit_field()});
GenerateAssignToLocation(
reference.bit_field_struct_location(),
VisitResult(bit_field_struct.type(), assembler().TopRange(1)));
VisitResult updated_bit_field_struct =
GenerateSetBitField(bit_field_struct.type(), reference.bit_field(),
bit_field_struct, converted_value);
GenerateAssignToLocation(reference.bit_field_struct_location(),
updated_bit_field_struct);
} else {
DCHECK(reference.IsTemporary());
ReportError("cannot assign to const-bound or temporary ",

View File

@ -432,11 +432,29 @@ class ImplementationVisitor {
VisitResult Visit(Expression* expr);
const Type* Visit(Statement* stmt);
template <typename T>
void CheckInitializersWellformed(
const std::string& aggregate_name,
const std::vector<Field>& aggregate_fields,
const std::string& aggregate_name, const std::vector<T>& aggregate_fields,
const std::vector<NameAndExpression>& initializers,
bool ignore_first_field = false);
bool ignore_first_field = false) {
size_t fields_offset = ignore_first_field ? 1 : 0;
size_t fields_size = aggregate_fields.size() - fields_offset;
for (size_t i = 0; i < std::min(fields_size, initializers.size()); i++) {
const std::string& field_name =
aggregate_fields[i + fields_offset].name_and_type.name;
Identifier* found_name = initializers[i].name;
if (field_name != found_name->value) {
Error("Expected field name \"", field_name, "\" instead of \"",
found_name->value, "\"")
.Position(found_name->pos)
.Throw();
}
}
if (fields_size != initializers.size()) {
ReportError("expected ", fields_size, " initializers for ",
aggregate_name, " found ", initializers.size());
}
}
InitializerResults VisitInitializerResults(
const ClassType* class_type,
@ -713,6 +731,12 @@ class ImplementationVisitor {
StackRange GenerateLabelGoto(LocalLabel* label,
base::Optional<StackRange> arguments = {});
VisitResult GenerateSetBitField(const Type* bitfield_struct_type,
const BitField& bitfield,
VisitResult bitfield_struct,
VisitResult value,
bool starts_as_zero = false);
std::vector<Binding<LocalLabel>*> LabelsFromIdentifiers(
const std::vector<Identifier*>& names);

View File

@ -364,14 +364,17 @@ struct LoadBitFieldInstruction : InstructionBase {
struct StoreBitFieldInstruction : InstructionBase {
TORQUE_INSTRUCTION_BOILERPLATE()
StoreBitFieldInstruction(const Type* bit_field_struct_type,
BitField bit_field)
BitField bit_field, bool starts_as_zero)
: bit_field_struct_type(bit_field_struct_type),
bit_field(std::move(bit_field)) {}
bit_field(std::move(bit_field)),
starts_as_zero(starts_as_zero) {}
DefinitionLocation GetValueDefinition() const;
const Type* bit_field_struct_type;
BitField bit_field;
// Allows skipping the mask step if we know the starting value is zero.
bool starts_as_zero;
};
struct CallIntrinsicInstruction : InstructionBase {

View File

@ -452,7 +452,7 @@ void TypeVisitor::VisitStructMethods(
DeclareMethods(struct_type, struct_declaration->methods);
}
const StructType* TypeVisitor::ComputeTypeForStructExpression(
const Type* TypeVisitor::ComputeTypeForStructExpression(
TypeExpression* type_expression,
const std::vector<const Type*>& term_argument_types) {
auto* basic = BasicTypeExpression::DynamicCast(type_expression);
@ -472,11 +472,11 @@ const StructType* TypeVisitor::ComputeTypeForStructExpression(
// Compute types of non-generic structs as usual
if (!(maybe_generic_type && decl)) {
const Type* type = ComputeType(type_expression);
const StructType* struct_type = StructType::DynamicCast(type);
if (!struct_type) {
ReportError(*type, " is not a struct, but used like one");
if (!type->IsStructType() && !type->IsBitFieldStructType()) {
ReportError(*type,
" is not a struct or bitfield struct, but used like one");
}
return struct_type;
return type;
}
auto generic_type = *maybe_generic_type;

View File

@ -32,7 +32,9 @@ class TypeVisitor {
static void VisitStructMethods(StructType* struct_type,
const StructDeclaration* struct_declaration);
static Signature MakeSignature(const CallableDeclaration* declaration);
static const StructType* ComputeTypeForStructExpression(
// Can return either StructType or BitFieldStructType, since they can both be
// used in struct expressions like `MyStruct{ a: 0, b: foo }`
static const Type* ComputeTypeForStructExpression(
TypeExpression* type_expression,
const std::vector<const Type*>& term_argument_types);

View File

@ -694,6 +694,43 @@ TEST(TestBitFieldStore) {
}
}
TEST(TestBitFieldInit) {
CcTest::InitializeVM();
Isolate* isolate(CcTest::i_isolate());
i::HandleScope scope(isolate);
const int kNumParams = 4;
CodeAssemblerTester asm_tester(isolate, kNumParams);
TestTorqueAssembler m(asm_tester.state());
{
// Untag all of the parameters to get plain integer values.
TNode<BoolT> a =
m.UncheckedCast<BoolT>(m.Unsigned(m.SmiToInt32(m.Parameter(0))));
TNode<Uint16T> b =
m.UncheckedCast<Uint16T>(m.Unsigned(m.SmiToInt32(m.Parameter(1))));
TNode<Uint32T> c =
m.UncheckedCast<Uint32T>(m.Unsigned(m.SmiToInt32(m.Parameter(2))));
TNode<BoolT> d =
m.UncheckedCast<BoolT>(m.Unsigned(m.SmiToInt32(m.Parameter(3))));
// Call the Torque-defined macro, which verifies that reading each bitfield
// out of val yields the correct result.
m.TestBitFieldInit(a, b, c, d);
m.Return(m.UndefinedConstant());
}
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
// Test every possible bit combination for this 8-bit value.
for (int a = 0; a <= 1; ++a) {
for (int b = 0; b <= 7; ++b) {
for (int c = 0; c <= 7; ++c) {
for (int d = 0; d <= 1; ++d) {
ft.Call(ft.Val(a), ft.Val(b), ft.Val(c), ft.Val(d));
}
}
}
}
}
TEST(TestBitFieldUintptrOps) {
CcTest::InitializeVM();
Isolate* isolate(CcTest::i_isolate());

View File

@ -1085,6 +1085,12 @@ namespace test {
TestBitFieldLoad(val, !a, b, c, b == c);
}
@export
macro TestBitFieldInit(a: bool, b: uint16, c: uint32, d: bool) {
const val: TestBitFieldStruct = TestBitFieldStruct{a: a, b: b, c: c, d: d};
TestBitFieldLoad(val, a, b, c, d);
}
// Some other bitfield structs, to verify getting uintptr values out of word32
// structs and vice versa.
bitfield struct TestBitFieldStruct2 extends uint32 {