Commit 77d91c78 authored by Kenton Varda's avatar Kenton Varda

Zero out group on init. Important when the group is in a union.

parent cfc10236
......@@ -237,6 +237,210 @@ private:
KJ_UNREACHABLE;
}
// -----------------------------------------------------------------
// Code to deal with "slots" -- determines what to zero out when we clear a group.
static uint typeSizeBits(schema::Type::Which whichType) {
switch (whichType) {
case schema::Type::BOOL: return 1;
case schema::Type::INT8: return 8;
case schema::Type::INT16: return 16;
case schema::Type::INT32: return 32;
case schema::Type::INT64: return 64;
case schema::Type::UINT8: return 8;
case schema::Type::UINT16: return 16;
case schema::Type::UINT32: return 32;
case schema::Type::UINT64: return 64;
case schema::Type::FLOAT32: return 32;
case schema::Type::FLOAT64: return 64;
case schema::Type::ENUM: return 16;
case schema::Type::VOID:
case schema::Type::TEXT:
case schema::Type::DATA:
case schema::Type::LIST:
case schema::Type::STRUCT:
case schema::Type::INTERFACE:
case schema::Type::OBJECT:
KJ_FAIL_REQUIRE("Should only be called for data types.");
}
KJ_UNREACHABLE;
}
enum class Section {
NONE,
DATA,
POINTERS
};
static Section sectionFor(schema::Type::Which whichType) {
switch (whichType) {
case schema::Type::VOID:
return Section::NONE;
case schema::Type::BOOL:
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::INT64:
case schema::Type::UINT8:
case schema::Type::UINT16:
case schema::Type::UINT32:
case schema::Type::UINT64:
case schema::Type::FLOAT32:
case schema::Type::FLOAT64:
case schema::Type::ENUM:
return Section::DATA;
case schema::Type::TEXT:
case schema::Type::DATA:
case schema::Type::LIST:
case schema::Type::STRUCT:
case schema::Type::INTERFACE:
case schema::Type::OBJECT:
return Section::POINTERS;
}
KJ_UNREACHABLE;
}
static kj::StringPtr maskType(schema::Type::Which whichType) {
switch (whichType) {
case schema::Type::BOOL: return "bool";
case schema::Type::INT8: return " ::uint8_t";
case schema::Type::INT16: return " ::uint16_t";
case schema::Type::INT32: return " ::uint32_t";
case schema::Type::INT64: return " ::uint64_t";
case schema::Type::UINT8: return " ::uint8_t";
case schema::Type::UINT16: return " ::uint16_t";
case schema::Type::UINT32: return " ::uint32_t";
case schema::Type::UINT64: return " ::uint64_t";
case schema::Type::FLOAT32: return " ::uint32_t";
case schema::Type::FLOAT64: return " ::uint64_t";
case schema::Type::ENUM: return " ::uint16_t";
case schema::Type::VOID:
case schema::Type::TEXT:
case schema::Type::DATA:
case schema::Type::LIST:
case schema::Type::STRUCT:
case schema::Type::INTERFACE:
case schema::Type::OBJECT:
KJ_FAIL_REQUIRE("Should only be called for data types.");
}
KJ_UNREACHABLE;
}
struct Slot {
schema::Type::Which whichType;
uint offset;
bool isSupersetOf(Slot other) const {
auto section = sectionFor(whichType);
if (section != sectionFor(other.whichType)) return false;
switch (section) {
case Section::NONE:
return true; // all voids overlap
case Section::DATA: {
auto bits = typeSizeBits(whichType);
auto start = offset * bits;
auto otherBits = typeSizeBits(other.whichType);
auto otherStart = other.offset * otherBits;
return start <= otherStart && otherStart + otherBits <= start + bits;
}
case Section::POINTERS:
return offset == other.offset;
}
KJ_UNREACHABLE;
}
bool operator<(Slot other) const {
// Sort by section, then start position, and finally size.
auto section = sectionFor(whichType);
auto otherSection = sectionFor(other.whichType);
if (section < otherSection) {
return true;
} else if (section > otherSection) {
return false;
}
switch (section) {
case Section::NONE:
return false;
case Section::DATA: {
auto bits = typeSizeBits(whichType);
auto start = offset * bits;
auto otherBits = typeSizeBits(other.whichType);
auto otherStart = other.offset * otherBits;
if (start < otherStart) {
return true;
} else if (start > otherStart) {
return false;
}
// Sort larger sizes before smaller.
return bits > otherBits;
}
case Section::POINTERS:
return offset < other.offset;
}
KJ_UNREACHABLE;
}
};
void getSlots(StructSchema schema, kj::Vector<Slot>& slots) {
auto structProto = schema.getProto().getStruct();
if (structProto.getDiscriminantCount() > 0) {
slots.add(Slot { schema::Type::UINT16, structProto.getDiscriminantOffset() });
}
for (auto field: schema.getFields()) {
auto proto = field.getProto();
switch (proto.which()) {
case schema::Field::NON_GROUP: {
auto nonGroup = proto.getNonGroup();
slots.add(Slot { nonGroup.getType().which(), nonGroup.getOffset() });
break;
}
case schema::Field::GROUP:
getSlots(schema.getDependency(proto.getGroup()).asStruct(), slots);
break;
}
}
}
kj::Array<Slot> getSortedSlots(StructSchema schema) {
// Get a representation of all of the field locations owned by this schema, e.g. so that they
// can be zero'd out.
kj::Vector<Slot> slots(schema.getFields().size());
getSlots(schema, slots);
std::sort(slots.begin(), slots.end());
kj::Vector<Slot> result(slots.size());
// All void slots are redundant, and they sort towards the front of the list. By starting out
// with `prevSlot` = void, we will end up skipping them all, which is what we want.
Slot prevSlot = { schema::Type::VOID, 0 };
for (auto slot: slots) {
if (prevSlot.isSupersetOf(slot)) {
// This slot is redundant as prevSlot is a superset of it.
continue;
}
// Since all sizes are power-of-two, if two slots overlap at all, one must be a superset of
// the other. Since we sort slots by starting position, we know that the only way `slot`
// could be a superset of `prevSlot` is if they have the same starting position. However,
// since we sort slots with the same starting position by descending size, this is not
// possible.
KJ_DASSERT(!slot.isSupersetOf(prevSlot));
result.add(slot);
prevSlot = slot;
}
return result.releaseAsArray();
}
// -----------------------------------------------------------------
struct DiscriminantChecks {
......@@ -293,7 +497,8 @@ private:
// Continue below.
break;
case schema::Field::GROUP:
case schema::Field::GROUP: {
auto slots = getSortedSlots(schemaLoader.get(field.getProto().getGroup()).asStruct());
return FieldText {
kj::strTree(
" inline ", titleCase, "::Reader get", titleCase, "() const;\n"
......@@ -315,10 +520,23 @@ private:
"}\n"
"inline ", scope, titleCase, "::Builder ", scope, "Builder::init", titleCase, "() {\n",
unionDiscrim.set,
// TODO(soon): Zero out fields.
KJ_MAP(slots, slot) {
switch (sectionFor(slot.whichType)) {
case Section::NONE:
return kj::strTree();
case Section::DATA:
return kj::strTree(
" _builder.setDataField<", maskType(slot.whichType), ">(",
slot.offset, " * ::capnp::ELEMENTS, 0);\n");
case Section::POINTERS:
return kj::strTree(
" _builder.clearPointer(", slot.offset, " * ::capnp::POINTERS);\n");
}
},
" return ", scope, titleCase, "::Builder(_builder);\n"
"}\n")
};
}
}
auto nonGroup = proto.getNonGroup();
......
......@@ -2536,6 +2536,8 @@ inline DeclName::Base::Builder DeclName::Builder::getBase() {
return DeclName::Base::Builder(_builder);
}
inline DeclName::Base::Builder DeclName::Builder::initBase() {
_builder.setDataField< ::uint16_t>(0 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(0 * ::capnp::POINTERS);
return DeclName::Base::Builder(_builder);
}
inline bool DeclName::Reader::hasMemberPath() const {
......@@ -3344,6 +3346,8 @@ inline Declaration::Id::Builder Declaration::Builder::getId() {
return Declaration::Id::Builder(_builder);
}
inline Declaration::Id::Builder Declaration::Builder::initId() {
_builder.setDataField< ::uint16_t>(0 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(1 * ::capnp::POINTERS);
return Declaration::Id::Builder(_builder);
}
inline bool Declaration::Reader::hasNestedDecls() const {
......@@ -3528,6 +3532,7 @@ inline Declaration::Using::Builder Declaration::Builder::getUsing() {
inline Declaration::Using::Builder Declaration::Builder::initUsing() {
_builder.setDataField<Declaration::Which>(
1 * ::capnp::ELEMENTS, Declaration::USING);
_builder.clearPointer(5 * ::capnp::POINTERS);
return Declaration::Using::Builder(_builder);
}
inline Declaration::Const::Reader Declaration::Reader::getConst() const {
......@@ -3543,6 +3548,8 @@ inline Declaration::Const::Builder Declaration::Builder::getConst() {
inline Declaration::Const::Builder Declaration::Builder::initConst() {
_builder.setDataField<Declaration::Which>(
1 * ::capnp::ELEMENTS, Declaration::CONST);
_builder.clearPointer(5 * ::capnp::POINTERS);
_builder.clearPointer(6 * ::capnp::POINTERS);
return Declaration::Const::Builder(_builder);
}
inline bool Declaration::Reader::hasEnum() const {
......@@ -3651,6 +3658,9 @@ inline Declaration::Field::Builder Declaration::Builder::getField() {
inline Declaration::Field::Builder Declaration::Builder::initField() {
_builder.setDataField<Declaration::Which>(
1 * ::capnp::ELEMENTS, Declaration::FIELD);
_builder.setDataField< ::uint16_t>(6 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(5 * ::capnp::POINTERS);
_builder.clearPointer(6 * ::capnp::POINTERS);
return Declaration::Field::Builder(_builder);
}
inline bool Declaration::Reader::hasUnion() const {
......@@ -3759,6 +3769,9 @@ inline Declaration::Method::Builder Declaration::Builder::getMethod() {
inline Declaration::Method::Builder Declaration::Builder::initMethod() {
_builder.setDataField<Declaration::Which>(
1 * ::capnp::ELEMENTS, Declaration::METHOD);
_builder.setDataField< ::uint16_t>(6 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(5 * ::capnp::POINTERS);
_builder.clearPointer(6 * ::capnp::POINTERS);
return Declaration::Method::Builder(_builder);
}
inline Declaration::Annotation::Reader Declaration::Reader::getAnnotation() const {
......@@ -3774,6 +3787,19 @@ inline Declaration::Annotation::Builder Declaration::Builder::getAnnotation() {
inline Declaration::Annotation::Builder Declaration::Builder::initAnnotation() {
_builder.setDataField<Declaration::Which>(
1 * ::capnp::ELEMENTS, Declaration::ANNOTATION);
_builder.setDataField<bool>(96 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(97 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(98 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(99 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(100 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(101 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(102 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(103 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(104 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(105 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(106 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(107 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(5 * ::capnp::POINTERS);
return Declaration::Annotation::Builder(_builder);
}
inline bool Declaration::Reader::hasNakedId() const {
......@@ -4407,6 +4433,8 @@ inline Declaration::AnnotationApplication::Value::Builder Declaration::Annotatio
return Declaration::AnnotationApplication::Value::Builder(_builder);
}
inline Declaration::AnnotationApplication::Value::Builder Declaration::AnnotationApplication::Builder::initValue() {
_builder.setDataField< ::uint16_t>(0 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(1 * ::capnp::POINTERS);
return Declaration::AnnotationApplication::Value::Builder(_builder);
}
inline Declaration::AnnotationApplication::Value::Which Declaration::AnnotationApplication::Value::Reader::which() const {
......@@ -4598,6 +4626,8 @@ inline Declaration::Param::DefaultValue::Builder Declaration::Param::Builder::ge
return Declaration::Param::DefaultValue::Builder(_builder);
}
inline Declaration::Param::DefaultValue::Builder Declaration::Param::Builder::initDefaultValue() {
_builder.setDataField< ::uint16_t>(0 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(3 * ::capnp::POINTERS);
return Declaration::Param::DefaultValue::Builder(_builder);
}
inline Declaration::Param::DefaultValue::Which Declaration::Param::DefaultValue::Reader::which() const {
......@@ -4955,6 +4985,8 @@ inline Declaration::Field::DefaultValue::Builder Declaration::Field::Builder::ge
return Declaration::Field::DefaultValue::Builder(_builder);
}
inline Declaration::Field::DefaultValue::Builder Declaration::Field::Builder::initDefaultValue() {
_builder.setDataField< ::uint16_t>(6 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(6 * ::capnp::POINTERS);
return Declaration::Field::DefaultValue::Builder(_builder);
}
inline Declaration::Field::DefaultValue::Which Declaration::Field::DefaultValue::Reader::which() const {
......@@ -5082,6 +5114,8 @@ inline Declaration::Method::ReturnType::Builder Declaration::Method::Builder::ge
return Declaration::Method::ReturnType::Builder(_builder);
}
inline Declaration::Method::ReturnType::Builder Declaration::Method::Builder::initReturnType() {
_builder.setDataField< ::uint16_t>(6 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(6 * ::capnp::POINTERS);
return Declaration::Method::ReturnType::Builder(_builder);
}
inline Declaration::Method::ReturnType::Which Declaration::Method::ReturnType::Reader::which() const {
......
......@@ -918,6 +918,8 @@ inline Statement::Block::Builder Statement::Builder::getBlock() {
return Statement::Block::Builder(_builder);
}
inline Statement::Block::Builder Statement::Builder::initBlock() {
_builder.setDataField< ::uint16_t>(0 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(1 * ::capnp::POINTERS);
return Statement::Block::Builder(_builder);
}
inline bool Statement::Reader::hasDocComment() const {
......
......@@ -434,6 +434,72 @@ TEST(Encoding, Groups) {
}
}
TEST(Encoding, InterleavedGroups) {
MallocMessageBuilder builder;
auto root = builder.initRoot<test::TestInterleavedGroups>();
// Init both groups to different values.
{
auto group = root.getGroup1();
group.setFoo(12345678u);
group.setBar(123456789012345llu);
auto corge = group.initCorge();
corge.setGrault(987654321098765llu);
corge.setGarply(12345u);
corge.setPlugh("plugh");
corge.setXyzzy("xyzzy");
group.setWaldo("waldo");
}
{
auto group = root.getGroup2();
group.setFoo(23456789u);
group.setBar(234567890123456llu);
auto corge = group.initCorge();
corge.setGrault(876543210987654llu);
corge.setGarply(23456u);
corge.setPlugh("hgulp");
corge.setXyzzy("yzzyx");
group.setWaldo("odlaw");
}
// Check group1 is still set correctly.
{
auto group = root.asReader().getGroup1();
EXPECT_EQ(12345678u, group.getFoo());
EXPECT_EQ(123456789012345llu, group.getBar());
auto corge = group.getCorge();
EXPECT_EQ(987654321098765llu, corge.getGrault());
EXPECT_EQ(12345u, corge.getGarply());
EXPECT_EQ("plugh", corge.getPlugh());
EXPECT_EQ("xyzzy", corge.getXyzzy());
EXPECT_EQ("waldo", group.getWaldo());
}
// Zero out group 1 and see if it is zero'd.
{
auto group = root.initGroup1().asReader();
EXPECT_EQ(0u, group.getFoo());
EXPECT_EQ(0u, group.getBar());
EXPECT_EQ(test::TestInterleavedGroups::Group1::QUX, group.which());
EXPECT_EQ(0u, group.getQux());
EXPECT_FALSE(group.hasWaldo());
}
// Group 2 should not have been touched.
{
auto group = root.asReader().getGroup2();
EXPECT_EQ(23456789u, group.getFoo());
EXPECT_EQ(234567890123456llu, group.getBar());
auto corge = group.getCorge();
EXPECT_EQ(876543210987654llu, corge.getGrault());
EXPECT_EQ(23456u, corge.getGarply());
EXPECT_EQ("hgulp", corge.getPlugh());
EXPECT_EQ("yzzyx", corge.getXyzzy());
EXPECT_EQ("odlaw", group.getWaldo());
}
}
TEST(Encoding, UnionDefault) {
MallocMessageBuilder builder;
TestUnionDefaults::Reader reader = builder.getRoot<TestUnionDefaults>().asReader();
......
......@@ -2143,6 +2143,11 @@ OrphanBuilder StructBuilder::disown(WirePointerCount ptrIndex) {
return WireHelpers::disown(segment, pointers + ptrIndex);
}
void StructBuilder::clearPointer(WirePointerCount ptrIndex) {
WireHelpers::zeroObject(segment, pointers + ptrIndex);
memset(pointers + ptrIndex, 0, sizeof(WirePointer));
}
void StructBuilder::transferContentFrom(StructBuilder other) {
// Determine the amount of data the builders have in common.
BitCount sharedDataSize = kj::min(dataSize, other.dataSize);
......
......@@ -368,6 +368,9 @@ public:
// Detach the given pointer field from this object. The pointer becomes null, and the child
// object is returned as an orphan.
void clearPointer(WirePointerCount ptrIndex);
// Equivalent to calling disown() and letting the result simply be destroyed.
void transferContentFrom(StructBuilder other);
// Adopt all pointers from `other`, and also copy all data. If `other`'s sections are larger
// than this, the extra data is not transferred, meaning there is a risk of data loss when
......
......@@ -2410,6 +2410,13 @@ inline Node::Struct::Builder Node::Builder::getStruct() {
inline Node::Struct::Builder Node::Builder::initStruct() {
_builder.setDataField<Node::Which>(
6 * ::capnp::ELEMENTS, Node::STRUCT);
_builder.setDataField< ::uint16_t>(7 * ::capnp::ELEMENTS, 0);
_builder.setDataField< ::uint16_t>(12 * ::capnp::ELEMENTS, 0);
_builder.setDataField< ::uint16_t>(13 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(224 * ::capnp::ELEMENTS, 0);
_builder.setDataField< ::uint16_t>(15 * ::capnp::ELEMENTS, 0);
_builder.setDataField< ::uint32_t>(8 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(3 * ::capnp::POINTERS);
return Node::Struct::Builder(_builder);
}
inline Node::Enum::Reader Node::Reader::getEnum() const {
......@@ -2425,6 +2432,7 @@ inline Node::Enum::Builder Node::Builder::getEnum() {
inline Node::Enum::Builder Node::Builder::initEnum() {
_builder.setDataField<Node::Which>(
6 * ::capnp::ELEMENTS, Node::ENUM);
_builder.clearPointer(3 * ::capnp::POINTERS);
return Node::Enum::Builder(_builder);
}
inline Node::Interface::Reader Node::Reader::getInterface() const {
......@@ -2440,6 +2448,7 @@ inline Node::Interface::Builder Node::Builder::getInterface() {
inline Node::Interface::Builder Node::Builder::initInterface() {
_builder.setDataField<Node::Which>(
6 * ::capnp::ELEMENTS, Node::INTERFACE);
_builder.clearPointer(3 * ::capnp::POINTERS);
return Node::Interface::Builder(_builder);
}
inline Node::Const::Reader Node::Reader::getConst() const {
......@@ -2455,6 +2464,8 @@ inline Node::Const::Builder Node::Builder::getConst() {
inline Node::Const::Builder Node::Builder::initConst() {
_builder.setDataField<Node::Which>(
6 * ::capnp::ELEMENTS, Node::CONST);
_builder.clearPointer(3 * ::capnp::POINTERS);
_builder.clearPointer(4 * ::capnp::POINTERS);
return Node::Const::Builder(_builder);
}
inline Node::Annotation::Reader Node::Reader::getAnnotation() const {
......@@ -2470,6 +2481,19 @@ inline Node::Annotation::Builder Node::Builder::getAnnotation() {
inline Node::Annotation::Builder Node::Builder::initAnnotation() {
_builder.setDataField<Node::Which>(
6 * ::capnp::ELEMENTS, Node::ANNOTATION);
_builder.setDataField<bool>(112 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(113 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(114 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(115 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(116 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(117 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(118 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(119 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(120 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(121 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(122 * ::capnp::ELEMENTS, 0);
_builder.setDataField<bool>(123 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(3 * ::capnp::POINTERS);
return Node::Annotation::Builder(_builder);
}
inline bool Node::NestedNode::Reader::hasName() const {
......@@ -3221,6 +3245,9 @@ inline Field::NonGroup::Builder Field::Builder::getNonGroup() {
inline Field::NonGroup::Builder Field::Builder::initNonGroup() {
_builder.setDataField<Field::Which>(
4 * ::capnp::ELEMENTS, Field::NON_GROUP);
_builder.setDataField< ::uint32_t>(1 * ::capnp::ELEMENTS, 0);
_builder.clearPointer(2 * ::capnp::POINTERS);
_builder.clearPointer(3 * ::capnp::POINTERS);
return Field::NonGroup::Builder(_builder);
}
inline bool Field::Reader::hasGroup() const {
......@@ -3261,6 +3288,8 @@ inline Field::Ordinal::Builder Field::Builder::getOrdinal() {
return Field::Ordinal::Builder(_builder);
}
inline Field::Ordinal::Builder Field::Builder::initOrdinal() {
_builder.setDataField< ::uint16_t>(5 * ::capnp::ELEMENTS, 0);
_builder.setDataField< ::uint16_t>(6 * ::capnp::ELEMENTS, 0);
return Field::Ordinal::Builder(_builder);
}
inline bool Field::NonGroup::Reader::hasOffset() const {
......
......@@ -270,18 +270,18 @@ struct TestUnnamedUnion {
}
struct TestGroups {
groups union {
foo group {
groups :union {
foo :group {
corge @0 :Int32;
grault @2 :Int64;
garply @8 :Text;
}
bar group {
bar :group {
corge @3 :Int32;
grault @4 :Text;
garply @5 :Int64;
}
baz group {
baz :group {
corge @1 :Int32;
grault @6 :Text;
garply @7 :Text;
......@@ -289,6 +289,44 @@ struct TestGroups {
}
}
struct TestInterleavedGroups {
group1 :group {
foo @0 :UInt32;
bar @2 :UInt64;
union {
qux @4 :UInt16;
corge :group {
grault @6 :UInt64;
garply @8 :UInt16;
plugh @14 :Text;
xyzzy @16 :Text;
}
fred @12 :Text;
}
waldo @10 :Text;
}
group2 :group {
foo @1 :UInt32;
bar @3 :UInt64;
union {
qux @5 :UInt16;
corge :group {
grault @7 :UInt64;
garply @9 :UInt16;
plugh @15 :Text;
xyzzy @17 :Text;
}
fred @13 :Text;
}
waldo @11 :Text;
}
}
struct TestUnionDefaults {
s16s8s64s8Set @0 :TestUnion =
(union0 = (u0f0s16 = 321), union1 = (u1f0s8 = 123), union2 = (u2f0s64 = 12345678901234567),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment