diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp index d0fc170cb0d0..7799ceca628c 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp @@ -1572,60 +1572,51 @@ void AvmTraceBuilder::op_sload(uint8_t indirect, uint32_t slot_offset, uint32_t auto clk = static_cast(main_trace.size()) + 1; auto [resolved_slot, resolved_dest] = unpack_indirects<2>(indirect, { slot_offset, dest_offset }); - auto read_dest_value = constrained_read_from_memory( - call_ptr, clk, resolved_dest, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); auto read_slot = constrained_read_from_memory( - call_ptr, clk, resolved_slot, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IB); - // TODO: Reenable tag_match - // bool tag_match = read_dest_value.tag_match && read_slot.tag_match; + call_ptr, clk, resolved_slot, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); + // Read the slot value that we will write hints to in a row main_trace.push_back(Row{ .main_clk = clk, - .main_ia = read_dest_value.val, - .main_ib = read_slot.val, - .main_ind_addr_a = FF(read_dest_value.indirect_address), - .main_ind_addr_b = FF(read_slot.indirect_address), + .main_ia = read_slot.val, + .main_ind_addr_a = FF(read_slot.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_dest_value.direct_address), - .main_mem_addr_b = FF(read_slot.direct_address), + .main_mem_addr_a = FF(read_slot.direct_address), .main_pc = pc, // No PC increment here since this is the same opcode as the rows created below .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_b = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_dest_value.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(read_slot.is_indirect)), - // .main_tag_err = FF(static_cast(tag_match)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_slot.is_indirect)), + .main_tag_err = FF(static_cast(!read_slot.tag_match)), }); clk++; + AddressWithMode write_dst = resolved_dest; + // Loop over the size and write the hints to memory for (uint32_t i = 0; i < size; i++) { FF value = execution_hints.get_side_effect_hints().at(side_effect_counter); - mem_trace_builder.write_into_memory(call_ptr, - clk, - IntermRegister::IA, - read_dest_value.direct_address + i, - value, - AvmMemoryTag::FF, - AvmMemoryTag::FF); + auto write_a = constrained_write_to_memory( + call_ptr, clk, write_dst, value, AvmMemoryTag::U0, AvmMemoryTag::FF, IntermRegister::IA); auto row = Row{ .main_clk = clk, .main_ia = value, .main_ib = read_slot.val + i, // slot increments each time + .main_ind_addr_a = write_a.indirect_address, .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = read_dest_value.direct_address + i, + .main_mem_addr_a = write_a.direct_address, // direct address incremented at end of the loop .main_pc = pc, // No PC increment here since this is the same opcode for all loop iterations - .main_r_in_tag = static_cast(AvmMemoryTag::FF), .main_rwa = 1, .main_sel_mem_op_a = 1, .main_sel_op_sload = FF(1), .main_sel_q_kernel_output_lookup = 1, + .main_sel_resolve_ind_addr_a = FF(static_cast(write_a.is_indirect)), + .main_tag_err = FF(static_cast(!write_a.tag_match)), .main_w_in_tag = static_cast(AvmMemoryTag::FF), }; // Output storage read to kernel outputs (performs lookup) + // Tuples of (slot, value) in the kernel lookup kernel_trace_builder.op_sload(clk, side_effect_counter, row.main_ib, row.main_ia); // Constrain gas cost @@ -1634,6 +1625,9 @@ void AvmTraceBuilder::op_sload(uint8_t indirect, uint32_t slot_offset, uint32_t main_trace.push_back(row); side_effect_counter++; clk++; + + // After the first loop, all future write destinations are direct, increment the direct address + write_dst = AddressWithMode{ AddressingMode::DIRECT, write_a.direct_address + 1 }; } pc++; } @@ -1644,48 +1638,44 @@ void AvmTraceBuilder::op_sstore(uint8_t indirect, uint32_t src_offset, uint32_t auto [resolved_src, resolved_slot] = unpack_indirects<2>(indirect, { src_offset, slot_offset }); - auto read_src_value = constrained_read_from_memory( - call_ptr, clk, resolved_src, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); auto read_slot = constrained_read_from_memory( - call_ptr, clk, resolved_slot, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IB); - // TODO: Reenable tag_match - // bool tag_match = read_src_value.tag_match && read_slot.tag_match; + call_ptr, clk, resolved_slot, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); main_trace.push_back(Row{ .main_clk = clk, - .main_ia = read_src_value.val, - .main_ib = read_slot.val, - .main_ind_addr_a = FF(read_src_value.indirect_address), - .main_ind_addr_b = FF(read_slot.indirect_address), + .main_ia = read_slot.val, + .main_ind_addr_a = FF(read_slot.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_src_value.direct_address), - .main_mem_addr_b = FF(read_slot.direct_address), + .main_mem_addr_a = FF(read_slot.direct_address), .main_pc = pc, // No PC increment here since this is the same opcode as the rows created below .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_b = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_src_value.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(read_slot.is_indirect)), - // TODO: Reenable when tag_match is wokring sstore - // .main_tag_err = FF(static_cast(tag_match)), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_slot.is_indirect)), + .main_tag_err = FF(static_cast(!read_slot.tag_match)), .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), }); clk++; + AddressWithMode read_src = resolved_src; + + // This loop reads a _size_ number of elements from memory and places them into a tuple of (ele, slot) + // in the kernel lookup. for (uint32_t i = 0; i < size; i++) { - auto read_a = mem_trace_builder.read_and_load_from_memory( - call_ptr, clk, IntermRegister::IA, read_src_value.direct_address + i, AvmMemoryTag::FF, AvmMemoryTag::U0); + auto read_a = constrained_read_from_memory( + call_ptr, clk, read_src, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); Row row = Row{ .main_clk = clk, .main_ia = read_a.val, .main_ib = read_slot.val + i, // slot increments each time + .main_ind_addr_a = read_a.indirect_address, .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = read_src_value.direct_address + i, + .main_mem_addr_a = read_a.direct_address, // direct address incremented at end of the loop .main_pc = pc, .main_r_in_tag = static_cast(AvmMemoryTag::FF), .main_sel_mem_op_a = 1, .main_sel_q_kernel_output_lookup = 1, + .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), .main_tag_err = FF(static_cast(!read_a.tag_match)), }; row.main_sel_op_sstore = FF(1); @@ -1697,6 +1687,8 @@ void AvmTraceBuilder::op_sstore(uint8_t indirect, uint32_t src_offset, uint32_t main_trace.push_back(row); side_effect_counter++; clk++; + // All future reads are direct, increment the direct address + read_src = AddressWithMode{ AddressingMode::DIRECT, read_a.direct_address + 1 }; } pc++; } @@ -2553,14 +2545,14 @@ uint32_t AvmTraceBuilder::read_slice_to_memory(uint8_t space_id, * stored * @param function_selector_offset An index in memory pointing to the function selector of the external call (TEMP) */ -void AvmTraceBuilder::op_call([[maybe_unused]] uint8_t indirect, - [[maybe_unused]] uint32_t gas_offset, - [[maybe_unused]] uint32_t addr_offset, - [[maybe_unused]] uint32_t args_offset, - [[maybe_unused]] uint32_t args_size, - [[maybe_unused]] uint32_t ret_offset, - [[maybe_unused]] uint32_t ret_size, - [[maybe_unused]] uint32_t success_offset, +void AvmTraceBuilder::op_call(uint8_t indirect, + uint32_t gas_offset, + uint32_t addr_offset, + uint32_t args_offset, + uint32_t args_size, + uint32_t ret_offset, + uint32_t ret_size, + uint32_t success_offset, [[maybe_unused]] uint32_t function_selector_offset) { auto clk = static_cast(main_trace.size()) + 1; @@ -2568,7 +2560,7 @@ void AvmTraceBuilder::op_call([[maybe_unused]] uint8_t indirect, gas_trace_builder.constrain_gas_for_external_call( clk, static_cast(hint.l2_gas_used), static_cast(hint.da_gas_used)); - // We can load up to 4 things per row + auto [resolved_gas_offset, resolved_addr_offset, resolved_args_offset, @@ -2577,70 +2569,52 @@ void AvmTraceBuilder::op_call([[maybe_unused]] uint8_t indirect, resolved_success_offset] = unpack_indirects<6>(indirect, { gas_offset, addr_offset, args_offset, args_size, ret_offset, success_offset }); - // Should read the address next to read_gas as well (tuple of gas values) - auto read_gas = constrained_read_from_memory( + // Should read the address next to read_gas as well (tuple of gas values (l2Gas, daGas)) + auto read_gas_l2 = constrained_read_from_memory( call_ptr, clk, resolved_gas_offset, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); + auto read_gas_da = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IB, read_gas_l2.direct_address + 1, AvmMemoryTag::FF, AvmMemoryTag::U0); auto read_addr = constrained_read_from_memory( call_ptr, clk, resolved_addr_offset, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IC); auto read_args = constrained_read_from_memory( call_ptr, clk, resolved_args_offset, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::ID); - bool tag_match = read_gas.tag_match && read_addr.tag_match && read_args.tag_match; + bool tag_match = read_gas_l2.tag_match && read_gas_da.tag_match && read_addr.tag_match && read_args.tag_match; // We read the input and output addresses in one row as they should contain FF elements main_trace.push_back(Row{ .main_clk = clk, - .main_ia = read_gas.val, /* gas_offset */ - .main_ic = read_addr.val, /* addr_offset */ - .main_id = read_args.val, /* args_offset */ - .main_ind_addr_a = FF(read_gas.indirect_address), + .main_ia = read_gas_l2.val, /* gas_offset_l2 */ + .main_ib = read_gas_da.val, /* gas_offset_da */ + .main_ic = read_addr.val, /* addr_offset */ + .main_id = read_args.val, /* args_offset */ + .main_ind_addr_a = FF(read_gas_l2.indirect_address), .main_ind_addr_c = FF(read_addr.indirect_address), .main_ind_addr_d = FF(read_args.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_gas.direct_address), + .main_mem_addr_a = FF(read_gas_l2.direct_address), + .main_mem_addr_b = FF(read_gas_l2.direct_address + 1), .main_mem_addr_c = FF(read_addr.direct_address), .main_mem_addr_d = FF(read_args.direct_address), .main_pc = FF(pc), .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), .main_sel_mem_op_a = FF(1), + .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), .main_sel_mem_op_d = FF(1), .main_sel_op_external_call = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_gas.is_indirect)), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_gas_l2.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(read_addr.is_indirect)), .main_sel_resolve_ind_addr_d = FF(static_cast(read_args.is_indirect)), .main_tag_err = FF(static_cast(!tag_match)), }); clk++; - // Read the rest on a separate line, remember that the 4th operand is indirect - auto read_ret = constrained_read_from_memory( - call_ptr, clk, resolved_ret_offset, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); - main_trace.push_back(Row{ - .main_clk = clk, - .main_ia = read_ret.val, /* ret_offset */ - .main_ind_addr_a = FF(read_ret.indirect_address), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_ret.direct_address), - .main_pc = FF(pc), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), - .main_sel_mem_op_a = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_ret.is_indirect)), - }); - clk++; - auto mem_read_success = constrained_read_from_memory( - call_ptr, clk, resolved_success_offset, AvmMemoryTag::U32, AvmMemoryTag::U0, IntermRegister::IA); - main_trace.push_back(Row{ - .main_clk = clk, - .main_ia = mem_read_success.val, /* success_offset */ - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(success_offset), - .main_pc = FF(pc), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), - .main_sel_mem_op_a = FF(1), - }); - clk++; + // The return data hint is used for now, we check it has the same length as the ret_size + ASSERT(hint.return_data.size() == ret_size); + // Write the return data to memory uint32_t num_rows = write_slice_to_memory( call_ptr, clk, resolved_ret_offset, AvmMemoryTag::U0, AvmMemoryTag::FF, internal_return_ptr, hint.return_data); clk += num_rows; + // Write the success flag to memory write_slice_to_memory(call_ptr, clk, resolved_success_offset, diff --git a/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp b/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp index 076f70cabae1..dc18269ae45b 100644 --- a/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp @@ -1723,13 +1723,12 @@ TEST_F(AvmExecutionTests, kernelOutputEmitOpcodes) TEST_F(AvmExecutionTests, kernelOutputStorageLoadOpcodeSimple) { // Sload from a value that has not previously been written to will require a hint to process - std::string bytecode_hex = to_hex(OpCode::SET) + // opcode SET - "00" // Indirect flag - "03" // U32 - "00000009" // value 9 - "00000001" // dst_offset 1 - // Cast set to field - + to_hex(OpCode::CAST) + // opcode CAST + std::string bytecode_hex = to_hex(OpCode::SET) + // opcode SET + "00" // Indirect flag + "03" // U32 + "00000009" // value 9 + "00000001" // dst_offset 1 + + to_hex(OpCode::CAST) + // opcode CAST (Cast set to field) "00" // Indirect flag "06" // tag field "00000001" // dst 1 @@ -1737,7 +1736,7 @@ TEST_F(AvmExecutionTests, kernelOutputStorageLoadOpcodeSimple) + to_hex(OpCode::SLOAD) + // opcode SLOAD "00" // Indirect flag "00000001" // slot offset 1 - "00000001" // slot offset 1 + "00000001" // slot size 1 "00000002" // write storage value to offset 2 + to_hex(OpCode::RETURN) + // opcode RETURN "00" // Indirect flag @@ -1794,7 +1793,7 @@ TEST_F(AvmExecutionTests, kernelOutputStorageLoadOpcodeComplex) + to_hex(OpCode::SLOAD) + // opcode SLOAD "00" // Indirect flag (second operand indirect - dest offset) "00000001" // slot offset 1 - "00000002" // slot offset 2 + "00000002" // slot size 2 "00000002" // write storage value to offset 2 + to_hex(OpCode::RETURN) + // opcode RETURN "00" // Indirect flag @@ -2129,74 +2128,83 @@ TEST_F(AvmExecutionTests, kernelOutputHashExistsOpcodes) validate_trace(std::move(trace), public_inputs); } -// TEST_F(AvmExecutionTests, opCallOpcodes) -// { -// std::string bytecode_preamble; -// // Gas offset preamble -// bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for gas offset indirect -// "00" // Indirect flag -// "03" // U32 -// "00000010" // val 16 (address where gas offset is located) -// "00000011" + // dst_offset 17 -// to_hex(OpCode::SET) + // opcode SET for value stored in gas offset -// "00" // Indirect flag -// "03" // U32 -// "00000011" // val i -// "00000000"; -// // args offset preamble -// bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for args offset indirect -// "00" // Indirect flag -// "03" // U32 -// "00000100" // val i -// "00000012" + // dst_offset 0 -// to_hex(OpCode::SET) + // opcode SET for value stored in args offset -// "00" // Indirect flag -// "03" // U32 -// "00000012" // val i -// "00000001"; -// // ret offset preamble -// bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for ret offset indirect -// "00" // Indirect flag -// "03" // U32 -// "00000008" // val i -// "00000004" + // dst_offset 0 -// to_hex(OpCode::SET) + // opcode SET for value stored in ret offset -// "00" // Indirect flag -// "03" // U32 -// "00000002" // val i -// "00000007"; -// std::string bytecode_hex = bytecode_preamble // SET gas, addr, args size, ret offset, success, function -// selector -// + to_hex(OpCode::CALL) + // opcode CALL -// "15" // Indirect flag -// "00000000" // gas offset -// "00000001" // addr offset -// "00000002" // args offset -// "00000003" // args size offset -// "00000004" // ret offset -// "00000007" // ret size -// "0000000a" // success offset -// "00000006" // function_selector_offset -// + to_hex(OpCode::RETURN) + // opcode RETURN -// "00" // Indirect flag -// "00000008" // ret offset 8 -// "00000003"; // ret size 3 - -// auto bytecode = hex_to_bytes(bytecode_hex); -// auto instructions = Deserialization::parse(bytecode); - -// std::vector calldata = {}; -// std::vector returndata = {}; - -// // Generate Hint for call operation -// auto execution_hints = ExecutionHints().with_externalcall_hints( -// { { .success = 1, .return_data = { 9, 8 }, .l2_gas_used = 0, .da_gas_used = 0 } }); - -// auto trace = Execution::gen_trace(instructions, returndata, calldata, public_inputs_vec, execution_hints); -// EXPECT_EQ(returndata, std::vector({ 9, 8, 1 })); // The 1 represents the success - -// validate_trace(std::move(trace), public_inputs); -// } +TEST_F(AvmExecutionTests, opCallOpcodes) +{ + // Calldata for l2_gas, da_gas, contract_address, nested_call_args (4 elements), + std::vector calldata = { 17, 10, 34802342, 1, 2, 3, 4 }; + std::string bytecode_preamble; + // Set up Gas offsets + bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for gas offset indirect + "00" // Indirect flag + "03" // U32 + "00000000" // val 0 (address where gas tuple is located) + "00000011"; // dst_offset 17 + // Set up contract address offset + bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for args offset indirect + "00" // Indirect flag + "03" // U32 + "00000002" // val 2 (where contract address is located) + "00000012"; // dst_offset 18 + // Set up args offset + bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for ret offset indirect + "00" // Indirect flag + "03" // U32 + "00000003" // val 3 (the start of the args array) + "00000013"; // dst_offset 19 + // Set up args size offset + bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for ret offset indirect + "00" // Indirect flag + "03" // U32 + "00000004" // val 4 (the length of the args array) + "00000014"; // dst_offset 20 + // Set up the ret offset + bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for ret offset indirect + "00" // Indirect flag + "03" // U32 + "00000100" // val 256 (the start of where to write the return data) + "00000015"; // dst_offset 21 + // Set up the success offset + bytecode_preamble += to_hex(OpCode::SET) + // opcode SET for ret offset indirect + "00" // Indirect flag + "03" // U32 + "00000102" // val 258 (write the success flag at ret_offset + ret_size) + "00000016"; // dst_offset 22 + + std::string bytecode_hex = to_hex(OpCode::CALLDATACOPY) + // opcode CALLDATACOPY + "00" // Indirect flag + "00000000" // cd_offset + "00000007" // copy_size + "00000000" // dst_offset + + bytecode_preamble // Load up memory offsets + + to_hex(OpCode::CALL) + // opcode CALL + "3f" // Indirect flag + "00000011" // gas offset + "00000012" // addr offset + "00000013" // args offset + "00000014" // args size offset + "00000015" // ret offset + "00000002" // ret size + "00000016" // success offset + "00000017" // function_selector_offset + + to_hex(OpCode::RETURN) + // opcode RETURN + "00" // Indirect flag + "00000100" // ret offset 8 + "00000003"; // ret size 3 (extra read is for the success flag) + + auto bytecode = hex_to_bytes(bytecode_hex); + auto instructions = Deserialization::parse(bytecode); + + std::vector returndata = {}; + + // Generate Hint for call operation + auto execution_hints = ExecutionHints().with_externalcall_hints( + { { .success = 1, .return_data = { 9, 8 }, .l2_gas_used = 0, .da_gas_used = 0 } }); + + auto trace = Execution::gen_trace(instructions, returndata, calldata, public_inputs_vec, execution_hints); + EXPECT_EQ(returndata, std::vector({ 9, 8, 1 })); // The 1 represents the success + + validate_trace(std::move(trace), public_inputs); +} TEST_F(AvmExecutionTests, opGetContractInstanceOpcodes) { diff --git a/barretenberg/cpp/src/barretenberg/vm/tests/avm_kernel.test.cpp b/barretenberg/cpp/src/barretenberg/vm/tests/avm_kernel.test.cpp index a985bb45a635..96ec0edc0851 100644 --- a/barretenberg/cpp/src/barretenberg/vm/tests/avm_kernel.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/tests/avm_kernel.test.cpp @@ -1084,7 +1084,7 @@ TEST_F(AvmKernelOutputPositiveTests, kernelSload) /*ib=*/slot, /*mem_addr_b=*/0, /*ind_b=*/false, - /*r_in_tag=*/AvmMemoryTag::FF, + /*r_in_tag=*/AvmMemoryTag::U0, // Kernel Sload is writing to memory /*side_effect_counter=*/0, /*rwa=*/1, /*no_b=*/true); @@ -1126,7 +1126,7 @@ TEST_F(AvmKernelOutputPositiveTests, kernelSstore) /*ib=*/slot, /*mem_addr_b=*/0, /*ind_b*/ false, - /*w_in_tag=*/AvmMemoryTag::FF, + /*r_in_tag=*/AvmMemoryTag::FF, /*side_effect_counter=*/0, /*rwa=*/0, /*no_b=*/true);