From 149afc39301967700cf8df0acb45bc69910e0e9b Mon Sep 17 00:00:00 2001 From: John Kessenich Date: Tue, 14 Aug 2018 13:31:43 -0600 Subject: [PATCH] SPV: More corrections of versus "immediate" operands. --- SPIRV/GlslangToSpv.cpp | 175 +++++++++++++++++++++++++++++++------------------ SPIRV/SpvBuilder.cpp | 29 ++++++-- SPIRV/SpvBuilder.h | 3 +- SPIRV/spvIR.h | 9 ++- 4 files changed, 147 insertions(+), 69 deletions(-) diff --git a/SPIRV/GlslangToSpv.cpp b/SPIRV/GlslangToSpv.cpp index 81f7f08..d8369d2 100755 --- a/SPIRV/GlslangToSpv.cpp +++ b/SPIRV/GlslangToSpv.cpp @@ -3630,9 +3630,10 @@ spv::Id TGlslangToSpvTraverser::createImageTextureFunctionCall(glslang::TIntermO // Check for image functions other than queries if (node->isImage()) { - std::vector operands; + std::vector operands; auto opIt = arguments.begin(); - operands.push_back(*(opIt++)); + spv::IdImmediate image = { true, *(opIt++) }; + operands.push_back(image); // Handle subpass operations // TODO: GLSL should change to have the "MS" only on the type rather than the @@ -3643,38 +3644,47 @@ spv::Id TGlslangToSpvTraverser::createImageTextureFunctionCall(glslang::TIntermO std::vector comps; comps.push_back(zero); comps.push_back(zero); - operands.push_back(builder.makeCompositeConstant(builder.makeVectorType(builder.makeIntType(32), 2), comps)); + spv::IdImmediate coord = { true, + builder.makeCompositeConstant(builder.makeVectorType(builder.makeIntType(32), 2), comps) }; + operands.push_back(coord); if (sampler.ms) { - operands.push_back(spv::ImageOperandsSampleMask); - operands.push_back(*(opIt++)); + spv::IdImmediate imageOperands = { false, spv::ImageOperandsSampleMask }; + operands.push_back(imageOperands); + spv::IdImmediate imageOperand = { true, *(opIt++) }; + operands.push_back(imageOperand); } spv::Id result = builder.createOp(spv::OpImageRead, resultType(), operands); builder.setPrecision(result, precision); return result; } - operands.push_back(*(opIt++)); + spv::IdImmediate coord = { true, *(opIt++) }; + operands.push_back(coord); #ifdef AMD_EXTENSIONS if (node->getOp() == glslang::EOpImageLoad || node->getOp() == glslang::EOpImageLoadLod) { #else if (node->getOp() == glslang::EOpImageLoad) { #endif if (sampler.ms) { - operands.push_back(spv::ImageOperandsSampleMask); - operands.push_back(*opIt); + spv::IdImmediate imageOperands = { false, spv::ImageOperandsSampleMask }; + operands.push_back(imageOperands); + spv::IdImmediate imageOperand = { true, *opIt }; + operands.push_back(imageOperand); #ifdef AMD_EXTENSIONS } else if (cracked.lod) { builder.addExtension(spv::E_SPV_AMD_shader_image_load_store_lod); builder.addCapability(spv::CapabilityImageReadWriteLodAMD); - operands.push_back(spv::ImageOperandsLodMask); - operands.push_back(*opIt); + spv::IdImmediate imageOperands = { false, spv::ImageOperandsLodMask }; + operands.push_back(imageOperands); + spv::IdImmediate imageOperand = { true, *opIt }; + operands.push_back(imageOperand); #endif } - if (builder.getImageTypeFormat(builder.getImageType(operands.front())) == spv::ImageFormatUnknown) + if (builder.getImageTypeFormat(builder.getImageType(operands.front().word)) == spv::ImageFormatUnknown) builder.addCapability(spv::CapabilityStorageImageReadWithoutFormat); - std::vector result( 1, builder.createOp(spv::OpImageRead, resultType(), operands) ); + std::vector result(1, builder.createOp(spv::OpImageRead, resultType(), operands)); builder.setPrecision(result[0], precision); // If needed, add a conversion constructor to the proper size. @@ -3688,22 +3698,30 @@ spv::Id TGlslangToSpvTraverser::createImageTextureFunctionCall(glslang::TIntermO } else if (node->getOp() == glslang::EOpImageStore) { #endif if (sampler.ms) { - operands.push_back(*(opIt + 1)); - operands.push_back(spv::ImageOperandsSampleMask); - operands.push_back(*opIt); + spv::IdImmediate texel = { true, *(opIt + 1) }; + operands.push_back(texel); + spv::IdImmediate imageOperands = { false, spv::ImageOperandsSampleMask }; + operands.push_back(imageOperands); + spv::IdImmediate imageOperand = { true, *opIt }; + operands.push_back(imageOperand); #ifdef AMD_EXTENSIONS } else if (cracked.lod) { builder.addExtension(spv::E_SPV_AMD_shader_image_load_store_lod); builder.addCapability(spv::CapabilityImageReadWriteLodAMD); - operands.push_back(*(opIt + 1)); - operands.push_back(spv::ImageOperandsLodMask); - operands.push_back(*opIt); + spv::IdImmediate texel = { true, *(opIt + 1) }; + operands.push_back(texel); + spv::IdImmediate imageOperands = { false, spv::ImageOperandsLodMask }; + operands.push_back(imageOperands); + spv::IdImmediate imageOperand = { true, *opIt }; + operands.push_back(imageOperand); #endif - } else - operands.push_back(*opIt); + } else { + spv::IdImmediate texel = { true, *opIt }; + operands.push_back(texel); + } builder.createNoResultOp(spv::OpImageWrite, operands); - if (builder.getImageTypeFormat(builder.getImageType(operands.front())) == spv::ImageFormatUnknown) + if (builder.getImageTypeFormat(builder.getImageType(operands.front().word)) == spv::ImageFormatUnknown) builder.addCapability(spv::CapabilityStorageImageWriteWithoutFormat); return spv::NoResult; #ifdef AMD_EXTENSIONS @@ -3712,19 +3730,23 @@ spv::Id TGlslangToSpvTraverser::createImageTextureFunctionCall(glslang::TIntermO } else if (node->getOp() == glslang::EOpSparseImageLoad) { #endif builder.addCapability(spv::CapabilitySparseResidency); - if (builder.getImageTypeFormat(builder.getImageType(operands.front())) == spv::ImageFormatUnknown) + if (builder.getImageTypeFormat(builder.getImageType(operands.front().word)) == spv::ImageFormatUnknown) builder.addCapability(spv::CapabilityStorageImageReadWithoutFormat); if (sampler.ms) { - operands.push_back(spv::ImageOperandsSampleMask); - operands.push_back(*opIt++); + spv::IdImmediate imageOperands = { false, spv::ImageOperandsSampleMask }; + operands.push_back(imageOperands); + spv::IdImmediate imageOperand = { true, *opIt++ }; + operands.push_back(imageOperand); #ifdef AMD_EXTENSIONS } else if (cracked.lod) { builder.addExtension(spv::E_SPV_AMD_shader_image_load_store_lod); builder.addCapability(spv::CapabilityImageReadWriteLodAMD); - operands.push_back(spv::ImageOperandsLodMask); - operands.push_back(*opIt++); + spv::IdImmediate imageOperands = { false, spv::ImageOperandsLodMask }; + operands.push_back(imageOperands); + spv::IdImmediate imageOperand = { true, *opIt++ }; + operands.push_back(imageOperand); #endif } @@ -3744,7 +3766,9 @@ spv::Id TGlslangToSpvTraverser::createImageTextureFunctionCall(glslang::TIntermO // GLSL "IMAGE_PARAMS" will involve in constructing an image texel pointer and this pointer, // as the first source operand, is required by SPIR-V atomic operations. - operands.push_back(sampler.ms ? *(opIt++) : builder.makeUintConstant(0)); // For non-MS, the value should be 0 + // For non-MS, the sample value should be 0 + spv::IdImmediate sample = { true, sampler.ms ? *(opIt++) : builder.makeUintConstant(0) }; + operands.push_back(sample); spv::Id resultTypeId = builder.makePointer(spv::StorageClassImage, resultType()); spv::Id pointer = builder.createOp(spv::OpImageTexelPointer, resultTypeId, operands); @@ -5383,7 +5407,7 @@ spv::Id TGlslangToSpvTraverser::createInvocationsOperation(glslang::TOperator op #endif spv::Op opCode = spv::OpNop; - std::vector spvGroupOperands; + std::vector spvGroupOperands; spv::GroupOperation groupOperation = spv::GroupOperationMax; if (op == glslang::EOpBallot || op == glslang::EOpReadFirstInvocation || @@ -5410,7 +5434,6 @@ spv::Id TGlslangToSpvTraverser::createInvocationsOperation(glslang::TOperator op builder.addExtension(spv::E_SPV_AMD_shader_ballot); #endif - spvGroupOperands.push_back(builder.makeUintConstant(spv::ScopeSubgroup)); #ifdef AMD_EXTENSIONS switch (op) { case glslang::EOpMinInvocations: @@ -5420,7 +5443,6 @@ spv::Id TGlslangToSpvTraverser::createInvocationsOperation(glslang::TOperator op case glslang::EOpMaxInvocationsNonUniform: case glslang::EOpAddInvocationsNonUniform: groupOperation = spv::GroupOperationReduce; - spvGroupOperands.push_back(groupOperation); break; case glslang::EOpMinInvocationsInclusiveScan: case glslang::EOpMaxInvocationsInclusiveScan: @@ -5429,7 +5451,6 @@ spv::Id TGlslangToSpvTraverser::createInvocationsOperation(glslang::TOperator op case glslang::EOpMaxInvocationsInclusiveScanNonUniform: case glslang::EOpAddInvocationsInclusiveScanNonUniform: groupOperation = spv::GroupOperationInclusiveScan; - spvGroupOperands.push_back(groupOperation); break; case glslang::EOpMinInvocationsExclusiveScan: case glslang::EOpMaxInvocationsExclusiveScan: @@ -5438,16 +5459,23 @@ spv::Id TGlslangToSpvTraverser::createInvocationsOperation(glslang::TOperator op case glslang::EOpMaxInvocationsExclusiveScanNonUniform: case glslang::EOpAddInvocationsExclusiveScanNonUniform: groupOperation = spv::GroupOperationExclusiveScan; - spvGroupOperands.push_back(groupOperation); break; default: break; } + spv::IdImmediate scope = { true, builder.makeUintConstant(spv::ScopeSubgroup) }; + spvGroupOperands.push_back(scope); + if (groupOperation != spv::GroupOperationMax) { + spv::IdImmediate groupOp = { false, groupOperation }; + spvGroupOperands.push_back(groupOp); + } #endif } - for (auto opIt = operands.begin(); opIt != operands.end(); ++opIt) - spvGroupOperands.push_back(*opIt); + for (auto opIt = operands.begin(); opIt != operands.end(); ++opIt) { + spv::IdImmediate op = { true, *opIt }; + spvGroupOperands.push_back(op); + } switch (op) { case glslang::EOpAnyInvocation: @@ -5586,7 +5614,8 @@ spv::Id TGlslangToSpvTraverser::createInvocationsOperation(glslang::TOperator op } // Create group invocation operations on a vector -spv::Id TGlslangToSpvTraverser::CreateInvocationsVectorOperation(spv::Op op, spv::GroupOperation groupOperation, spv::Id typeId, std::vector& operands) +spv::Id TGlslangToSpvTraverser::CreateInvocationsVectorOperation(spv::Op op, spv::GroupOperation groupOperation, + spv::Id typeId, std::vector& operands) { #ifdef AMD_EXTENSIONS assert(op == spv::OpGroupFMin || op == spv::OpGroupUMin || op == spv::OpGroupSMin || @@ -5619,18 +5648,23 @@ spv::Id TGlslangToSpvTraverser::CreateInvocationsVectorOperation(spv::Op op, spv for (int comp = 0; comp < numComponents; ++comp) { std::vector indexes; indexes.push_back(comp); - spv::Id scalar = builder.createCompositeExtract(operands[0], scalarType, indexes); - std::vector spvGroupOperands; + spv::IdImmediate scalar = { true, builder.createCompositeExtract(operands[0], scalarType, indexes) }; + std::vector spvGroupOperands; if (op == spv::OpSubgroupReadInvocationKHR) { spvGroupOperands.push_back(scalar); - spvGroupOperands.push_back(operands[1]); + spv::IdImmediate operand = { true, operands[1] }; + spvGroupOperands.push_back(operand); } else if (op == spv::OpGroupBroadcast) { - spvGroupOperands.push_back(builder.makeUintConstant(spv::ScopeSubgroup)); + spv::IdImmediate scope = { true, builder.makeUintConstant(spv::ScopeSubgroup) }; + spvGroupOperands.push_back(scope); spvGroupOperands.push_back(scalar); - spvGroupOperands.push_back(operands[1]); + spv::IdImmediate operand = { true, operands[1] }; + spvGroupOperands.push_back(operand); } else { - spvGroupOperands.push_back(builder.makeUintConstant(spv::ScopeSubgroup)); - spvGroupOperands.push_back(groupOperation); + spv::IdImmediate scope = { true, builder.makeUintConstant(spv::ScopeSubgroup) }; + spvGroupOperands.push_back(scope); + spv::IdImmediate groupOp = { false, groupOperation }; + spvGroupOperands.push_back(groupOp); spvGroupOperands.push_back(scalar); } @@ -5642,7 +5676,8 @@ spv::Id TGlslangToSpvTraverser::CreateInvocationsVectorOperation(spv::Op op, spv } // Create subgroup invocation operations. -spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, spv::Id typeId, std::vector& operands, glslang::TBasicType typeProxy) +spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, spv::Id typeId, + std::vector& operands, glslang::TBasicType typeProxy) { // Add the required capabilities. switch (op) { @@ -5890,14 +5925,11 @@ spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, s default: assert(0 && "Unhandled subgroup operation!"); } - std::vector spvGroupOperands; - - // Every operation begins with the Execution Scope operand. - spvGroupOperands.push_back(builder.makeUintConstant(spv::ScopeSubgroup)); - - // Next, for all operations that use a Group Operation, push that as an operand. + // get the right Group Operation + spv::GroupOperation groupOperation = spv::GroupOperationMax; switch (op) { - default: break; + default: + break; case glslang::EOpSubgroupBallotBitCount: case glslang::EOpSubgroupAdd: case glslang::EOpSubgroupMul: @@ -5906,7 +5938,7 @@ spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, s case glslang::EOpSubgroupAnd: case glslang::EOpSubgroupOr: case glslang::EOpSubgroupXor: - spvGroupOperands.push_back(spv::GroupOperationReduce); + groupOperation = spv::GroupOperationReduce; break; case glslang::EOpSubgroupBallotInclusiveBitCount: case glslang::EOpSubgroupInclusiveAdd: @@ -5916,7 +5948,7 @@ spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, s case glslang::EOpSubgroupInclusiveAnd: case glslang::EOpSubgroupInclusiveOr: case glslang::EOpSubgroupInclusiveXor: - spvGroupOperands.push_back(spv::GroupOperationInclusiveScan); + groupOperation = spv::GroupOperationInclusiveScan; break; case glslang::EOpSubgroupBallotExclusiveBitCount: case glslang::EOpSubgroupExclusiveAdd: @@ -5926,7 +5958,7 @@ spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, s case glslang::EOpSubgroupExclusiveAnd: case glslang::EOpSubgroupExclusiveOr: case glslang::EOpSubgroupExclusiveXor: - spvGroupOperands.push_back(spv::GroupOperationExclusiveScan); + groupOperation = spv::GroupOperationExclusiveScan; break; case glslang::EOpSubgroupClusteredAdd: case glslang::EOpSubgroupClusteredMul: @@ -5935,7 +5967,7 @@ spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, s case glslang::EOpSubgroupClusteredAnd: case glslang::EOpSubgroupClusteredOr: case glslang::EOpSubgroupClusteredXor: - spvGroupOperands.push_back(spv::GroupOperationClusteredReduce); + groupOperation = spv::GroupOperationClusteredReduce; break; #ifdef NV_EXTENSIONS case glslang::EOpSubgroupPartitionedAdd: @@ -5945,7 +5977,7 @@ spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, s case glslang::EOpSubgroupPartitionedAnd: case glslang::EOpSubgroupPartitionedOr: case glslang::EOpSubgroupPartitionedXor: - spvGroupOperands.push_back(spv::GroupOperationPartitionedReduceNV); + groupOperation = spv::GroupOperationPartitionedReduceNV; break; case glslang::EOpSubgroupPartitionedInclusiveAdd: case glslang::EOpSubgroupPartitionedInclusiveMul: @@ -5954,7 +5986,7 @@ spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, s case glslang::EOpSubgroupPartitionedInclusiveAnd: case glslang::EOpSubgroupPartitionedInclusiveOr: case glslang::EOpSubgroupPartitionedInclusiveXor: - spvGroupOperands.push_back(spv::GroupOperationPartitionedInclusiveScanNV); + groupOperation = spv::GroupOperationPartitionedInclusiveScanNV; break; case glslang::EOpSubgroupPartitionedExclusiveAdd: case glslang::EOpSubgroupPartitionedExclusiveMul: @@ -5963,22 +5995,41 @@ spv::Id TGlslangToSpvTraverser::createSubgroupOperation(glslang::TOperator op, s case glslang::EOpSubgroupPartitionedExclusiveAnd: case glslang::EOpSubgroupPartitionedExclusiveOr: case glslang::EOpSubgroupPartitionedExclusiveXor: - spvGroupOperands.push_back(spv::GroupOperationPartitionedExclusiveScanNV); + groupOperation = spv::GroupOperationPartitionedExclusiveScanNV; break; #endif } + // build the instruction + std::vector spvGroupOperands; + + // Every operation begins with the Execution Scope operand. + spv::IdImmediate executionScope = { true, builder.makeUintConstant(spv::ScopeSubgroup) }; + spvGroupOperands.push_back(executionScope); + + // Next, for all operations that use a Group Operation, push that as an operand. + if (groupOperation != spv::GroupOperationMax) { + spv::IdImmediate groupOperand = { false, groupOperation }; + spvGroupOperands.push_back(groupOperand); + } + // Push back the operands next. - for (auto opIt : operands) { - spvGroupOperands.push_back(opIt); + for (auto opIt = operands.cbegin(); opIt != operands.cend(); ++opIt) { + spv::IdImmediate operand = { true, *opIt }; + spvGroupOperands.push_back(operand); } // Some opcodes have additional operands. + spv::Id directionId = spv::NoResult; switch (op) { default: break; - case glslang::EOpSubgroupQuadSwapHorizontal: spvGroupOperands.push_back(builder.makeUintConstant(0)); break; - case glslang::EOpSubgroupQuadSwapVertical: spvGroupOperands.push_back(builder.makeUintConstant(1)); break; - case glslang::EOpSubgroupQuadSwapDiagonal: spvGroupOperands.push_back(builder.makeUintConstant(2)); break; + case glslang::EOpSubgroupQuadSwapHorizontal: directionId = builder.makeUintConstant(0); break; + case glslang::EOpSubgroupQuadSwapVertical: directionId = builder.makeUintConstant(1); break; + case glslang::EOpSubgroupQuadSwapDiagonal: directionId = builder.makeUintConstant(2); break; + } + if (directionId != spv::NoResult) { + spv::IdImmediate direction = { true, directionId }; + spvGroupOperands.push_back(direction); } return builder.createOp(opCode, typeId, spvGroupOperands); diff --git a/SPIRV/SpvBuilder.cpp b/SPIRV/SpvBuilder.cpp index c20641c..5a7bb5c 100755 --- a/SPIRV/SpvBuilder.cpp +++ b/SPIRV/SpvBuilder.cpp @@ -81,6 +81,7 @@ Id Builder::import(const char* name) { Instruction* import = new Instruction(getUniqueId(), NoType, OpExtInstImport); import->addStringOperand(name); + module.mapInstruction(import); imports.push_back(std::unique_ptr(import)); return import->getResultId(); @@ -1331,7 +1332,7 @@ void Builder::createNoResultOp(Op opCode) buildPoint->addInstruction(std::unique_ptr(op)); } -// An opcode that has one operand, no result id, and no type +// An opcode that has one id operand, no result id, and no type void Builder::createNoResultOp(Op opCode, Id operand) { Instruction* op = new Instruction(opCode); @@ -1339,12 +1340,16 @@ void Builder::createNoResultOp(Op opCode, Id operand) buildPoint->addInstruction(std::unique_ptr(op)); } -// An opcode that has one operand, no result id, and no type -void Builder::createNoResultOp(Op opCode, const std::vector& operands) +// An opcode that has multiple operands, no result id, and no type +void Builder::createNoResultOp(Op opCode, const std::vector& operands) { Instruction* op = new Instruction(opCode); - for (auto it = operands.cbegin(); it != operands.cend(); ++it) - op->addIdOperand(*it); + for (auto it = operands.cbegin(); it != operands.cend(); ++it) { + if (it->isId) + op->addIdOperand(it->word); + else + op->addImmediateOperand(it->word); + } buildPoint->addInstruction(std::unique_ptr(op)); } @@ -1428,6 +1433,20 @@ Id Builder::createOp(Op opCode, Id typeId, const std::vector& operands) return op->getResultId(); } +Id Builder::createOp(Op opCode, Id typeId, const std::vector& operands) +{ + Instruction* op = new Instruction(getUniqueId(), typeId, opCode); + for (auto it = operands.cbegin(); it != operands.cend(); ++it) { + if (it->isId) + op->addIdOperand(it->word); + else + op->addImmediateOperand(it->word); + } + buildPoint->addInstruction(std::unique_ptr(op)); + + return op->getResultId(); +} + Id Builder::createSpecConstantOp(Op opCode, Id typeId, const std::vector& operands, const std::vector& literals) { Instruction* op = new Instruction(getUniqueId(), typeId, OpSpecConstantOp); diff --git a/SPIRV/SpvBuilder.h b/SPIRV/SpvBuilder.h index 8894f01..01698b3 100755 --- a/SPIRV/SpvBuilder.h +++ b/SPIRV/SpvBuilder.h @@ -295,13 +295,14 @@ public: void createNoResultOp(Op); void createNoResultOp(Op, Id operand); - void createNoResultOp(Op, const std::vector& operands); + void createNoResultOp(Op, const std::vector& operands); void createControlBarrier(Scope execution, Scope memory, MemorySemanticsMask); void createMemoryBarrier(unsigned executionScope, unsigned memorySemantics); Id createUnaryOp(Op, Id typeId, Id operand); Id createBinOp(Op, Id typeId, Id operand1, Id operand2); Id createTriOp(Op, Id typeId, Id operand1, Id operand2, Id operand3); Id createOp(Op, Id typeId, const std::vector& operands); + Id createOp(Op, Id typeId, const std::vector& operands); Id createFunctionCall(spv::Function*, const std::vector&); Id createSpecConstantOp(Op, Id typeId, const std::vector& operands, const std::vector& literals); diff --git a/SPIRV/spvIR.h b/SPIRV/spvIR.h index c126b4f..14d997d 100755 --- a/SPIRV/spvIR.h +++ b/SPIRV/spvIR.h @@ -79,6 +79,11 @@ const MemorySemanticsMask MemorySemanticsAllMemory = MemorySemanticsAtomicCounterMemoryMask | MemorySemanticsImageMemoryMask); +struct IdImmediate { + bool isId; // true if word is an Id, false if word is an immediate + unsigned word; +}; + // // SPIR-V IR instruction. // @@ -349,7 +354,9 @@ public: Instruction* getInstruction(Id id) const { return idToInstruction[id]; } const std::vector& getFunctions() const { return functions; } - spv::Id getTypeId(Id resultId) const { return idToInstruction[resultId]->getTypeId(); } + spv::Id getTypeId(Id resultId) const { + return idToInstruction[resultId] == nullptr ? NoType : idToInstruction[resultId]->getTypeId(); + } StorageClass getStorageClass(Id typeId) const { assert(idToInstruction[typeId]->getOpCode() == spv::OpTypePointer); -- 2.7.4