From 4268466383f379f474a4675e390e1a5f5519a45b Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 9 Sep 2018 23:33:08 +0300 Subject: [PATCH] [D3D12] DXBC texture bindings (but no samplers and fetch) --- src/xenia/gpu/d3d12/d3d12_shader.cc | 10 +- src/xenia/gpu/d3d12/d3d12_shader.h | 7 +- src/xenia/gpu/d3d12/pipeline_cache.cc | 10 +- src/xenia/gpu/dxbc_shader_translator.cc | 257 +++++++++++++++++++++++- src/xenia/gpu/dxbc_shader_translator.h | 44 ++++ 5 files changed, 308 insertions(+), 20 deletions(-) diff --git a/src/xenia/gpu/d3d12/d3d12_shader.cc b/src/xenia/gpu/d3d12/d3d12_shader.cc index 80c1a653d..6d937d1a1 100644 --- a/src/xenia/gpu/d3d12/d3d12_shader.cc +++ b/src/xenia/gpu/d3d12/d3d12_shader.cc @@ -27,18 +27,20 @@ D3D12Shader::~D3D12Shader() { } } -#if 0 void D3D12Shader::SetTexturesAndSamplers( - const HlslShaderTranslator::TextureSRV* texture_srvs, + const DxbcShaderTranslator::TextureSRV* texture_srvs, uint32_t texture_srv_count, const uint32_t* sampler_fetch_constants, uint32_t sampler_count) { + used_texture_mask_ = 0; for (uint32_t i = 0; i < texture_srv_count; ++i) { TextureSRV& srv = texture_srvs_[i]; - const HlslShaderTranslator::TextureSRV& translator_srv = texture_srvs[i]; + const DxbcShaderTranslator::TextureSRV& translator_srv = texture_srvs[i]; srv.fetch_constant = translator_srv.fetch_constant; srv.dimension = translator_srv.dimension; + used_texture_mask_ |= 1u << translator_srv.fetch_constant; } texture_srv_count_ = texture_srv_count; +#if 0 // If there's a texture, there's a sampler for it. used_texture_mask_ = 0; for (uint32_t i = 0; i < sampler_count; ++i) { @@ -47,8 +49,8 @@ void D3D12Shader::SetTexturesAndSamplers( used_texture_mask_ |= 1u << sampler_fetch_constant; } sampler_count_ = sampler_count; -} #endif +} bool D3D12Shader::DisassembleDXBC() { if (!host_disassembly_.empty()) { diff --git a/src/xenia/gpu/d3d12/d3d12_shader.h b/src/xenia/gpu/d3d12/d3d12_shader.h index a374608c3..ceed8a55e 100644 --- a/src/xenia/gpu/d3d12/d3d12_shader.h +++ b/src/xenia/gpu/d3d12/d3d12_shader.h @@ -10,8 +10,7 @@ #ifndef XENIA_GPU_D3D12_D3D12_SHADER_H_ #define XENIA_GPU_D3D12_D3D12_SHADER_H_ -// TODO(Triang3l): Remove hlsl_shader_translator. -#include "xenia/gpu/hlsl_shader_translator.h" +#include "xenia/gpu/dxbc_shader_translator.h" #include "xenia/gpu/shader.h" #include "xenia/ui/d3d12/d3d12_api.h" @@ -25,12 +24,10 @@ class D3D12Shader : public Shader { const uint32_t* dword_ptr, uint32_t dword_count); ~D3D12Shader() override; -#if 0 void SetTexturesAndSamplers( - const HlslShaderTranslator::TextureSRV* texture_srvs, + const DxbcShaderTranslator::TextureSRV* texture_srvs, uint32_t texture_srv_count, const uint32_t* sampler_fetch_constants, uint32_t sampler_count); -#endif bool DisassembleDXBC(); diff --git a/src/xenia/gpu/d3d12/pipeline_cache.cc b/src/xenia/gpu/d3d12/pipeline_cache.cc index 1c47c277f..f35d824cc 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.cc +++ b/src/xenia/gpu/d3d12/pipeline_cache.cc @@ -198,15 +198,15 @@ bool PipelineCache::TranslateShader(D3D12Shader* shader, // TODO(Triang3l): Re-enable this when the DXBC shader translators supports // textures. -#if 0 - uint32_t texture_srv_count, sampler_count; - const HlslShaderTranslator::TextureSRV* texture_srvs = + uint32_t texture_srv_count; + const DxbcShaderTranslator::TextureSRV* texture_srvs = shader_translator_->GetTextureSRVs(texture_srv_count); +#if 0 + uint32_t sampler_count; const uint32_t* sampler_fetch_constants = shader_translator_->GetSamplerFetchConstants(sampler_count); - shader->SetTexturesAndSamplers(texture_srvs, texture_srv_count, - sampler_fetch_constants, sampler_count); #endif + shader->SetTexturesAndSamplers(texture_srvs, texture_srv_count, nullptr, 0); if (shader->is_valid()) { XELOGGPU("Generated %s shader (%db) - hash %.16" PRIX64 ":\n%s\n", diff --git a/src/xenia/gpu/dxbc_shader_translator.cc b/src/xenia/gpu/dxbc_shader_translator.cc index 1b6bd929d..3319eb230 100644 --- a/src/xenia/gpu/dxbc_shader_translator.cc +++ b/src/xenia/gpu/dxbc_shader_translator.cc @@ -19,6 +19,7 @@ #include "xenia/base/assert.h" #include "xenia/base/math.h" +#include "xenia/base/string.h" DEFINE_bool(dxbc_indexable_temps, true, "Use indexable temporary registers in translated DXBC shaders for " @@ -69,6 +70,8 @@ void DxbcShaderTranslator::Reset() { system_temp_count_current_ = 0; system_temp_count_max_ = 0; writes_depth_ = false; + texture_srvs_.clear(); + sampler_bindings_.clear(); std::memset(&stat_, 0, sizeof(stat_)); } @@ -474,6 +477,8 @@ void DxbcShaderTranslator::StartTranslation() { system_temp_ps_pc_p0_a0_ = PushSystemTemp(true); system_temp_aL_ = PushSystemTemp(true); system_temp_loop_count_ = PushSystemTemp(true); + system_temp_grad_h_lod_ = PushSystemTemp(true); + system_temp_grad_v_ = PushSystemTemp(true); if (is_vertex_shader()) { system_temp_position_ = PushSystemTemp(true); } else if (is_pixel_shader()) { @@ -653,7 +658,9 @@ void DxbcShaderTranslator::CompleteShaderCode() { // - system_temp_ps_pc_p0_a0_. // - system_temp_aL_. // - system_temp_loop_count_. - PopSystemTemp(4); + // - system_temp_grad_h_lod_. + // - system_temp_grad_v_. + PopSystemTemp(6); // Write stage-specific epilogue. if (is_vertex_shader()) { @@ -2298,6 +2305,126 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction( StoreResult(instr.result, system_temp_pv_, false); } +uint32_t DxbcShaderTranslator::FindOrAddTextureSRV(uint32_t fetch_constant, + TextureDimension dimension) { + // 1D and 2D textures (including stacked ones) are treated as 2D arrays for + // binding and coordinate simplicity. + if (dimension == TextureDimension::k1D) { + dimension = TextureDimension::k2D; + } + // 1 is added to the return value because T0/t0 is shared memory. + for (uint32_t i = 0; i < uint32_t(texture_srvs_.size()); ++i) { + const TextureSRV& texture_srv = texture_srvs_[i]; + if (texture_srv.fetch_constant == fetch_constant && + texture_srv.dimension == dimension) { + return 1 + i; + } + } + TextureSRV new_texture_srv; + new_texture_srv.fetch_constant = fetch_constant; + new_texture_srv.dimension = dimension; + const char* dimension_name; + switch (dimension) { + case TextureDimension::k3D: + dimension_name = "3d"; + break; + case TextureDimension::kCube: + dimension_name = "cube"; + break; + default: + dimension_name = "2d"; + } + new_texture_srv.name = + xe::format_string("xe_texture%u_%s", fetch_constant, dimension_name); + uint32_t srv_register = 1 + uint32_t(texture_srvs_.size()); + texture_srvs_.push_back(new_texture_srv); + return srv_register; +} + +void DxbcShaderTranslator::ProcessTextureFetchInstruction( + const ParsedTextureFetchInstruction& instr) { + // TODO(Triang3l): Predicate. + + bool store_result = false; + + DxbcSourceOperand operand; + uint32_t operand_length = 0; + if (instr.operand_count >= 1) { + LoadDxbcSourceOperand(instr.operands[0], operand); + operand_length = DxbcSourceOperandLength(operand); + } + + uint32_t tfetch_index = instr.operands[1].storage_index; + // Fetch constants are laid out like: + // tf0[0] tf0[1] tf0[2] tf0[3] + // tf0[4] tf0[5] tf1[0] tf1[1] + // tf1[2] tf1[3] tf1[4] tf1[5] + uint32_t tfetch_pair_offset = (tfetch_index >> 1) * 3; + + // TODO(Triang3l): kTextureFetch, kGetTextureBorderColorFrac, + // kGetTextureComputedLod, kGetTextureGradients, kGetTextureWeights, + if (instr.opcode == FetchOpcode::kTextureFetch || + instr.opcode == FetchOpcode::kGetTextureComputedLod) { + store_result = true; + + uint32_t srv_register = FindOrAddTextureSRV(tfetch_index, instr.dimension); + // 3D or 2D stacked is selected dynamically. + uint32_t srv_register_3d; + if (instr.dimension == TextureDimension::k3D) { + srv_register_3d = + FindOrAddTextureSRV(tfetch_index, TextureDimension::k2D); + } else { + srv_register_3d = UINT32_MAX; + } + + // TODO(Triang3l): Sampler, actually sample instead of this stub. + shader_code_.push_back(ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_MOV) | + ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(8)); + shader_code_.push_back( + EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_TEMP, 0b1111, 1)); + shader_code_.push_back(system_temp_pv_); + shader_code_.push_back(EncodeVectorSwizzledOperand( + D3D10_SB_OPERAND_TYPE_IMMEDIATE32, kSwizzleXYZW, 0)); + shader_code_.push_back(0x3F800000); + shader_code_.push_back(0x3F800000); + shader_code_.push_back(0x3F800000); + shader_code_.push_back(0x3F800000); + ++stat_.instruction_count; + ++stat_.mov_instruction_count; + } else if (instr.opcode == FetchOpcode::kSetTextureLod) { + shader_code_.push_back( + ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_MOV) | + ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3 + operand_length)); + shader_code_.push_back( + EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_TEMP, 0b1000, 1)); + shader_code_.push_back(system_temp_grad_h_lod_); + UseDxbcSourceOperand(operand, kSwizzleXYZW, 0); + ++stat_.instruction_count; + ++stat_.mov_instruction_count; + } else if (instr.opcode == FetchOpcode::kSetTextureGradientsHorz || + instr.opcode == FetchOpcode::kSetTextureGradientsVert) { + shader_code_.push_back( + ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_MOV) | + ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(3 + operand_length)); + shader_code_.push_back( + EncodeVectorMaskedOperand(D3D10_SB_OPERAND_TYPE_TEMP, 0b0111, 1)); + shader_code_.push_back(instr.opcode == FetchOpcode::kSetTextureGradientsVert + ? system_temp_grad_v_ + : system_temp_grad_h_lod_); + UseDxbcSourceOperand(operand, kSwizzleXYZW); + ++stat_.instruction_count; + ++stat_.mov_instruction_count; + } + + if (instr.operand_count >= 1) { + UnloadDxbcSourceOperand(operand); + } + + if (store_result) { + StoreResult(instr.result, system_temp_pv_, false); + } +} + void DxbcShaderTranslator::ProcessVectorAluInstruction( const ParsedAluInstruction& instr) { // TODO(Triang3l): Predicate. @@ -4362,29 +4489,62 @@ void DxbcShaderTranslator::WriteResourceDefinitions() { } // *************************************************************************** - // Bindings, in t#, cb# order + // Bindings, in s#, t#, cb# order // *************************************************************************** // Write used resource names, except for constant buffers because we have // their names already. new_offset = (uint32_t(shader_object_.size()) - chunk_position_dwords) * sizeof(uint32_t); + uint32_t sampler_name_offset = new_offset; + for (uint32_t i = 0; i < uint32_t(sampler_bindings_.size()); ++i) { + new_offset += + AppendString(shader_object_, sampler_bindings_[i].name.c_str()); + } uint32_t shared_memory_name_offset = new_offset; new_offset += AppendString(shader_object_, "xe_shared_memory"); - // TODO(Triang3l): Texture and sampler names. + uint32_t texture_name_offset = new_offset; + for (uint32_t i = 0; i < uint32_t(texture_srvs_.size()); ++i) { + new_offset += AppendString(shader_object_, texture_srvs_[i].name.c_str()); + } // Write the offset to the header. shader_object_[chunk_position_dwords + 3] = new_offset; + // Samplers. + for (uint32_t i = 0; i < uint32_t(sampler_bindings_.size()); ++i) { + const SamplerBinding& sampler_binding = sampler_bindings_[i]; + shader_object_.push_back(sampler_name_offset); + // D3D_SIT_SAMPLER. + shader_object_.push_back(3); + // No D3D_RESOURCE_RETURN_TYPE. + shader_object_.push_back(0); + // D3D_SRV_DIMENSION_UNKNOWN (not an SRV). + shader_object_.push_back(0); + // Multisampling not applicable. + shader_object_.push_back(0); + // Register s[i]. + shader_object_.push_back(i); + // One binding. + shader_object_.push_back(1); + // No D3D_SHADER_INPUT_FLAGS. + shader_object_.push_back(0); + // Register space 0. + shader_object_.push_back(0); + // Sampler ID S[i]. + shader_object_.push_back(i); + sampler_name_offset += GetStringLength(sampler_binding.name.c_str()); + } + // Shared memory. shader_object_.push_back(shared_memory_name_offset); // D3D_SIT_BYTEADDRESS. shader_object_.push_back(7); // D3D_RETURN_TYPE_MIXED. shader_object_.push_back(6); - // D3D_SRV_DIMENSION_UNKNOWN. + // D3D_SRV_DIMENSION_BUFFER. shader_object_.push_back(1); - // Not multisampled. + // Multisampling not applicable. shader_object_.push_back(0); // Register t0. shader_object_.push_back(0); @@ -4397,7 +4557,40 @@ void DxbcShaderTranslator::WriteResourceDefinitions() { // SRV ID T0. shader_object_.push_back(0); - // TODO(Triang3l): Textures and samplers. + for (uint32_t i = 0; i < uint32_t(texture_srvs_.size()); ++i) { + const TextureSRV& texture_srv = texture_srvs_[i]; + shader_object_.push_back(texture_name_offset); + // D3D_SIT_TEXTURE. + shader_object_.push_back(2); + // D3D_RETURN_TYPE_FLOAT. + shader_object_.push_back(5); + switch (texture_srv.dimension) { + case TextureDimension::k3D: + // D3D_SRV_DIMENSION_TEXTURE3D. + shader_object_.push_back(8); + break; + case TextureDimension::kCube: + // D3D_SRV_DIMENSION_TEXTURECUBE. + shader_object_.push_back(9); + break; + default: + // D3D_SRV_DIMENSION_TEXTURE2DARRAY. + shader_object_.push_back(5); + } + // Not multisampled. + shader_object_.push_back(0xFFFFFFFFu); + // Register t[1 + i] - t0 is shared memory. + shader_object_.push_back(1 + i); + // One binding. + shader_object_.push_back(1); + // D3D_SIF_TEXTURE_COMPONENTS (4-component). + shader_object_.push_back(0xC); + // Register space 0. + shader_object_.push_back(0); + // SRV ID T[1 + i] - T0 is shared memory. + shader_object_.push_back(1 + i); + texture_name_offset += GetStringLength(texture_srv.name.c_str()); + } // Constant buffers. for (uint32_t i = 0; i < uint32_t(RdefConstantBufferIndex::kCount); ++i) { @@ -4409,6 +4602,7 @@ void DxbcShaderTranslator::WriteResourceDefinitions() { shader_object_.push_back(0); // D3D_SRV_DIMENSION_UNKNOWN (not an SRV). shader_object_.push_back(0); + // Multisampling not applicable. shader_object_.push_back(0); shader_object_.push_back(uint32_t(cbuffer.register_index)); shader_object_.push_back(cbuffer.binding_count); @@ -4416,6 +4610,7 @@ void DxbcShaderTranslator::WriteResourceDefinitions() { shader_object_.push_back(cbuffer.user_packed ? 1 : 0); // Register space 0. shader_object_.push_back(0); + // CBV ID CB[i]. shader_object_.push_back(i); } } @@ -4680,7 +4875,9 @@ void DxbcShaderTranslator::WriteShaderCode() { shader_object_.push_back(EncodeVectorSwizzledOperand( D3D10_SB_OPERAND_TYPE_CONSTANT_BUFFER, kSwizzleXYZW, 3)); shader_object_.push_back(cbuffer_index); + // Lower register bound in the space. shader_object_.push_back(uint32_t(cbuffer.register_index)); + // Upper register bound in the space. shader_object_.push_back(uint32_t(cbuffer.register_index) + cbuffer.binding_count - 1); shader_object_.push_back((cbuffer.size + 15) >> 4); @@ -4688,6 +4885,21 @@ void DxbcShaderTranslator::WriteShaderCode() { shader_object_.push_back(0); } + // Samplers. + for (uint32_t i = 0; i < uint32_t(sampler_bindings_.size()); ++i) { + const SamplerBinding& sampler_binding = sampler_bindings_[i]; + shader_object_.push_back( + ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_SAMPLER) | + ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(6) | + ENCODE_D3D10_SB_SAMPLER_MODE(D3D10_SB_SAMPLER_MODE_DEFAULT)); + shader_object_.push_back(EncodeVectorSwizzledOperand( + D3D10_SB_OPERAND_TYPE_SAMPLER, kSwizzleXYZW, 3)); + shader_object_.push_back(i); + shader_object_.push_back(i); + shader_object_.push_back(i); + shader_object_.push_back(0); + } + // Shader resources. // Shared memory ByteAddressBuffer (T0, at t0, space0). shader_object_.push_back( @@ -4700,6 +4912,39 @@ void DxbcShaderTranslator::WriteShaderCode() { shader_object_.push_back(0); shader_object_.push_back(0); + // Textures. + for (uint32_t i = 0; i < uint32_t(texture_srvs_.size()); ++i) { + const TextureSRV& texture_srv = texture_srvs_[i]; + D3D10_SB_RESOURCE_DIMENSION texture_srv_dimension; + switch (texture_srv.dimension) { + case TextureDimension::k3D: + texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURE3D; + break; + case TextureDimension::kCube: + texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURECUBE; + break; + default: + texture_srv_dimension = D3D10_SB_RESOURCE_DIMENSION_TEXTURE2DARRAY; + } + shader_object_.push_back( + ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_RESOURCE) | + ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(7) | + ENCODE_D3D10_SB_RESOURCE_DIMENSION(texture_srv_dimension)); + shader_object_.push_back(EncodeVectorSwizzledOperand( + D3D10_SB_OPERAND_TYPE_RESOURCE, kSwizzleXYZW, 3)); + // T0 is shared memory. + shader_object_.push_back(1 + i); + // t0 is shared memory. + shader_object_.push_back(1 + i); + shader_object_.push_back(1 + i); + shader_object_.push_back( + ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_FLOAT, 0) | + ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_FLOAT, 1) | + ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_FLOAT, 2) | + ENCODE_D3D10_SB_RESOURCE_RETURN_TYPE(D3D10_SB_RETURN_TYPE_FLOAT, 3)); + shader_object_.push_back(0); + } + // Inputs and outputs. if (is_vertex_shader()) { // Unswapped vertex index input (only X component). diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index 21c61eab4..9025c4b56 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -10,8 +10,11 @@ #ifndef XENIA_GPU_DXBC_SHADER_TRANSLATOR_H_ #define XENIA_GPU_DXBC_SHADER_TRANSLATOR_H_ +#include +#include #include +#include "xenia/base/math.h" #include "xenia/gpu/shader_translator.h" namespace xe { @@ -59,6 +62,30 @@ class DxbcShaderTranslator : public ShaderTranslator { uint32_t color_output_map[4]; }; + struct TextureSRV { + uint32_t fetch_constant; + TextureDimension dimension; + std::string name; + }; + // The first binding returned is at t1 because t0 is shared memory. + const TextureSRV* GetTextureSRVs(uint32_t& count_out) const { + count_out = uint32_t(texture_srvs_.size()); + return texture_srvs_.data(); + } + + struct SamplerBinding { + uint32_t fetch_constant; + TextureFilter mag_filter; + TextureFilter min_filter; + TextureFilter mip_filter; + AnisoFilter aniso_filter; + std::string name; + }; + const SamplerBinding* GetSamplerBindings(uint32_t& count_out) const { + count_out = uint32_t(sampler_bindings_.size()); + return sampler_bindings_.data(); + } + protected: void Reset() override; @@ -68,6 +95,8 @@ class DxbcShaderTranslator : public ShaderTranslator { void ProcessVertexFetchInstruction( const ParsedVertexFetchInstruction& instr) override; + void ProcessTextureFetchInstruction( + const ParsedTextureFetchInstruction& instr) override; void ProcessAluInstruction(const ParsedAluInstruction& instr) override; private: @@ -272,11 +301,20 @@ class DxbcShaderTranslator : public ShaderTranslator { // Emits copde for endian swapping of the data located in pv. void SwapVertexData(uint32_t vfetch_index, uint32_t write_mask); + // Returns T#/t# index (they are the same in this translator). + uint32_t FindOrAddTextureSRV(uint32_t fetch_constant, + TextureDimension dimension); + void ProcessVectorAluInstruction(const ParsedAluInstruction& instr); void ProcessScalarAluInstruction(const ParsedAluInstruction& instr); // Appends a string to a DWORD stream, returns the DWORD-aligned length. static uint32_t AppendString(std::vector& dest, const char* source); + // Returns the length of a string as if it was appended to a DWORD stream, in + // bytes. + static inline uint32_t GetStringLength(const char* source) { + return uint32_t(xe::align(std::strlen(source) + 1, sizeof(uint32_t))); + } void WriteResourceDefinitions(); void WriteInputSignature(); @@ -418,6 +456,9 @@ class DxbcShaderTranslator : public ShaderTranslator { // Loop counter stack, .x is the active loop. Represents number of times // remaining to loop. uint32_t system_temp_loop_count_; + // Explicitly set texture gradients and LOD. + uint32_t system_temp_grad_h_lod_; + uint32_t system_temp_grad_v_; // Position in vertex shaders (because viewport and W transformations can be // applied in the end of the shader). @@ -429,6 +470,9 @@ class DxbcShaderTranslator : public ShaderTranslator { bool writes_depth_; + std::vector texture_srvs_; + std::vector sampler_bindings_; + // The STAT chunk (based on Wine d3dcompiler_parse_stat). struct Statistics { uint32_t instruction_count;