diff --git a/src/codegen/code-stub-assembler.cc b/src/codegen/code-stub-assembler.cc index f42ee86905..52369f2158 100644 --- a/src/codegen/code-stub-assembler.cc +++ b/src/codegen/code-stub-assembler.cc @@ -7437,24 +7437,38 @@ TNode CodeStubAssembler::ToInteger(SloppyTNode context, TNode CodeStubAssembler::DecodeWord32(SloppyTNode word32, uint32_t shift, uint32_t mask) { - return UncheckedCast(Word32Shr( - Word32And(word32, Int32Constant(mask)), static_cast(shift))); + DCHECK_EQ((mask >> shift) << shift, mask); + return Unsigned(Word32And(Word32Shr(word32, static_cast(shift)), + Int32Constant(mask >> shift))); } TNode CodeStubAssembler::DecodeWord(SloppyTNode word, uint32_t shift, uint32_t mask) { - return Unsigned( - WordShr(WordAnd(word, IntPtrConstant(mask)), static_cast(shift))); + DCHECK_EQ((mask >> shift) << shift, mask); + return Unsigned(WordAnd(WordShr(word, static_cast(shift)), + IntPtrConstant(mask >> shift))); +} + +TNode CodeStubAssembler::UpdateWord32(TNode word, + TNode value, + uint32_t shift, uint32_t mask) { + DCHECK_EQ((mask >> shift) << shift, mask); + // Ensure the {value} fits fully in the mask. + CSA_ASSERT(this, Uint32LessThanOrEqual(value, Uint32Constant(mask >> shift))); + TNode encoded_value = Word32Shl(value, Int32Constant(shift)); + TNode inverted_mask = Int32Constant(~mask); + return Word32Or(Word32And(word, inverted_mask), encoded_value); } TNode CodeStubAssembler::UpdateWord(TNode word, - TNode value, uint32_t shift, - uint32_t mask) { + TNode value, + uint32_t shift, uint32_t mask) { + DCHECK_EQ((mask >> shift) << shift, mask); + // Ensure the {value} fits fully in the mask. + CSA_ASSERT(this, + UintPtrLessThanOrEqual(value, UintPtrConstant(mask >> shift))); TNode encoded_value = WordShl(value, static_cast(shift)); TNode inverted_mask = IntPtrConstant(~static_cast(mask)); - // Ensure the {value} fits fully in the mask. - CSA_ASSERT(this, WordEqual(WordAnd(encoded_value, inverted_mask), - IntPtrConstant(0))); return WordOr(WordAnd(word, inverted_mask), encoded_value); } @@ -10320,7 +10334,7 @@ TNode CodeStubAssembler::CreateAllocationSiteInFeedbackVector( StoreMapNoWriteBarrier(site, RootIndex::kAllocationSiteWithWeakNextMap); // Should match AllocationSite::Initialize. TNode field = UpdateWord( - IntPtrConstant(0), IntPtrConstant(GetInitialFastElementsKind())); + IntPtrConstant(0), UintPtrConstant(GetInitialFastElementsKind())); StoreObjectFieldNoWriteBarrier( site, AllocationSite::kTransitionInfoOrBoilerplateOffset, SmiTag(Signed(field))); diff --git a/src/codegen/code-stub-assembler.h b/src/codegen/code-stub-assembler.h index aec1fe8470..5ac12de2ce 100644 --- a/src/codegen/code-stub-assembler.h +++ b/src/codegen/code-stub-assembler.h @@ -2744,14 +2744,39 @@ class V8_EXPORT_PRIVATE CodeStubAssembler // Returns a node that contains the updated values of a |BitField|. template - TNode UpdateWord(TNode word, TNode value) { + TNode UpdateWord32(TNode word, TNode value) { + return UpdateWord32(word, value, BitField::kShift, BitField::kMask); + } + + // Returns a node that contains the updated values of a |BitField|. + template + TNode UpdateWord(TNode word, TNode value) { return UpdateWord(word, value, BitField::kShift, BitField::kMask); } + // Returns a node that contains the updated values of a |BitField|. + template + TNode UpdateWordInWord32(TNode word, + TNode value) { + return UncheckedCast(TruncateIntPtrToInt32( + Signed(UpdateWord(ChangeUint32ToWord(word), value)))); + } + + // Returns a node that contains the updated values of a |BitField|. + template + TNode UpdateWord32InWord(TNode word, TNode value) { + return UpdateWord(word, ChangeUint32ToWord(value)); + } + // Returns a node that contains the updated {value} inside {word} starting // at {shift} and fitting in {mask}. - TNode UpdateWord(TNode word, TNode value, uint32_t shift, - uint32_t mask); + TNode UpdateWord32(TNode word, TNode value, + uint32_t shift, uint32_t mask); + + // Returns a node that contains the updated {value} inside {word} starting + // at {shift} and fitting in {mask}. + TNode UpdateWord(TNode word, TNode value, + uint32_t shift, uint32_t mask); // Returns true if any of the |T|'s bits in given |word32| are set. template diff --git a/src/objects/shared-function-info.tq b/src/objects/shared-function-info.tq index 6a2caf1220..2eda70da5d 100644 --- a/src/objects/shared-function-info.tq +++ b/src/objects/shared-function-info.tq @@ -30,7 +30,7 @@ bitfield struct SharedFunctionInfoFlags extends uint32 { allow_lazy_compilation: bool: 1 bit; needs_home_object: bool: 1 bit; is_asm_wasm_broken: bool: 1 bit; - function_map_index: int32: 5 bit; + function_map_index: uint32: 5 bit; disabled_optimization_reason: BailoutReason: 4 bit; requires_instance_members_initializer: bool: 1 bit; construct_as_builtin: bool: 1 bit; diff --git a/src/torque/csa-generator.cc b/src/torque/csa-generator.cc index 6d1e5812bb..267a8163f9 100644 --- a/src/torque/csa-generator.cc +++ b/src/torque/csa-generator.cc @@ -721,6 +721,67 @@ void CSAGenerator::EmitInstruction(const StoreReferenceInstruction& instruction, << object << ", " << offset << "}, " << value << ");\n"; } +namespace { +std::string GetBitFieldSpecialization(const BitFieldStructType* container, + const BitField& field) { + std::string suffix = field.num_bits == 1 ? "Bit" : "Bits"; + return "TorqueGenerated" + container->name() + + "Fields::" + CamelifyString(field.name_and_type.name) + suffix; +} +} // namespace + +void CSAGenerator::EmitInstruction(const LoadBitFieldInstruction& instruction, + Stack* stack) { + std::string result_name = FreshNodeName(); + + std::string bit_field_struct = stack->Pop(); + stack->Push(result_name); + + const BitFieldStructType* source_type = instruction.bit_field_struct_type; + const Type* result_type = instruction.bit_field.name_and_type.type; + bool source_uintptr = source_type->IsSubtypeOf(TypeOracle::GetUIntPtrType()); + bool result_uintptr = result_type->IsSubtypeOf(TypeOracle::GetUIntPtrType()); + std::string source_word_type = source_uintptr ? "WordT" : "Word32T"; + std::string decoder = + source_uintptr + ? (result_uintptr ? "DecodeWord" : "DecodeWord32FromWord") + : (result_uintptr ? "DecodeWordFromWord32" : "DecodeWord32"); + + out_ << " " << result_type->GetGeneratedTypeName() << result_name + << " = ca_.UncheckedCast<" << result_type->GetGeneratedTNodeTypeName() + << ">(CodeStubAssembler(state_)." << decoder << "<" + << GetBitFieldSpecialization(source_type, instruction.bit_field) + << ">(ca_.UncheckedCast<" << source_word_type << ">(" << bit_field_struct + << ")));\n"; +} + +void CSAGenerator::EmitInstruction(const StoreBitFieldInstruction& instruction, + Stack* stack) { + std::string result_name = FreshNodeName(); + + std::string value = stack->Pop(); + std::string bit_field_struct = stack->Pop(); + stack->Push(result_name); + + const BitFieldStructType* struct_type = instruction.bit_field_struct_type; + const Type* field_type = instruction.bit_field.name_and_type.type; + bool struct_uintptr = struct_type->IsSubtypeOf(TypeOracle::GetUIntPtrType()); + bool field_uintptr = field_type->IsSubtypeOf(TypeOracle::GetUIntPtrType()); + std::string struct_word_type = struct_uintptr ? "WordT" : "Word32T"; + std::string field_word_type = field_uintptr ? "UintPtrT" : "Uint32T"; + std::string encoder = + struct_uintptr ? (field_uintptr ? "UpdateWord" : "UpdateWord32InWord") + : (field_uintptr ? "UpdateWordInWord32" : "UpdateWord32"); + + out_ << " " << struct_type->GetGeneratedTypeName() << result_name + << " = ca_.UncheckedCast<" << struct_type->GetGeneratedTNodeTypeName() + << ">(CodeStubAssembler(state_)." << encoder << "<" + << GetBitFieldSpecialization(struct_type, instruction.bit_field) + << ">(ca_.UncheckedCast<" << struct_word_type << ">(" << bit_field_struct + << "), ca_.UncheckedCast<" << field_word_type << ">(" << value + << ")));\n"; +} + // static void CSAGenerator::EmitCSAValue(VisitResult result, const Stack& values, diff --git a/src/torque/implementation-visitor.cc b/src/torque/implementation-visitor.cc index 05578d3a0a..f862a025fd 100644 --- a/src/torque/implementation-visitor.cc +++ b/src/torque/implementation-visitor.cc @@ -1931,6 +1931,12 @@ LocationReference ImplementationVisitor::GetLocationReference( ProjectStructField(reference.temporary(), fieldname), reference.temporary_description()); } + if (reference.ReferencedType()->IsBitFieldStructType()) { + const BitFieldStructType* bitfield_struct = + BitFieldStructType::cast(reference.ReferencedType()); + const BitField& field = bitfield_struct->LookupField(fieldname); + return LocationReference::BitFieldAccess(reference, field); + } if (reference.IsHeapReference()) { VisitResult ref = reference.heap_reference(); auto generic_type = StructType::MatchUnaryGeneric( @@ -2114,6 +2120,14 @@ VisitResult ImplementationVisitor::GenerateFetchFromLocation( assembler().Emit(LoadReferenceInstruction{reference.ReferencedType()}); DCHECK_EQ(1, LoweredSlotCount(reference.ReferencedType())); return VisitResult(reference.ReferencedType(), assembler().TopRange(1)); + } else if (reference.IsBitFieldAccess()) { + // First fetch the bitfield struct, then get the bits out of it. + VisitResult bit_field_struct = + GenerateFetchFromLocation(reference.bit_field_struct_location()); + assembler().Emit(LoadBitFieldInstruction{ + BitFieldStructType::cast(bit_field_struct.type()), + reference.bit_field()}); + return VisitResult(reference.ReferencedType(), assembler().TopRange(1)); } else { if (reference.IsHeapSlice()) { ReportError( @@ -2157,6 +2171,21 @@ void ImplementationVisitor::GenerateAssignToLocation( silenced_float_value.stack_range(), referenced_type); } assembler().Emit(StoreReferenceInstruction{referenced_type}); + } else if (reference.IsBitFieldAccess()) { + // First fetch the bitfield struct, then set the updated bits, then store it + // back to where we found it. + VisitResult bit_field_struct = + GenerateFetchFromLocation(reference.bit_field_struct_location()); + VisitResult converted_value = + GenerateImplicitConvert(reference.ReferencedType(), assignment_value); + GenerateCopy(bit_field_struct); + GenerateCopy(converted_value); + assembler().Emit(StoreBitFieldInstruction{ + BitFieldStructType::cast(bit_field_struct.type()), + reference.bit_field()}); + GenerateAssignToLocation( + reference.bit_field_struct_location(), + VisitResult(bit_field_struct.type(), assembler().TopRange(1))); } else { DCHECK(reference.IsTemporary()); ReportError("cannot assign to temporary ", diff --git a/src/torque/implementation-visitor.h b/src/torque/implementation-visitor.h index 96a4ffa455..36493a1fe3 100644 --- a/src/torque/implementation-visitor.h +++ b/src/torque/implementation-visitor.h @@ -83,6 +83,13 @@ class LocationReference { result.call_arguments_ = {object}; return result; } + static LocationReference BitFieldAccess(const LocationReference& object, + BitField field) { + LocationReference result; + result.bit_field_struct_ = std::make_shared(object); + result.bit_field_ = std::move(field); + return result; + } bool IsConst() const { return temporary_.has_value(); } @@ -106,15 +113,32 @@ class LocationReference { DCHECK(IsHeapSlice()); return *heap_slice_; } + bool IsBitFieldAccess() const { + bool is_bitfield_access = bit_field_struct_ != nullptr; + DCHECK_EQ(is_bitfield_access, bit_field_.has_value()); + return is_bitfield_access; + } + const LocationReference& bit_field_struct_location() const { + DCHECK(IsBitFieldAccess()); + return *bit_field_struct_; + } + const BitField& bit_field() const { + DCHECK(IsBitFieldAccess()); + return *bit_field_; + } const Type* ReferencedType() const { if (IsHeapReference()) { return *Type::MatchUnaryGeneric(heap_reference().type(), TypeOracle::GetReferenceGeneric()); - } else if (IsHeapSlice()) { + } + if (IsHeapSlice()) { return *Type::MatchUnaryGeneric(heap_slice().type(), TypeOracle::GetSliceGeneric()); } + if (IsBitFieldAccess()) { + return bit_field_->name_and_type.type; + } return GetVisitResult().type(); } @@ -164,6 +188,13 @@ class LocationReference { VisitResultVector call_arguments_; base::Optional*> binding_; + // The location of the bitfield struct that contains this bitfield, if this + // reference is a bitfield access. Uses a shared_ptr so that LocationReference + // is copyable, allowing us to set this field equal to a copy of a + // stack-allocated LocationReference. + std::shared_ptr bit_field_struct_; + base::Optional bit_field_; + LocationReference() = default; }; diff --git a/src/torque/instructions.cc b/src/torque/instructions.cc index bc70473906..80b0457563 100644 --- a/src/torque/instructions.cc +++ b/src/torque/instructions.cc @@ -313,6 +313,19 @@ void StoreReferenceInstruction::TypeInstruction(Stack* stack, ExpectSubtype(stack->Pop(), TypeOracle::GetHeapObjectType()); } +void LoadBitFieldInstruction::TypeInstruction(Stack* stack, + ControlFlowGraph* cfg) const { + ExpectType(bit_field_struct_type, stack->Pop()); + stack->Push(bit_field.name_and_type.type); +} + +void StoreBitFieldInstruction::TypeInstruction(Stack* stack, + ControlFlowGraph* cfg) const { + ExpectSubtype(bit_field.name_and_type.type, stack->Pop()); + ExpectType(bit_field_struct_type, stack->Pop()); + stack->Push(bit_field_struct_type); +} + bool CallRuntimeInstruction::IsBlockTerminator() const { return is_tailcall || runtime_function->signature().return_type == TypeOracle::GetNeverType(); diff --git a/src/torque/instructions.h b/src/torque/instructions.h index adca598b5d..f751d1e037 100644 --- a/src/torque/instructions.h +++ b/src/torque/instructions.h @@ -33,6 +33,8 @@ class RuntimeFunction; V(CreateFieldReferenceInstruction) \ V(LoadReferenceInstruction) \ V(StoreReferenceInstruction) \ + V(LoadBitFieldInstruction) \ + V(StoreBitFieldInstruction) \ V(CallCsaMacroInstruction) \ V(CallIntrinsicInstruction) \ V(NamespaceConstantInstruction) \ @@ -227,6 +229,29 @@ struct StoreReferenceInstruction : InstructionBase { const Type* type; }; +// Pops a bitfield struct; pushes a bitfield value extracted from it. +struct LoadBitFieldInstruction : InstructionBase { + TORQUE_INSTRUCTION_BOILERPLATE() + LoadBitFieldInstruction(const BitFieldStructType* bit_field_struct_type, + BitField bit_field) + : bit_field_struct_type(bit_field_struct_type), + bit_field(std::move(bit_field)) {} + const BitFieldStructType* bit_field_struct_type; + BitField bit_field; +}; + +// Pops a bitfield value and a bitfield struct; pushes a new bitfield struct +// containing the updated value. +struct StoreBitFieldInstruction : InstructionBase { + TORQUE_INSTRUCTION_BOILERPLATE() + StoreBitFieldInstruction(const BitFieldStructType* bit_field_struct_type, + BitField bit_field) + : bit_field_struct_type(bit_field_struct_type), + bit_field(std::move(bit_field)) {} + const BitFieldStructType* bit_field_struct_type; + BitField bit_field; +}; + struct CallIntrinsicInstruction : InstructionBase { TORQUE_INSTRUCTION_BOILERPLATE() CallIntrinsicInstruction(Intrinsic* intrinsic, diff --git a/src/torque/types.cc b/src/torque/types.cc index e1137d7bda..dd58b5b7f3 100644 --- a/src/torque/types.cc +++ b/src/torque/types.cc @@ -258,6 +258,15 @@ std::string BitFieldStructType::ToExplicitString() const { return "bitfield struct " + name(); } +const BitField& BitFieldStructType::LookupField(const std::string& name) const { + for (const BitField& field : fields_) { + if (field.name_and_type.name == name) { + return field; + } + } + ReportError("Couldn't find bitfield ", name); +} + void AggregateType::CheckForDuplicateFields() const { // Check the aggregate hierarchy and currently defined class for duplicate // field declarations. @@ -499,8 +508,7 @@ std::vector ClassType::ComputeAllFields() const { void ClassType::GenerateAccessors() { // For each field, construct AST snippets that implement a CSA accessor - // function and define a corresponding '.field' operator. The - // implementation iterator will turn the snippets into code. + // function. The implementation iterator will turn the snippets into code. for (auto& field : fields_) { if (field.index || field.name_and_type.type == TypeOracle::GetVoidType()) { continue; @@ -791,12 +799,11 @@ bool IsAllowedAsBitField(const Type* type) { // compelling use case. return false; } - // Any integer-ish type, including bools and enums which inherit from integer - // types, are allowed. + // Any unsigned integer-ish type, including bools and enums which inherit from + // unsigned integer types, are allowed. Currently decoding signed integers is + // not supported. return type->IsSubtypeOf(TypeOracle::GetUint32Type()) || type->IsSubtypeOf(TypeOracle::GetUIntPtrType()) || - type->IsSubtypeOf(TypeOracle::GetInt32Type()) || - type->IsSubtypeOf(TypeOracle::GetIntPtrType()) || type->IsSubtypeOf(TypeOracle::GetBoolType()); } diff --git a/src/torque/types.h b/src/torque/types.h index f607491c52..cf0301397a 100644 --- a/src/torque/types.h +++ b/src/torque/types.h @@ -481,6 +481,8 @@ class V8_EXPORT_PRIVATE BitFieldStructType final : public Type { const std::string& name() const { return decl_->name->value; } const std::vector& fields() const { return fields_; } + const BitField& LookupField(const std::string& name) const; + private: friend class TypeOracle; BitFieldStructType(Namespace* nspace, const Type* parent, diff --git a/test/cctest/torque/test-torque.cc b/test/cctest/torque/test-torque.cc index 5cf70f3374..00d19dd75a 100644 --- a/test/cctest/torque/test-torque.cc +++ b/test/cctest/torque/test-torque.cc @@ -631,6 +631,95 @@ TEST(TestBranchOnBoolOptimization) { asm_tester.GenerateCode(); } +TEST(TestBitFieldLoad) { + CcTest::InitializeVM(); + Isolate* isolate(CcTest::i_isolate()); + i::HandleScope scope(isolate); + const int kNumParams = 5; + CodeAssemblerTester asm_tester(isolate, kNumParams); + TestTorqueAssembler m(asm_tester.state()); + { + // Untag all of the parameters to get plain integer values. + TNode val = + m.UncheckedCast(m.Unsigned(m.SmiToInt32(m.Parameter(0)))); + TNode expected_a = + m.UncheckedCast(m.Unsigned(m.SmiToInt32(m.Parameter(1)))); + TNode expected_b = + m.UncheckedCast(m.Unsigned(m.SmiToInt32(m.Parameter(2)))); + TNode expected_c = + m.UncheckedCast(m.Unsigned(m.SmiToInt32(m.Parameter(3)))); + TNode expected_d = + m.UncheckedCast(m.Unsigned(m.SmiToInt32(m.Parameter(4)))); + + // Call the Torque-defined macro, which verifies that reading each bitfield + // out of val yields the correct result. + m.TestBitFieldLoad(val, expected_a, expected_b, expected_c, expected_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) { + int val = a | ((b & 7) << 1) | (c << 4) | (d << 7); + ft.Call(ft.Val(val), ft.Val(a), ft.Val(b), ft.Val(c), ft.Val(d)); + } + } + } + } +} + +TEST(TestBitFieldStore) { + CcTest::InitializeVM(); + Isolate* isolate(CcTest::i_isolate()); + i::HandleScope scope(isolate); + const int kNumParams = 1; + CodeAssemblerTester asm_tester(isolate, kNumParams); + TestTorqueAssembler m(asm_tester.state()); + { + // Untag the parameters to get a plain integer value. + TNode val = + m.UncheckedCast(m.Unsigned(m.SmiToInt32(m.Parameter(0)))); + + m.TestBitFieldStore(val); + m.Return(m.UndefinedConstant()); + } + FunctionTester ft(asm_tester.GenerateCode(), kNumParams); + + // Test every possible bit combination for this 8-bit value. + for (int i = 0; i < 256; ++i) { + ft.Call(ft.Val(i)); + } +} + +TEST(TestBitFieldUintptrOps) { + CcTest::InitializeVM(); + Isolate* isolate(CcTest::i_isolate()); + i::HandleScope scope(isolate); + const int kNumParams = 2; + CodeAssemblerTester asm_tester(isolate, kNumParams); + TestTorqueAssembler m(asm_tester.state()); + { + // Untag the parameters to get a plain integer value. + TNode val2 = + m.UncheckedCast(m.Unsigned(m.SmiToInt32(m.Parameter(0)))); + TNode val3 = m.UncheckedCast( + m.ChangeUint32ToWord(m.Unsigned(m.SmiToInt32(m.Parameter(1))))); + + m.TestBitFieldUintptrOps(val2, val3); + m.Return(m.UndefinedConstant()); + } + FunctionTester ft(asm_tester.GenerateCode(), kNumParams); + + // Construct the expected test values. + int val2 = 3 | (61 << 5); + int val3 = 1 | (500 << 1) | (0x1cc << 10); + + ft.Call(ft.Val(val2), ft.Val(val3)); +} + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/test/torque/test-torque.tq b/test/torque/test-torque.tq index 6abf21554e..06439929f2 100644 --- a/test/torque/test-torque.tq +++ b/test/torque/test-torque.tq @@ -1028,4 +1028,80 @@ namespace test { } } + bitfield struct TestBitFieldStruct extends uint8 { + a: bool: 1 bit; + b: uint16: 3 bit; + c: uint32: 3 bit; + d: bool: 1 bit; + } + + @export + macro TestBitFieldLoad( + val: TestBitFieldStruct, expectedA: bool, expectedB: uint16, + expectedC: uint32, expectedD: bool) { + check(val.a == expectedA); + check(val.b == expectedB); + check(val.c == expectedC); + check(val.d == expectedD); + } + + @export + macro TestBitFieldStore(val: TestBitFieldStruct) { + let val: TestBitFieldStruct = val; // Get a mutable local copy. + const a: bool = val.a; + const b: uint16 = val.b; + let c: uint32 = val.c; + const d: bool = val.d; + + val.a = !a; + TestBitFieldLoad(val, !a, b, c, d); + + c = Unsigned(7 - Signed(val.c)); + val.c = c; + TestBitFieldLoad(val, !a, b, c, d); + + val.d = val.b == val.c; + TestBitFieldLoad(val, !a, b, c, b == c); + } + + // Some other bitfield structs, to verify getting uintptr values out of word32 + // structs and vice versa. + bitfield struct TestBitFieldStruct2 extends uint32 { + a: uintptr: 5 bit; + b: uintptr: 6 bit; + } + bitfield struct TestBitFieldStruct3 extends uintptr { + c: bool: 1 bit; + d: uint32: 9 bit; + e: uintptr: 17 bit; + } + + @export + macro TestBitFieldUintptrOps( + val2: TestBitFieldStruct2, val3: TestBitFieldStruct3) { + let val2: TestBitFieldStruct2 = val2; // Get a mutable local copy. + let val3: TestBitFieldStruct3 = val3; // Get a mutable local copy. + + // Caller is expected to provide these exact values, so we can verify + // reading values before starting to write anything. + check(val2.a == 3); + check(val2.b == 61); + check(val3.c); + check(val3.d == 500); + check(val3.e == 0x1cc); + + val2.b = 16; + check(val2.a == 3); + check(val2.b == 16); + + val2.b++; + check(val2.a == 3); + check(val2.b == 17); + + val3.d = 99; + val3.e = 1234; + check(val3.c); + check(val3.d == 99); + check(val3.e == 1234); + } }