diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-08-08 17:23:03 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-08-08 17:23:03 -0400 |
| commit | 41247c3942210df33b9e3dd733eafb23573a4f2f (patch) | |
| tree | a49a88e680078bd379fe183cf826d49d7f935737 /source/slang/slang-emit-cpp.cpp | |
| parent | c1cc93dd962a6db6c839341f11d2654cf0e62e37 (diff) | |
WIP: Preliminary Slang -> C++ code generation (#1009)
* Expanded prelude for some other resource types. Disable C++ output for ParameterGroup.
* WIP: Layout for CPU.
* Fixes to CPU layout.
* WIP: The uniform is output, but the variable definition is not.
* WIP: Entry point parameters to global scope in C++.
Handling of resource types (in so far as outputting)
* Some discussion of ABI and different input types.
* WIP: More C++ support around resource types.
* WIP: Split up variables into different structures on emit.
* WIP: Emitting C++ with wrapping up of 'Context'
* WIP: C++ code has access to semantic values.
Wrap in struct so can use method calls to pass shared state.
Disable legalizeResourceTypes and legalizeExistentialTypeLayout
* Fix structured buffer layout for CPU.
* Remove testing/handling of global uniforms on CPU path.
Typo fix.
Changed CPU tests to use new CPU calling convention.
* Check globals are working. Initalize context to zero globals.
* Order the global parameters for C++ ouput by their layout.
Note - that layout isn't quite working correctly because the StructuredBuffer<int> the int seems to be consuming uniform space.
* Work around for reflection not having all data needed for layout ordering for C++ code.
* Output constant buffers as pointers.
* Entry point parameters accessed through pointer to struct.
* WIP: Layout for CPU is reasonable for test case.
* Only output 'f' after float literal if type marks as a float.
* Cast construction works on C++.
* Made IntrinsicOp::ConvertConstruct to make intent clearer.
* C++ handling construction from scalar.
Handle access of a scalar with .x.
Check default initialization.
* Comment about need for split of kIROp_construct.
Release build works.
* Added support from constructVectorFromScalar to C/C++ target.
* Handling of in/out in C/C++.
* First pass documentation CPU support.
* Improvements to C++/C slang code generation documentation.
* Small doc change to include need for mechansim to specify cpp compiler path.
* Better handling of swizzling - allow swizzling a scalar into a vector.
Diffstat (limited to 'source/slang/slang-emit-cpp.cpp')
| -rw-r--r-- | source/slang/slang-emit-cpp.cpp | 1014 |
1 files changed, 932 insertions, 82 deletions
diff --git a/source/slang/slang-emit-cpp.cpp b/source/slang/slang-emit-cpp.cpp index 2ec087eef..0228955dc 100644 --- a/source/slang/slang-emit-cpp.cpp +++ b/source/slang/slang-emit-cpp.cpp @@ -10,6 +10,69 @@ #include <assert.h> +/* +ABI +--- + +In terms of ABI we need to discuss the variety of variables/resources that need to be defined by the host for appropriate execution +of the output code. + +https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-variable-syntax + +Broadly we could categorize these as.. + +1) Varying entry point parameters (or 'varying') +2) Uniform entry point parameters +3) Uniform globals +4) Thread shared (such as group shared) or ('thread shared') +5) Thread local ('static') + +If we can invoke a bunch of threads as a single invocation we could effectively have the ThreadShared not part of the ABI, but something +that is say allocated on the stack before the threads are kicked off. If we kick of threads individually then we would need to pass this +in as part of ABI. NOTE that it isn't right in so far as memory barriers etc couldn't work, as each thread would run to completion, but +we aren't going to worry about barriers for now. + +On 1 - there could be potentially input and outputs (perhaps in out?). On CPU I guess that's fine. + +On 2 and 3 they are effectively the same, and so for now 2+3 will be referred to together as 'uniforms'. +They should be copied into a single structure that has a well known order. + +On 1 these are parameters that vary on an invocation. Thus a caller might call many times with same globals structure +and different varying entry point parameters. + +On 5 - This would be a global that can be set and then accessed within the context of single thread + +So in order of rate of change + +1 : Probably change on every invocation (in the future such an invocation might be behind the API) +2 + 3 : Changes per group of 'threads' executed together +4 : Does not change between invocations +5 : Could be placed on the stack, and so not necessarily part of the ABI + +For now we are only going to implement something 'Compute shader'-like. Doing so makes the varying parameter always the same. + +So for now we would need to pass in + +ComputeVaryingInput - Fixed because we are doing compute shader +Uniform - All the uniform data in a big blob, both from uniform entry point parameters, and uniform globals + +When called we can have a structure that holds the thread local variables, and these two pointers. + + +We can stick pointers to these in a structure lets call it 'Context'. On C++ we could make all the functions 'methods', and then +we don't need to pass around the context as a parameter. For C this doesn't work, so it might be worth just biting the bullet and +just adding the context to the output. + +Issues: + +* How does this work with layout? The layout if it's going to specify offsets will need to know that they will be allocated into each +of these structs AND that the order they are placed needs to be consistent. + +* When variables access one of these sources, we will now need code that will add the dereferencing. Hopefully this can be done by looking +at the type of the variable, and then adding the appropriate access via part of emit. + +*/ + namespace Slang { static const char s_elemNames[] = "xyzw"; @@ -345,7 +408,23 @@ UnownedStringSlice CPPSourceEmitter::_getTypeName(IRType* inType) return m_slicePool.getSlice(handle); } - handle = _calcTypeName(type); + if (type->op == kIROp_MatrixType) + { + auto matType = static_cast<IRMatrixType*>(type); + + auto elementType = matType->getElementType(); + const auto rowCount = int(GetIntVal(matType->getRowCount())); + const auto colCount = int(GetIntVal(matType->getColumnCount())); + + // Make sure the vector type the matrix is built on is added + useType(_getVecType(elementType, colCount)); + } + + StringBuilder builder; + if (SLANG_SUCCEEDED(_calcTypeName(type, m_target, builder))) + { + handle = m_slicePool.add(builder); + } m_typeNameMap.Add(type, handle); @@ -353,14 +432,63 @@ UnownedStringSlice CPPSourceEmitter::_getTypeName(IRType* inType) return m_slicePool.getSlice(handle); } -StringSlicePool::Handle CPPSourceEmitter::_calcTypeName(IRType* type) +SlangResult CPPSourceEmitter::_calcTextureTypeName(IRTextureTypeBase* texType, StringBuilder& outName) +{ + switch (texType->getAccess()) + { + case SLANG_RESOURCE_ACCESS_READ: + break; + case SLANG_RESOURCE_ACCESS_READ_WRITE: + outName << "RW"; + break; + case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: + outName << "RasterizerOrdered"; + break; + case SLANG_RESOURCE_ACCESS_APPEND: + outName << "Append"; + break; + case SLANG_RESOURCE_ACCESS_CONSUME: + outName << "Consume"; + break; + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource access mode"); + return SLANG_FAIL; + } + + switch (texType->GetBaseShape()) + { + case TextureFlavor::Shape::Shape1D: outName << "Texture1D"; break; + case TextureFlavor::Shape::Shape2D: outName << "Texture2D"; break; + case TextureFlavor::Shape::Shape3D: outName << "Texture3D"; break; + case TextureFlavor::Shape::ShapeCube: outName << "TextureCube"; break; + case TextureFlavor::Shape::ShapeBuffer: outName << "Buffer"; break; + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape"); + return SLANG_FAIL; + } + + if (texType->isMultisample()) + { + outName << "MS"; + } + if (texType->isArray()) + { + outName << "Array"; + } + outName << "<" << _getTypeName(texType->getElementType()) << " >"; + + return SLANG_OK; +} + +SlangResult CPPSourceEmitter::_calcTypeName(IRType* type, CodeGenTarget target, StringBuilder& out) { switch (type->op) { case kIROp_HalfType: { // Special case half - return m_slicePool.add(getBuiltinTypeName(kIROp_FloatType)); + out << getBuiltinTypeName(kIROp_FloatType); + return SLANG_OK; } case kIROp_VectorType: { @@ -368,26 +496,23 @@ StringSlicePool::Handle CPPSourceEmitter::_calcTypeName(IRType* type) auto vecCount = int(GetIntVal(vecType->getElementCount())); const IROp elemType = vecType->getElementType()->op; - if (m_target == CodeGenTarget::CPPSource) + if (target == CodeGenTarget::CPPSource) { - StringBuilder builder; - builder << "Vector<" << getBuiltinTypeName(elemType) << ", " << vecCount << ">"; - return m_slicePool.add(builder); + out << "Vector<" << getBuiltinTypeName(elemType) << ", " << vecCount << ">"; } else { - StringBuilder builder; - builder << "Vec"; + out << "Vec"; UnownedStringSlice postFix = _getCTypeVecPostFix(elemType); - builder << postFix; + out << postFix; if (postFix.size() > 1) { - builder << "_"; + out << "_"; } - builder << vecCount; - return m_slicePool.add(builder); + out << vecCount; } + return SLANG_OK; } case kIROp_MatrixType: { @@ -397,42 +522,32 @@ StringSlicePool::Handle CPPSourceEmitter::_calcTypeName(IRType* type) const auto rowCount = int(GetIntVal(matType->getRowCount())); const auto colCount = int(GetIntVal(matType->getColumnCount())); - if (m_target == CodeGenTarget::CPPSource) + if (target == CodeGenTarget::CPPSource) { - StringBuilder builder; - builder << "Matrix<" << getBuiltinTypeName(elementType->op) << ", " << rowCount << ", " << colCount << ">"; - return m_slicePool.add(builder); + out << "Matrix<" << getBuiltinTypeName(elementType->op) << ", " << rowCount << ", " << colCount << ">"; } else { - // Make sure there is the vector name too - _getTypeName(_getVecType(elementType, colCount)); - - StringBuilder builder; - - builder << "Mat"; + out << "Mat"; const UnownedStringSlice postFix = _getCTypeVecPostFix(_getCType(elementType->op)); - builder << postFix; + out << postFix; if (postFix.size() > 1) { - builder << "_"; + out << "_"; } - builder << rowCount; - builder << colCount; - - return m_slicePool.add(builder); + out << rowCount; + out << colCount; } + return SLANG_OK; } case kIROp_HLSLRWStructuredBufferType: { auto bufType = static_cast<IRHLSLRWStructuredBufferType*>(type); - StringBuilder builder; - builder << "RWStructuredBuffer<"; - builder << _getTypeName(bufType->getElementType()); - builder << ">"; - - return m_slicePool.add(builder); + out << "RWStructuredBuffer<"; + SLANG_RETURN_ON_FAIL(_calcTypeName(bufType->getElementType(), target, out)); + out << ">"; + return SLANG_OK; } case kIROp_ArrayType: { @@ -440,24 +555,44 @@ StringSlicePool::Handle CPPSourceEmitter::_calcTypeName(IRType* type) auto elementType = arrayType->getElementType(); int elementCount = int(GetIntVal(arrayType->getElementCount())); - StringBuilder builder; - builder << "FixedArray<"; - builder << _getTypeName(elementType); - builder << ", " << elementCount << ">"; - - return m_slicePool.add(builder); + out << "FixedArray<"; + SLANG_RETURN_ON_FAIL(_calcTypeName(elementType, target, out)); + out << ", " << elementCount << ">"; + return SLANG_OK; + } + case kIROp_SamplerStateType: + { + out << "SamplerState"; + return SLANG_OK; + } + case kIROp_SamplerComparisonStateType: + { + out << "SamplerComparisonState"; + return SLANG_OK; } default: { if (IRBasicType::isaImpl(type->op)) { - return m_slicePool.add(getBuiltinTypeName(type->op)); + out << getBuiltinTypeName(type->op); + return SLANG_OK; } + + if (auto texType = as<IRTextureTypeBase>(type)) + { + // We don't support TextureSampler, so ignore that + if (texType->op != kIROp_TextureSamplerType) + { + return _calcTextureTypeName(texType, out); + } + } + break; } } - return StringSlicePool::kNullHandle; + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled type for C/C++ emit"); + return SLANG_FAIL; } void CPPSourceEmitter::useType(IRType* type) @@ -615,8 +750,12 @@ static IRBasicType* _getElementType(IRType* type) static bool _isOperator(const UnownedStringSlice& funcName) { - const char c = funcName[0]; - return !((c >= 'a' && c <='z') || (c >= 'A' && c <= 'Z') || c == '_'); + if (funcName.size() > 0) + { + const char c = funcName[0]; + return !((c >= 'a' && c <='z') || (c >= 'A' && c <= 'Z') || c == '_'); + } + return false; } void CPPSourceEmitter::_emitAryDefinition(const SpecializedIntrinsic& specOp) @@ -989,6 +1128,130 @@ void CPPSourceEmitter::_emitNormalizeDefinition(const UnownedStringSlice& funcNa writer->emit("}\n\n"); } +void CPPSourceEmitter::_emitConstructConvertDefinition(const UnownedStringSlice& funcName, const SpecializedIntrinsic& specOp) +{ + SourceWriter* writer = getSourceWriter(); + IRFuncType* funcType = specOp.signatureType; + + SLANG_ASSERT(funcType->getParamCount() == 2); + + IRType* srcType = funcType->getParamType(1); + IRType* retType = specOp.returnType; + + emitType(retType); + writer->emit(" "); + writer->emit(funcName); + writer->emit("("); + emitType(srcType); + writer->emitChar(' '); + writer->emitChar(char('a' + 0)); + writer->emit(")"); + + writer->emit("\n{\n"); + writer->indent(); + + writer->emit("return "); + emitType(retType); + writer->emit("{ "); + + IRType* dstElemType = _getElementType(retType); + //IRType* srcElemType = _getElementType(srcType); + + TypeDimension dim = _getTypeDimension(srcType, false); + + for (int i = 0; i < dim.rowCount; ++i) + { + if (dim.rowCount > 1) + { + if (i > 0) + { + writer->emit(", \n"); + } + writer->emit("{ "); + } + + for (int j = 0; j < dim.colCount; ++j) + { + if (j > 0) + { + writer->emit(", "); + } + + emitType(dstElemType); + writer->emit("("); + _emitAccess(UnownedStringSlice::fromLiteral("a"), dim, i, j, writer); + writer->emit(")"); + } + if (dim.rowCount > 1) + { + writer->emit("}"); + } + } + + writer->emit("};\n"); + + writer->dedent(); + writer->emit("}\n\n"); +} + +void CPPSourceEmitter::_emitConstructFromScalarDefinition(const UnownedStringSlice& funcName, const SpecializedIntrinsic& specOp) +{ + SourceWriter* writer = getSourceWriter(); + IRFuncType* funcType = specOp.signatureType; + + SLANG_ASSERT(funcType->getParamCount() == 2); + + IRType* srcType = funcType->getParamType(1); + IRType* retType = specOp.returnType; + + emitType(retType); + writer->emit(" "); + writer->emit(funcName); + writer->emit("("); + emitType(srcType); + writer->emitChar(' '); + writer->emitChar(char('a' + 0)); + writer->emit(")"); + + writer->emit("\n{\n"); + writer->indent(); + + writer->emit("return "); + emitType(retType); + writer->emit("{ "); + + const TypeDimension dim = _getTypeDimension(retType, false); + + for (int i = 0; i < dim.rowCount; ++i) + { + if (dim.rowCount > 1) + { + if (i > 0) + { + writer->emit(", \n"); + } + writer->emit("{ "); + } + for (int j = 0; j < dim.colCount; ++j) + { + if (j > 0) + { + writer->emit(", "); + } + writer->emit("a"); + } + if (dim.rowCount > 1) + { + writer->emit("}"); + } + } + + writer->emit("};\n"); + + writer->dedent(); + writer->emit("}\n\n"); +} + void CPPSourceEmitter::_emitReflectDefinition(const UnownedStringSlice& funcName, const SpecializedIntrinsic& specOp) { SourceWriter* writer = getSourceWriter(); @@ -1061,6 +1324,14 @@ void CPPSourceEmitter::emitSpecializedOperationDefinition(const SpecializedIntri { return _emitReflectDefinition(_getFuncName(specOp), specOp); } + case IntrinsicOp::ConstructConvert: + { + return _emitConstructConvertDefinition(_getFuncName(specOp), specOp); + } + case IntrinsicOp::ConstructFromScalar: + { + return _emitConstructFromScalarDefinition(_getFuncName(specOp), specOp); + } default: { const auto& info = getOperationInfo(specOp.op); @@ -1183,47 +1454,40 @@ void CPPSourceEmitter::emitCall(const SpecializedIntrinsic& specOp, IRInst* inst } case IntrinsicOp::Swizzle: { - // For C++ we don't need to emit a swizzle function - // For C we need a construction function + // Currently only works for C++ (we use {} constuction) - which means we don't need to generate a function. + // For C we need to generate suitable construction function auto swizzleInst = static_cast<IRSwizzle*>(inst); const Index elementCount = Index(swizzleInst->getElementCount()); - if (elementCount == 1) - { - defaultEmitInstExpr(inst, inOuterPrec); - } - else - { - // TODO(JS): Not sure this is correct on the parens handling front - IRType* retType = specOp.returnType; - emitType(retType); - writer->emit("{"); + // TODO(JS): Not 100% sure this is correct on the parens handling front + IRType* retType = specOp.returnType; + emitType(retType); + writer->emit("{"); - for (Index i = 0; i < elementCount; ++i) + for (Index i = 0; i < elementCount; ++i) + { + if (i > 0) { - if (i > 0) - { - writer->emit(", "); - } - - auto outerPrec = getInfo(EmitOp::General); + writer->emit(", "); + } - auto prec = getInfo(EmitOp::Postfix); - emitOperand(swizzleInst->getBase(), leftSide(outerPrec, prec)); + auto outerPrec = getInfo(EmitOp::General); - writer->emit("."); + auto prec = getInfo(EmitOp::Postfix); + emitOperand(swizzleInst->getBase(), leftSide(outerPrec, prec)); - IRInst* irElementIndex = swizzleInst->getElementIndex(i); - SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit); - IRConstant* irConst = (IRConstant*)irElementIndex; - UInt elementIndex = (UInt)irConst->value.intVal; - SLANG_RELEASE_ASSERT(elementIndex < 4); + writer->emit("."); - writer->emitChar(s_elemNames[elementIndex]); - } + IRInst* irElementIndex = swizzleInst->getElementIndex(i); + SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit); + IRConstant* irConst = (IRConstant*)irElementIndex; + UInt elementIndex = (UInt)irConst->value.intVal; + SLANG_RELEASE_ASSERT(elementIndex < 4); - writer->emit("}"); + writer->emitChar(s_elemNames[elementIndex]); } + + writer->emit("}"); break; } default: @@ -1302,6 +1566,46 @@ StringSlicePool::Handle CPPSourceEmitter::_calcFuncName(const SpecializedIntrins } else { + switch (specOp.op) + { + case IntrinsicOp::ConstructConvert: + { + // Work out the function name + IRFuncType* signatureType = specOp.signatureType; + SLANG_ASSERT(signatureType->getParamCount() == 2); + + IRType* dstType = signatureType->getParamType(0); + //IRType* srcType = signatureType->getParamType(1); + + StringBuilder builder; + builder << "convert_"; + // I need a function that is called that will construct this + if (SLANG_FAILED(_calcTypeName(dstType, CodeGenTarget::CSource, builder))) + { + return StringSlicePool::kNullHandle; + } + return m_slicePool.add(builder); + } + case IntrinsicOp::ConstructFromScalar: + { + // Work out the function name + IRFuncType* signatureType = specOp.signatureType; + SLANG_ASSERT(signatureType->getParamCount() == 2); + + IRType* dstType = signatureType->getParamType(0); + + StringBuilder builder; + builder << "constructFromScalar_"; + // I need a function that is called that will construct this + if (SLANG_FAILED(_calcTypeName(dstType, CodeGenTarget::CSource, builder))) + { + return StringSlicePool::kNullHandle; + } + return m_slicePool.add(builder); + } + default: break; + } + const auto& info = getOperationInfo(specOp.op); if (info.funcName.size()) { @@ -1316,6 +1620,38 @@ StringSlicePool::Handle CPPSourceEmitter::_calcFuncName(const SpecializedIntrins void CPPSourceEmitter::emitOperationCall(IntrinsicOp op, IRInst* inst, IRUse* operands, int operandCount, IRType* retType, const EmitOpInfo& inOuterPrec) { + switch (op) + { + case IntrinsicOp::ConstructFromScalar: + { + SLANG_ASSERT(operandCount == 1); + IRType* dstType = inst->getDataType(); + IRType* srcType = _getElementType(dstType); + IRType* argTypes[2] = { dstType, srcType }; + + SpecializedIntrinsic specOp = getSpecializedOperation(op, argTypes, 2, retType); + + emitCall(specOp, inst, operands, operandCount, inOuterPrec); + return; + } + case IntrinsicOp::ConstructConvert: + { + SLANG_ASSERT(inst->getOperandCount() == 1); + IRType* argTypes[2] = {inst->getDataType(), inst->getOperand(0)->getDataType() }; + + SpecializedIntrinsic specOp = getSpecializedOperation(op, argTypes, 2, retType); + + IRFuncType* signatureType = specOp.signatureType; + SLANG_UNUSED(signatureType); + + SLANG_ASSERT(signatureType->getParamType(0) != signatureType->getParamType(1)); + + emitCall(specOp, inst, operands, operandCount, inOuterPrec); + return; + } + default: break; + } + if (operandCount > 8) { List<IRType*> argTypes; @@ -1381,11 +1717,94 @@ CPPSourceEmitter::CPPSourceEmitter(const Desc& desc): } } -void CPPSourceEmitter::emitParameterGroupImpl(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) +void CPPSourceEmitter::_emitInOutParamType(IRType* type, String const& name, IRType* valueType) +{ + StringSliceLoc nameAndLoc(name.getUnownedSlice()); + + if (auto refType = as<IRRefType>(type)) + { + m_writer->emit("const "); + } + + UnownedStringSlice slice = _getTypeName(valueType); + m_writer->emit(slice); + m_writer->emit("& "); + m_writer->emitName(nameAndLoc); +} + +void CPPSourceEmitter::emitParamTypeImpl(IRType* type, String const& name) +{ + // An `out` or `inout` parameter will have been + // encoded as a parameter of pointer type, so + // we need to decode that here. + // + if (auto outType = as<IROutType>(type)) + { + return _emitInOutParamType(type, name, outType->getValueType()); + } + else if (auto inOutType = as<IRInOutType>(type)) + { + return _emitInOutParamType(type, name, inOutType->getValueType()); + } + else if (auto refType = as<IRRefType>(type)) + { + return _emitInOutParamType(type, name, refType->getValueType()); + } + + emitType(type, name); +} + +bool CPPSourceEmitter::tryEmitGlobalParamImpl(IRGlobalParam* varDecl, IRType* varType) { SLANG_UNUSED(varDecl); - SLANG_UNUSED(type); - SLANG_ASSERT(!"Not implemented"); + SLANG_UNUSED(varType); + + switch (varType->op) + { + case kIROp_StructType: + { + String name = getName(varDecl); + + UnownedStringSlice typeName = _getTypeName(varType); + m_writer->emit(typeName); + m_writer->emit("* "); + m_writer->emit(name); + m_writer->emit(";\n"); + return true; + } + } + + return false; +} + +void CPPSourceEmitter::emitParameterGroupImpl(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) +{ + // Output global parameters + auto varLayout = getVarLayout(varDecl); + SLANG_RELEASE_ASSERT(varLayout); + + String name = getName(varDecl); + auto elementType = type->getElementType(); + + switch (type->op) + { + case kIROp_ParameterBlockType: + case kIROp_ConstantBufferType: + { + UnownedStringSlice typeName = _getTypeName(elementType); + m_writer->emit(typeName); + m_writer->emit("* "); + m_writer->emit(name); + m_writer->emit(";\n"); + break; + } + default: + { + emitType(elementType, name); + m_writer->emit(";\n"); + break; + } + } } void CPPSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, EntryPointLayout* entryPointLayout) @@ -1426,6 +1845,87 @@ void CPPSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, EntryPointLa m_writer->emit("SLANG_PRELUDE_EXPORT\n"); } +void CPPSourceEmitter::emitSimpleFuncImpl(IRFunc* func) +{ + auto resultType = func->getResultType(); + + auto name = getFuncName(func); + + // Deal with decorations that need + // to be emitted as attributes + + // We are going to ignore the parameters passed and just pass in the Context + + auto entryPointLayout = asEntryPoint(func); + if (entryPointLayout) + { + StringBuilder prefixName; + prefixName << "_" << name; + emitType(resultType, prefixName); + m_writer->emit("()\n"); + } + else + { + emitType(resultType, name); + + m_writer->emit("("); + auto firstParam = func->getFirstParam(); + for (auto pp = firstParam; pp; pp = pp->getNextParam()) + { + if (pp != firstParam) + m_writer->emit(", "); + + emitSimpleFuncParamImpl(pp); + } + m_writer->emit(")"); + + emitSemantics(func); + } + + // TODO: encode declaration vs. definition + if (isDefinition(func)) + { + m_writer->emit("\n{\n"); + m_writer->indent(); + + // HACK: forward-declare all the local variables needed for the + // parameters of non-entry blocks. + emitPhiVarDecls(func); + + // Need to emit the operations in the blocks of the function + emitFunctionBody(func); + + m_writer->dedent(); + m_writer->emit("}\n\n"); + } + else + { + m_writer->emit(";\n\n"); + } +} + +void CPPSourceEmitter::emitSimpleValueImpl(IRInst* inst) +{ + switch (inst->op) + { + case kIROp_FloatLit: + { + IRConstant* constantInst = static_cast<IRConstant*>(inst); + + m_writer->emit(constantInst->value.floatVal); + + // If the literal is a float, then we need to add 'f' at end + IRType* type = constantInst->getDataType(); + if (type && type->op == kIROp_FloatType ) + { + m_writer->emitChar('f'); + } + break; + } + default: Super::emitSimpleValueImpl(inst); + } +} + void CPPSourceEmitter::emitVectorTypeNameImpl(IRType* elementType, IRIntegerValue elementCount) { emitSimpleType(_getVecType(elementType, int(elementCount))); @@ -1578,7 +2078,44 @@ bool CPPSourceEmitter::tryEmitInstExprImpl(IRInst* inst, const EmitOpInfo& inOut switch (inst->op) { + case kIROp_constructVectorFromScalar: + { + SLANG_ASSERT(inst->getOperandCount() == 1); + IRType* dstType = inst->getDataType(); + + // Check it's a vector + SLANG_ASSERT(dstType->op == kIROp_VectorType); + // Source must be a scalar + SLANG_ASSERT(as<IRBasicType>(inst->getOperand(0)->getDataType())); + + emitOperationCall(IntrinsicOp::ConstructFromScalar, inst, inst->getOperands(), int(inst->getOperandCount()), dstType, inOuterPrec); + return true; + } case kIROp_Construct: + { + IRType* dstType = inst->getDataType(); + IRType* srcType = inst->getOperand(0)->getDataType(); + + if ((dstType->op == kIROp_VectorType || dstType->op == kIROp_MatrixType) && + inst->getOperandCount() == 1) + { + if (as<IRBasicType>(srcType)) + { + emitOperationCall(IntrinsicOp::ConstructFromScalar, inst, inst->getOperands(), int(inst->getOperandCount()), dstType, inOuterPrec); + } + else + { + SLANG_ASSERT(_getElementType(dstType) != _getElementType(srcType)); + // If it's constructed from a type conversion + emitOperationCall(IntrinsicOp::ConstructConvert, inst, inst->getOperands(), int(inst->getOperandCount()), dstType, inOuterPrec); + } + } + else + { + emitOperationCall(IntrinsicOp::Init, inst, inst->getOperands(), int(inst->getOperandCount()), inst->getDataType(), inOuterPrec); + } + return true; + } case kIROp_makeVector: case kIROp_MakeMatrix: { @@ -1599,7 +2136,44 @@ bool CPPSourceEmitter::tryEmitInstExprImpl(IRInst* inst, const EmitOpInfo& inOut } case kIROp_swizzle: { - emitOperationCall(IntrinsicOp::Swizzle, inst, inst->getOperands(), int(inst->getOperandCount()), inst->getDataType(), inOuterPrec); + // For C++ we don't need to emit a swizzle function + // For C we need a construction function + auto swizzleInst = static_cast<IRSwizzle*>(inst); + + IRInst* baseInst = swizzleInst->getBase(); + IRType* baseType = baseInst->getDataType(); + + // If we are swizzling from a built in type, + if (as<IRBasicType>(baseType)) + { + // We can swizzle a scalar type to be a vector, or just a scalar + IRType* dstType = swizzleInst->getDataType(); + if (as<IRBasicType>(dstType)) + { + // If the output is a scalar, then could only have been a .x, which we can just ignore the '.x' part + emitOperand(baseInst, inOuterPrec); + } + else + { + SLANG_ASSERT(dstType->op == kIROp_VectorType); + emitOperationCall(IntrinsicOp::ConstructFromScalar, inst, inst->getOperands(), 1, dstType, inOuterPrec); + } + } + else + { + const Index elementCount = Index(swizzleInst->getElementCount()); + if (elementCount == 1) + { + // If just one thing is extracted then the . syntax will just work + defaultEmitInstExpr(inst, inOuterPrec); + } + else + { + // Will need to generate a swizzle method + emitOperationCall(IntrinsicOp::Swizzle, inst, inst->getOperands(), int(inst->getOperandCount()), inst->getDataType(), inOuterPrec); + } + } + return true; } case kIROp_Call: @@ -1619,6 +2193,7 @@ bool CPPSourceEmitter::tryEmitInstExprImpl(IRInst* inst, const EmitOpInfo& inOut return false; } + default: { IntrinsicOp op = getOperation(inst->op); @@ -1652,15 +2227,290 @@ void CPPSourceEmitter::emitPreprocessorDirectivesImpl() { emitSpecializedOperationDefinition(keyValue.Key); } +} + +void CPPSourceEmitter::emitOperandImpl(IRInst* inst, EmitOpInfo const& outerPrec) +{ + if (shouldFoldInstIntoUseSites(inst)) + { + emitInstExpr(inst, outerPrec); + return; + } + + switch (inst->op) + { + case 0: // nothing yet + case kIROp_GlobalParam: + { + // It's in UniformState + String name = getName(inst); + m_writer->emit("("); + switch (inst->getDataType()->op) + { + case kIROp_ParameterBlockType: + case kIROp_ConstantBufferType: + case kIROp_StructType: + { + m_writer->emit("*"); + break; + } + default: break; + } + m_writer->emit("uniformState->"); + m_writer->emit(name); + m_writer->emit(")"); + break; + } + case kIROp_Param: + { + auto varLayout = getVarLayout(inst); + + if (varLayout) + { + auto semanticNameSpelling = varLayout->systemValueSemantic; + if (semanticNameSpelling.getLength()) + { + semanticNameSpelling = semanticNameSpelling.toLower(); + + if (semanticNameSpelling == "sv_dispatchthreadid") + { + + m_writer->emit("dispatchThreadID"); + return; + } + else if (semanticNameSpelling == "sv_groupid") + { + m_writer->emit("varyingInput.groupID"); + return; + } + else if (semanticNameSpelling == "sv_groupthreadid") + { + m_writer->emit("varyingInput.groupThreadID"); + return; + } + } + } + + ; // Fall-thru + } + case kIROp_GlobalVar: + default: + // GlobalVar should be fine as should just be a member of Context + m_writer->emit(getName(inst)); + break; + } +} + +static bool _isVariable(IROp op) +{ + switch (op) + { + case kIROp_GlobalVar: + case kIROp_GlobalParam: + //case kIROp_Var: + { + return true; + } + default: return false; + } +} + +static bool _isFunction(IROp op) +{ + return op == kIROp_Func; +} + +struct GlobalParamInfo +{ + typedef GlobalParamInfo ThisType; + bool operator<(const ThisType& rhs) const { return offset < rhs.offset; } + bool operator==(const ThisType& rhs) const { return offset == rhs.offset; } + bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } + + IRInst* inst; + UInt offset; + UInt size; +}; + +void CPPSourceEmitter::emitModuleImpl(IRModule* module) +{ + List<EmitAction> actions; + computeEmitActions(module, actions); + + // Emit forward declarations. Don't emit variables that need to be grouped or function definitions (which will ref those types) + for (auto action : actions) + { + switch (action.level) + { + case EmitAction::Level::ForwardDeclaration: + emitFuncDecl(cast<IRFunc>(action.inst)); + break; + + case EmitAction::Level::Definition: + if (_isVariable(action.inst->op) || _isFunction(action.inst->op)) + { + // Don't emit functions or variables that have to be grouped into structures yet + } + else + { + emitGlobalInst(action.inst); + } + break; + } + } - // Lets take a look at layout + // Output the global parameters in a 'UniformState' structure + { + m_writer->emit("struct UniformState\n{\n"); + m_writer->indent(); - ProgramLayout* programLayout = m_programLayout; + List<GlobalParamInfo> params; - if (programLayout) + for (auto action : actions) + { + if (action.level == EmitAction::Level::Definition && action.inst->op == kIROp_GlobalParam) + { + VarLayout* varLayout = CLikeSourceEmitter::getVarLayout(action.inst); + SLANG_ASSERT(varLayout); + const VarLayout::ResourceInfo* varInfo = varLayout->FindResourceInfo(LayoutResourceKind::Uniform); + TypeLayout* typeLayout = varLayout->getTypeLayout(); + TypeLayout::ResourceInfo* typeInfo = typeLayout->FindResourceInfo(LayoutResourceKind::Uniform); + + GlobalParamInfo paramInfo; + paramInfo.inst = action.inst; + // Index is the byte offset for uniform + paramInfo.offset = varInfo ? varInfo->index : 0; + paramInfo.size = typeInfo ? typeInfo->count.raw : 0; + + params.add(paramInfo); + } + } + + // We want to sort by layout offset, and insert suitable padding + params.sort(); + + int padIndex = 0; + size_t offset = 0; + for (const auto& paramInfo : params) + { + if (offset < paramInfo.offset) + { + // We want to output some padding + StringBuilder builder; + builder << "uint8_t _pad" << (padIndex++) << "[" << (paramInfo.offset - offset) << "];\n"; + } + + emitGlobalInst(paramInfo.inst); + // Set offset after this + offset = paramInfo.offset + paramInfo.size; + } + + m_writer->emit("\n"); + m_writer->dedent(); + m_writer->emit("\n};\n\n"); + } + + // Output the 'Context' which will be used for execution { + m_writer->emit("struct Context\n{\n"); + m_writer->indent(); + m_writer->emit("UniformState* uniformState;\n"); + m_writer->emit("ComputeVaryingInput varyingInput;\n"); + m_writer->emit("uint3 dispatchThreadID;\n"); + // Output all the thread locals + for (auto action : actions) + { + if (action.level == EmitAction::Level::Definition && action.inst->op == kIROp_GlobalVar) + { + emitGlobalInst(action.inst); + } + } + + // Finally output the functions as methods on the context + for (auto action : actions) + { + if (action.level == EmitAction::Level::Definition && _isFunction(action.inst->op)) + { + emitGlobalInst(action.inst); + } + } + + m_writer->dedent(); + m_writer->emit("};\n\n"); + } + + // Finally we need to output dll entry points + + for (auto action : actions) + { + if (action.level == EmitAction::Level::Definition && _isFunction(action.inst->op)) + { + IRFunc* func = as<IRFunc>(action.inst); + + auto entryPointLayout = asEntryPoint(func); + if (entryPointLayout) + { + auto resultType = func->getResultType(); + auto name = getFuncName(func); + + // Emit the actual function + emitEntryPointAttributes(func, entryPointLayout); + emitType(resultType, name); + + m_writer->emit("(ComputeVaryingInput* varyingInput, UniformState* uniformState)\n{\n"); + emitSemantics(func); + + m_writer->indent(); + // Initialize when constructing so that globals are zeroed + m_writer->emit("Context context = {};\n"); + m_writer->emit("context.uniformState = uniformState;\n"); + m_writer->emit("context.varyingInput = *varyingInput;\n"); + + // Emit dispatchThreadID + if (entryPointLayout->profile.GetStage() == Stage::Compute) + { + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sv-dispatchthreadid + // SV_DispatchThreadID is the sum of SV_GroupID * numthreads and GroupThreadID. + + static const UInt kAxisCount = 3; + UInt sizeAlongAxis[kAxisCount]; + + // TODO: this is kind of gross because we are using a public + // reflection API function, rather than some kind of internal + // utility it forwards to... + spReflectionEntryPoint_getComputeThreadGroupSize((SlangReflectionEntryPoint*)entryPointLayout, kAxisCount, &sizeAlongAxis[0]); + + m_writer->emit("context.dispatchThreadID = {\n"); + m_writer->indent(); + + StringBuilder builder; + + for (int i = 0; i < kAxisCount; ++i) + { + builder.Clear(); + const char elem[2] = {s_elemNames[i], 0}; + builder << "varyingInput->groupID." << elem << " * " << sizeAlongAxis[i] << " + varyingInput->groupThreadID." << elem; + if (i < kAxisCount - 1) + { + builder << ","; + } + builder << "\n"; + m_writer->emit(builder); + } + + m_writer->dedent(); + m_writer->emit("};\n"); + } + + m_writer->emit("context._"); + m_writer->emit(name); + m_writer->emit("();\n"); + m_writer->dedent(); + m_writer->emit("}\n"); + } + } } } |
