[metagen] Derive Torque names of class-scoped enum bit field types

Record enum bit field types with their enclosing classes, e.g.
JSPluralRules::Type, and name them in Torque by dropping the "::"
(JSPluralRulesType), as for bit field group structs. Rename the Torque
types that did not follow this rule: DescriptorArrayFastIterableState,
NameHashFieldType, and PromisePromiseState.

Also allow 'type' as a Torque identifier, for bit fields named after a
TypeBit alias.

Bug: 531344950
Change-Id: Iee8fb965945a416a98a4d256521bae896918ab63
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8657159
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Hannes Payer <hpayer@chromium.org>
Commit-Queue: Hannes Payer <hpayer@chromium.org>
Auto-Submit: Jakob Linke <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/main@{#111153}
This commit is contained in:
Jakob Linke authored and v8-scoped@luci-project-accounts.iam.gserviceaccount.com committed 2026-10-26 06:34:07 -07:00
1 parent 0530bcbf67
commit 94fe2a753a
14 files changed
+142 -55

No files matched your search

+6 -8
View File
@@ -345,11 +345,8 @@ extern enum CallFeedbackContent extends int32 { kTarget, kReceiver }
extern enum UnicodeEncoding { UTF16, UTF32 }
// Promise constants
extern enum PromiseState extends int31 constexpr 'Promise::PromiseState' {
kPending,
kFulfilled,
kRejected
}
extern enum PromisePromiseState extends int31
constexpr 'Promise::PromiseState' { kPending, kFulfilled, kRejected }
const kTaggedSize: constexpr int31 generates 'kTaggedSize';
const kDoubleSize: constexpr int31 generates 'kDoubleSize';
@@ -1338,7 +1335,7 @@ operator '+' macro StringAdd(
}
operator '==' macro PromiseStateEquals(
s1: PromiseState, s2: PromiseState): bool {
s1: PromisePromiseState, s2: PromisePromiseState): bool {
return Word32Equal(s1, s2);
}
@@ -2267,7 +2264,8 @@ extern operator '[]' macro LoadWeakFixedArrayElement(
extern operator '[]' macro LoadUint8Ptr(RawPtr<uint8>, intptr): uint8;
extern operator '[]' macro LoadUint64Ptr(RawPtr<uint64>, intptr): uint64;
extern enum HashFieldType extends uint32 constexpr 'Name::HashFieldType' {
extern enum NameHashFieldType extends uint32
constexpr 'Name::HashFieldType' {
kHash,
kIntegerIndex,
kForwardingIndex,
@@ -2275,7 +2273,7 @@ extern enum HashFieldType extends uint32 constexpr 'Name::HashFieldType' {
}
operator '==' macro HashFieldTypeEquals(
s1: HashFieldType, s2: HashFieldType): bool {
s1: NameHashFieldType, s2: NameHashFieldType): bool {
return Word32Equal(s1, s2);
}
+5 -5
View File
@@ -168,9 +168,9 @@ 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));
FromConstexpr<PromisePromiseState, constexpr PromisePromiseState>(
c: constexpr PromisePromiseState): PromisePromiseState {
return %RawDownCast<PromisePromiseState>(Int32Constant(c));
}
FromConstexpr<InstanceType, constexpr InstanceType>(c: constexpr InstanceType):
InstanceType {
@@ -444,8 +444,8 @@ extern macro BIntToSmi(bint): Smi;
Convert<Smi, bint>(v: bint): Smi {
return BIntToSmi(v);
}
Convert<PromiseState, int32>(s: int32): PromiseState {
return %RawDownCast<PromiseState>(s);
Convert<PromisePromiseState, int32>(s: int32): PromisePromiseState {
return %RawDownCast<PromisePromiseState>(s);
}
Convert<I8X16, Simd128>(s: Simd128): I8X16 {
return %RawDownCast<I8X16>(s);
+10 -10
View File
@@ -208,8 +208,8 @@ transitioning macro TriggerPromiseReactions(
// https://tc39.es/ecma262/#sec-fulfillpromise
transitioning builtin FulfillPromise(
implicit context: Context)(promise: JSPromise, value: JSAny): Undefined {
// Assert: The value of promise.[[PromiseState]] is "pending".
dcheck(promise.Status() == PromiseState::kPending);
// Assert: The value of promise.[[PromisePromiseState]] is "pending".
dcheck(promise.Status() == PromisePromiseState::kPending);
RunContextPromiseHookResolve(promise);
@@ -222,8 +222,8 @@ transitioning builtin FulfillPromise(
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
promise.reactions_or_result = value;
// 6. Set promise.[[PromiseState]] to "fulfilled".
promise.SetStatus(PromiseState::kFulfilled);
// 6. Set promise.[[PromisePromiseState]] to "fulfilled".
promise.SetStatus(PromisePromiseState::kFulfilled);
// 7. Return TriggerPromiseReactions(reactions, value).
TriggerPromiseReactions(reactions, value, kPromiseReactionFulfill);
@@ -268,8 +268,8 @@ transitioning builtin RejectPromise(
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
promise.reactions_or_result = reason;
// 6. Set promise.[[PromiseState]] to "rejected".
promise.SetStatus(PromiseState::kRejected);
// 6. Set promise.[[PromisePromiseState]] to "rejected".
promise.SetStatus(PromisePromiseState::kRejected);
// 8. Return TriggerPromiseReactions(reactions, reason).
TriggerPromiseReactions(reactions, reason, kPromiseReactionReject);
@@ -470,7 +470,7 @@ transitioning macro PerformPromiseThenImpl(
implicit context: Context)(promise: JSPromise,
onFulfilled: Callable|Undefined, onRejected: Callable|Undefined,
resultPromiseOrCapability: JSPromise|PromiseCapability|Undefined): void {
if (promise.Status() == PromiseState::kPending) {
if (promise.Status() == PromisePromiseState::kPending) {
// The {promise} is still in "Pending" state, so we just record a new
// PromiseReaction holding both the onFulfilled and onRejected callbacks.
// Once the {promise} is resolved we decide on the concrete handler to
@@ -485,14 +485,14 @@ transitioning macro PerformPromiseThenImpl(
const reactionsOrResult = promise.reactions_or_result;
let microtask: PromiseReactionJobTask;
let handlerContext: Context;
if (promise.Status() == PromiseState::kFulfilled) {
if (promise.Status() == PromisePromiseState::kFulfilled) {
handlerContext = ExtractHandlerContext(onFulfilled, onRejected);
microtask = NewPromiseFulfillReactionJobTask(
handlerContext, reactionsOrResult, onFulfilled,
resultPromiseOrCapability);
} else
deferred {
dcheck(promise.Status() == PromiseState::kRejected);
dcheck(promise.Status() == PromisePromiseState::kRejected);
handlerContext = ExtractHandlerContext(onRejected, onFulfilled);
microtask = NewPromiseRejectReactionJobTask(
handlerContext, reactionsOrResult, onRejected,
@@ -542,7 +542,7 @@ transitioning javascript builtin PromiseReject(
const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX);
if (promiseFun == receiver) {
const promise = NewJSPromise(PromiseState::kRejected, reason);
const promise = NewJSPromise(PromisePromiseState::kRejected, reason);
runtime::PromiseRejectEventFromStack(promise, reason);
return promise;
} else {
+4 -4
View File
@@ -48,7 +48,7 @@ macro PromiseHasHandler(promise: JSPromise): bool {
macro PromiseInit(promise: JSPromise): void {
promise.reactions_or_result = kZero;
promise.flags = SmiTag(JSPromiseFlags{
status: PromiseState::kPending,
status: PromisePromiseState::kPending,
is_native_resolver_invoked: false,
has_handler: false,
is_silent: false,
@@ -68,7 +68,7 @@ macro InnerNewJSPromise(implicit context: Context)(): JSPromise {
promise.elements = kEmptyFixedArray;
promise.reactions_or_result = kZero;
promise.flags = SmiTag(JSPromiseFlags{
status: PromiseState::kPending,
status: PromisePromiseState::kPending,
is_native_resolver_invoked: false,
has_handler: false,
is_silent: false,
@@ -288,9 +288,9 @@ transitioning macro NewJSPromise(implicit context: Context)(): JSPromise {
// fields.
@export
transitioning macro NewJSPromise(
implicit context: Context)(status: constexpr PromiseState,
implicit context: Context)(status: constexpr PromisePromiseState,
result: JSAny): JSPromise {
dcheck(status != PromiseState::kPending);
dcheck(status != PromisePromiseState::kPending);
const instance = InnerNewJSPromise();
instance.reactions_or_result = result;
+2 -2
View File
@@ -16,12 +16,12 @@ struct DescriptorEntry {
NumberDictionary;
}
type FastIterableState extends uint8
type DescriptorArrayFastIterableState extends uint8
constexpr 'DescriptorArray::FastIterableState';
@cppScope('DescriptorArray::Flags')
bitfield struct DescriptorArrayFlags extends uint32 {
fast_iterable: FastIterableState: 2 bit;
fast_iterable: DescriptorArrayFastIterableState: 2 bit;
}
@cppObjectLayoutDefinition
+5 -5
View File
@@ -4,7 +4,7 @@
@cppScope('JSPromise::Flags')
bitfield struct JSPromiseFlags extends uint31 {
status: PromiseState: 2 bit;
status: PromisePromiseState: 2 bit;
is_native_resolver_invoked: bool: 1 bit;
has_handler: bool: 1 bit;
is_silent: bool: 1 bit;
@@ -13,13 +13,13 @@ bitfield struct JSPromiseFlags extends uint31 {
@cppObjectLayoutDefinition
extern class JSPromise extends JSObjectWithEmbedderSlots {
macro Status(): PromiseState {
macro Status(): PromisePromiseState {
return this.flags.status;
}
macro SetStatus(status: constexpr PromiseState): void {
dcheck(this.Status() == PromiseState::kPending);
dcheck(status != PromiseState::kPending);
macro SetStatus(status: constexpr PromisePromiseState): void {
dcheck(this.Status() == PromisePromiseState::kPending);
dcheck(status != PromisePromiseState::kPending);
this.flags.status = status;
}
+3 -3
View File
@@ -10,7 +10,7 @@ extern class Name extends PrimitiveHeapObject {
@cppScope('Name::Hash')
bitfield struct NameHash extends uint32 {
hash_field_type: HashFieldType: 2 bit;
hash_field_type: NameHashFieldType: 2 bit;
// hash_field_type: kHash
hash: uint32: 30 bit at 2;
@@ -64,7 +64,7 @@ const kArrayIndexValueBits: constexpr int31
const kDoesNotContainCachedArrayIndexMask: constexpr uint32
generates 'Name::kDoesNotContainCachedArrayIndexMask';
const kNameEmptyHashField: NameHash =
NameHash{hash_field_type: HashFieldType::kEmpty};
NameHash{hash_field_type: NameHashFieldType::kEmpty};
macro ContainsCachedArrayIndex(hash: uint32): bool {
return (hash & kDoesNotContainCachedArrayIndexMask) == 0;
@@ -89,7 +89,7 @@ macro TenToThe(exponent: uint32): uint32 {
}
macro IsIntegerIndex(hash: NameHash): bool {
return hash.hash_field_type == HashFieldType::kIntegerIndex;
return hash.hash_field_type == NameHashFieldType::kIntegerIndex;
}
// This is in sync with the private StringHasher::MakeArrayIndexHash without
+9 -4
View File
@@ -85,9 +85,8 @@ std::string TorqueClassNameFromCppName(const std::string& cpp_name,
return cpp_name.substr(separator + 2);
}
std::string TorqueBitFieldStructName(const JsonObject& storage,
const std::string& context) {
std::string name = RequireString(storage, "name", context);
// Torque has no nested types: the C++ type Foo::Bar is FooBar in Torque.
std::string TorqueNameFromClassScopedName(std::string name) {
size_t pos;
while ((pos = name.find("::")) != std::string::npos) {
name.erase(pos, 2);
@@ -95,6 +94,11 @@ std::string TorqueBitFieldStructName(const JsonObject& storage,
return name;
}
std::string TorqueBitFieldStructName(const JsonObject& storage,
const std::string& context) {
return TorqueNameFromClassScopedName(RequireString(storage, "name", context));
}
// Map primitive C++ types to their Torque representation. Torque already knows
// all other class, struct, and namespace-scope alias names.
struct PrimitiveTypeName {
@@ -1344,7 +1348,8 @@ class LayoutImporter {
NameAndTypeExpression{
MakeNode<Identifier>(
TorqueBitFieldName(cpp_name, num_bits, context)),
NamedType(TorqueTypeName(RequireString(b, "type", context)))},
NamedType(TorqueTypeName(TorqueNameFromClassScopedName(
RequireString(b, "type", context))))},
static_cast<int>(num_bits), static_cast<int>(offset)});
}
return fields;
+2 -1
View File
@@ -2439,7 +2439,8 @@ struct TorqueGrammar : Grammar {
// Result: std::string
Symbol identifier = {Rule({Pattern(MatchIdentifier)}, YieldMatchedInput),
Rule({Token("runtime")}, YieldMatchedInput)};
Rule({Token("runtime")}, YieldMatchedInput),
Rule({Token("type")}, YieldMatchedInput)};
// Result: Identifier*
Symbol name = {Rule({&identifier}, MakeIdentifier)};
+54
View File
@@ -1734,6 +1734,60 @@ TEST(TorqueLayoutLoader, VerifierRejectsBitFieldStructNameMismatch) {
"the C++ bit field group is Test::BitFields, Torque uses OtherBits");
}
// The class-scoped enum TestScalar::Mode, named in Torque by dropping the
// "::".
std::string TestModeEnum(const std::string& name = "TestScalarMode") {
return "extern enum " + name +
" extends uint8 constexpr 'TestScalar::Mode' { kA, kB }\n";
}
// TestBitFieldStorage() with a bit of type TestScalar::Mode in place of bar.
std::string TestModeBitFieldStorage() {
return TestBitFieldStorage(
R"({"cpp_name": "IsFooBit", "type": "bool", )"
R"("offset": 0, "num_bits": 1}, )"
R"({"cpp_name": "ModeBit", "type": "TestScalar::Mode", )"
R"("offset": 1, "num_bits": 1})");
}
TEST(TorqueLayoutLoader, ImporterDerivesClassScopedEnumBitFieldType) {
ExpectSuccessfulLayoutCompilation(
TestModeEnum() + TestScalarClass("TestBitFields"),
TestScalarJson(TestModeBitFieldStorage(), 1),
/*use_cpp_layouts=*/true);
}
TEST(TorqueLayoutLoader, ImporterReplacesBitFieldStructWithClassScopedEnum) {
ExpectSuccessfulLayoutCompilation(
TestModeEnum() + TestBitFieldStruct("mode: TestScalarMode: 1 bit;") +
TestScalarClass("TestBitFields"),
TestScalarJson(TestModeBitFieldStorage(), 1),
/*use_cpp_layouts=*/true);
}
TEST(TorqueLayoutLoader, ImporterRejectsEnumNotNamedAfterCppType) {
ExpectFailingLayoutCompilation(
TestModeEnum("TestMode") + TestScalarClass("TestBitFields"),
TestScalarJson(TestModeBitFieldStorage(), 1),
/*use_cpp_layouts=*/true, "cannot find \"TestScalarMode\"");
}
TEST(TorqueLayoutLoader, AcceptsBitFieldNamedType) {
for (bool use_cpp_layouts : {true, false}) {
ExpectSuccessfulLayoutCompilation(
TestBitFieldStruct("type: uint8: 3 bit;") +
TestScalarClass("TestBitFields"),
TestScalarJson(
TestBitFieldStorage(R"({"cpp_name": "IsFooBit", "type": "bool", )"
R"("offset": 0, "num_bits": 1}, )"
R"({"cpp_name": "TypeBits", )"
R"("type": "uint8_t", )"
R"("offset": 1, "num_bits": 3})"),
1),
use_cpp_layouts);
}
}
TEST(TorqueLayoutLoader, ImporterDeclaresBitFieldStructFromCppOnlyBase) {
// TestBase has no Torque declaration. Import its fields into TestScalar,
// including the bitfield struct used by value_.
+8 -7
View File
@@ -148,12 +148,13 @@ def _bit_field_group_base(decl: cindex.Cursor) -> cindex.Type | None:
return None
def _cpp_bit_field_struct_name(decl: cindex.Cursor) -> str:
"""The C++ name of a BitFieldGroup, including enclosing classes."""
def _cpp_class_scoped_name(decl: cindex.Cursor) -> str:
"""The C++ name of `decl`, qualified by its enclosing classes."""
parts = []
while decl is not None and decl.kind in (cindex.CursorKind.CLASS_DECL,
cindex.CursorKind.STRUCT_DECL,
cindex.CursorKind.CLASS_TEMPLATE):
cindex.CursorKind.CLASS_TEMPLATE,
cindex.CursorKind.ENUM_DECL):
parts.append(decl.spelling)
decl = decl.semantic_parent
return "::".join(reversed(parts))
@@ -375,7 +376,7 @@ class _Extractor:
if canonical.kind == cindex.TypeKind.ENUM:
decl = canonical.get_declaration()
mark_visited(self.parsed, decl)
return decl.spelling
return _cpp_class_scoped_name(decl)
if canonical.kind == cindex.TypeKind.RECORD:
decl = _decl_of(canonical)
if decl is not None and decl.spelling == "StrongAlias":
@@ -481,8 +482,8 @@ class _Extractor:
base = _bit_field_group_base(decl)
if base is not None:
mark_visited(self.parsed, decl)
return self._bitfield_storage(field, _cpp_bit_field_struct_name(decl),
decl, base)
return self._bitfield_storage(field, _cpp_class_scoped_name(decl), decl,
base)
if decl is not None and decl.kind in (cindex.CursorKind.CLASS_DECL,
cindex.CursorKind.STRUCT_DECL):
@@ -520,7 +521,7 @@ class _Extractor:
f"is not a BitFieldGroup")
mark_visited(self.parsed, group)
return self._bitfield_storage(
field, _cpp_bit_field_struct_name(group), group, base, smi=True)
field, _cpp_class_scoped_name(group), group, base, smi=True)
if name == "TrustedPointerMember":
args = _template_args(elem_type) or _template_args(canonical)
if not args:
+8 -6
View File
@@ -72,6 +72,12 @@ def _check_identifier(s: str, what: str) -> None:
f"{what}: not an identifier: {s!r}")
def _check_class_scoped_name(s: str, what: str) -> None:
_check(isinstance(s, str), f"{what} must be a string")
for part in s.split("::"):
_check_identifier(part, what)
@dataclasses.dataclass(frozen=True)
class Config:
"""The build configuration the layouts were extracted under.
@@ -268,7 +274,7 @@ class BitFieldEntry:
def __post_init__(self) -> None:
_check_identifier(self.cpp_name, "bit field name")
_check_identifier(self.type, "bit field type")
_check_class_scoped_name(self.type, "bit field type")
_check(self.num_bits > 0, f"bit field {self.cpp_name}: no bits")
_check(self.offset >= 0, f"bit field {self.cpp_name}: negative offset")
@@ -333,11 +339,7 @@ class StorageType:
_check(self.width is not None and self.width > 0,
"struct storage needs a size")
if self.kind in ("bitfield", "smi_bitfield"):
_check(
isinstance(self.name, str),
f"{self.kind} storage name must be a string")
for part in (self.name or "").split("::"):
_check_identifier(part, f"{self.kind} storage name")
_check_class_scoped_name(self.name, f"{self.kind} storage name")
if self.kind == "bitfield":
_check(self.width in (1, 2, 4, 8), "bitfield storage width invalid")
capacity = self.width * 8
@@ -776,6 +776,23 @@ class BitFieldGroupTest(unittest.TestCase):
""")["Foo"].fields
self.assertEqual(field.storage.name, "Foo::Holder::Bits")
def test_class_scoped_enum_bit_field_type_is_qualified(self):
(field,) = self._layouts("""
namespace v8::internal {
class Foo : public HeapObject {
public:
enum class Mode : uint8_t { kA, kB };
struct Bits : BitFieldGroup<Bits, uint32_t> {
using ModeBits = First<Mode, 1>;
using KindBits = ModeBits::Next<Kind, 2>;
};
Bits bits_;
};
}
""")["Foo"].fields
self.assertEqual([(b.cpp_name, b.type) for b in field.storage.bits],
[("ModeBits", "Foo::Mode"), ("KindBits", "Kind")])
def test_smi_member_records_the_group_as_a_tagged_slot(self):
(field,) = self._layouts("""
class Foo : public HeapObject {
+9
View File
@@ -150,6 +150,15 @@ class StorageTypeTest(unittest.TestCase):
width=1,
bits=(BitFieldEntry("IsFooBit", "bool", 0, 1),))
def test_bitfield_type_may_be_class_scoped(self):
s = bitfield(BitFieldEntry("ModeBits", "Foo::Mode", 0, 2))
self.assertEqual(StorageType.from_json(s.to_json()), s)
def test_bitfield_rejects_invalid_qualified_types(self):
for type_name in (None, 42, "::Mode", "Foo::", "Foo::::Mode", "Foo:Mode"):
with self.subTest(type_name=type_name), self.assertRaises(LayoutError):
BitFieldEntry("ModeBits", type_name, 0, 2)
def test_bitfield_needs_fields(self):
with self.assertRaisesRegex(LayoutError, "without fields"):
bitfield()