diff options
| -rw-r--r-- | source/slang/emit.cpp | 47 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 8 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 507 | ||||
| -rw-r--r-- | tests/render/cross-compile-entry-point.slang | 2 |
4 files changed, 484 insertions, 80 deletions
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index d8d9e13ed..971f0345b 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -4060,7 +4060,29 @@ emitDeclImpl(decl, nullptr); return getIRName(declRef.decl); } - String getIRName(IRValue* inst) + String getGLSLSystemValueName( + VarLayout* varLayout) + { + auto semanticName = varLayout->systemValueSemantic; + semanticName = semanticName.ToLower(); + auto semanticIndex = varLayout->systemValueSemanticIndex; + + if(semanticName == "sv_position") + { + return "gl_Position"; + } + else if(semanticName == "sv_target") + { + return ""; + } + else + { + return "gl_Unknown"; + } + } + + String getIRName( + IRValue* inst) { switch(inst->op) { @@ -4075,6 +4097,23 @@ emitDeclImpl(decl, nullptr); break; } + if(getTarget(context) == CodeGenTarget::GLSL) + { + if(auto layoutMod = inst->findDecoration<IRLayoutDecoration>()) + { + auto layout = layoutMod->layout; + if(auto varLayout = layout.As<VarLayout>()) + { + if(varLayout->systemValueSemantic.Length() != 0) + { + auto translated = getGLSLSystemValueName(varLayout); + if(translated.Length()) + return translated; + } + } + } + } + if(auto decoration = inst->findDecoration<IRHighLevelDeclDecoration>()) { auto decl = decoration->decl; @@ -5066,7 +5105,7 @@ emitDeclImpl(decl, nullptr); if (!decoration) return nullptr; - return (VarLayout*) decoration->layout; + return (VarLayout*) decoration->layout.Ptr(); } void emitIRLayoutSemantics( @@ -5490,7 +5529,7 @@ emitDeclImpl(decl, nullptr); { if( auto layoutDecoration = func->findDecoration<IRLayoutDecoration>() ) { - return dynamic_cast<EntryPointLayout*>(layoutDecoration->layout); + return layoutDecoration->layout.As<EntryPointLayout>(); } return nullptr; } @@ -5575,7 +5614,7 @@ emitDeclImpl(decl, nullptr); { if (auto layoutDecoration = func->findDecoration<IRLayoutDecoration>()) { - if (auto entryPointLayout = dynamic_cast<EntryPointLayout*>(layoutDecoration->layout)) + if (auto entryPointLayout = layoutDecoration->layout.As<EntryPointLayout>()) { return entryPointLayout; } diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index 50577b2a3..c8e6ee52b 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -32,7 +32,7 @@ struct IRLayoutDecoration : IRDecoration { enum { kDecorationOp = kIRDecorationOp_Layout }; - Layout* layout; + RefPtr<Layout> layout; }; enum IRLoopControl @@ -304,10 +304,8 @@ struct IRBuilder IRFunc* func = nullptr; IRBlock* block = nullptr; // - // TODO: we eventually also want an `IRInst*` for - // an instruction to insert before, so that we - // can also use the builder to insert inside - // an existing block. + // An instruction in the current block that we should insert before + IRInst* insertBeforeInst = nullptr; IRFunc* getFunc() { return func; } IRBlock* getBlock() { return block; } diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index fb6013bc3..5ace50397 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -186,6 +186,13 @@ namespace Slang void IRBuilder::addInst( IRInst* inst) { + auto insertBefore = insertBeforeInst; + if(insertBeforeInst) + { + inst->insertBefore(insertBeforeInst); + return; + } + auto parent = block; if (!parent) return; @@ -2123,6 +2130,354 @@ namespace Slang valueToMove->insertBefore(placeBefore); } + // When scalarizing shader inputs/outputs for GLSL, we need a way + // to refer to a conceptual "value" that might comprise multiple + // IR-level values. We could in principle introduce tuple types + // into the IR so that everything stays at the IR level, but + // it seems easier to just layer it over the top for now. + // + // The `ScalarizedVal` type deals with the "tuple or single value?" + // question, and also the "l-value or r-value?" question. + struct ScalarizedValImpl : RefObject + {}; + struct ScalarizedTupleValImpl; + struct ScalarizedVal + { + enum class Flavor + { + // no value (null pointer) + none, + + // A simple `IRValue*` that represents the actual value + value, + + // An `IRValue*` that represents the address of the actual value + address, + + // A `TupleValImpl` that represents zero or more `ScalarizedVal`s + tuple, + }; + + // Create a value representing a simple value + static ScalarizedVal value(IRValue* irValue) + { + ScalarizedVal result; + result.flavor = Flavor::value; + result.irValue = irValue; + return result; + } + + + // Create a value representing an address + static ScalarizedVal address(IRValue* irValue) + { + ScalarizedVal result; + result.flavor = Flavor::address; + result.irValue = irValue; + return result; + } + + static ScalarizedVal tuple(ScalarizedTupleValImpl* impl) + { + ScalarizedVal result; + result.flavor = Flavor::tuple; + result.impl = (ScalarizedValImpl*)impl; + return result; + } + + Flavor flavor = Flavor::none; + IRValue* irValue = nullptr; + RefPtr<ScalarizedValImpl> impl; + }; + + // This is the case for a value that is a "tuple" of other values + struct ScalarizedTupleValImpl : ScalarizedValImpl + { + struct Element + { + ScalarizedVal val; + DeclRef<Decl> declRef; + }; + + RefPtr<Type> type; + List<Element> elements; + }; + + struct GlobalVaryingDeclarator + { + enum class Flavor + { + array, + }; + + Flavor flavor; + Val* elementCount; + GlobalVaryingDeclarator* next; + }; + + ScalarizedVal createSimpleGLSLGlobalVarying( + IRBuilder* builder, + Type* type, + VarLayout* varLayout, + TypeLayout* typeLayout, + LayoutResourceKind kind, + GlobalVaryingDeclarator* declarator) + { + // TODO: We might be creating an `in` or `out` variable based on + // an `in out` function parameter. In this case we should + // rewrite the `typeLayout` to only include the information + // for the appropriate `kind`. + // + // TODO: actually, we should *always* be re-creating the layout, + // because we need to apply any offsets from the parent... + + // TODO: If there are any `declarator`s, we need to unwrap + // them here, and allow them to modify the type of the + // variable that we declare. + // + // They should probably also affect how we return the + // `ScalarizedVal`, since we need to reflect the AOS->SOA conversion. + + // TODO: detect when the layout represents a system input/output + if( varLayout->systemValueSemantic.Length() != 0 ) + { + // This variable represents a system input/output, + // and we should probably handle that differently, right? + } + + // Simple case: just create a global variable of the matching type, + // and then use the value of the global as a replacement for the + // value of the original parameter. + // + auto globalVariable = addGlobalVariable(builder->getModule(), type); + moveValueBefore(globalVariable, builder->getFunc()); + builder->addLayoutDecoration(globalVariable, varLayout); + return ScalarizedVal::address(globalVariable); + } + + ScalarizedVal createGLSLGlobalVaryingsImpl( + IRBuilder* builder, + Type* type, + VarLayout* varLayout, + TypeLayout* typeLayout, + LayoutResourceKind kind, + GlobalVaryingDeclarator* declarator) + { + if( type->As<BasicExpressionType>() ) + { + return createSimpleGLSLGlobalVarying(builder, type, varLayout, typeLayout, kind, declarator); + } + else if( type->As<VectorExpressionType>() ) + { + return createSimpleGLSLGlobalVarying(builder, type, varLayout, typeLayout, kind, declarator); + } + else if( type->As<MatrixExpressionType>() ) + { + // TODO: a matrix-type varying should probably be handled like an array of rows + return createSimpleGLSLGlobalVarying(builder, type, varLayout, typeLayout, kind, declarator); + } + else if( auto arrayType = type->As<ArrayExpressionType>() ) + { + // We will need to SOA-ize any nested types. + + auto elementType = arrayType->baseType; + auto elementCount = arrayType->ArrayLength; + auto arrayLayout = dynamic_cast<ArrayTypeLayout*>(typeLayout); + SLANG_ASSERT(arrayLayout); + auto elementTypeLayout = arrayLayout->elementTypeLayout; + + GlobalVaryingDeclarator arrayDeclarator; + arrayDeclarator.flavor = GlobalVaryingDeclarator::Flavor::array; + arrayDeclarator.elementCount = elementCount; + arrayDeclarator.next = declarator; + + return createGLSLGlobalVaryingsImpl( + builder, + elementType, + varLayout, + elementTypeLayout, + kind, + &arrayDeclarator); + } + else if( auto declRefType = type->As<DeclRefType>() ) + { + auto declRef = declRefType->declRef; + if( auto structDeclRef = declRef.As<StructDecl>() ) + { + // This is either a user-defined struct, or a builtin type. + // TODO: exclude resource types here. + + // We need to recurse down into the individual fields, + // and generate a variable for each of them. + + // Note: we can use the presence of a `StructTypeLayout` as + // a quick way to reject a bunch of types that aren't actually `struct`s + auto structTypeLayout = dynamic_cast<StructTypeLayout*>(typeLayout); + if( structTypeLayout ) + { + RefPtr<ScalarizedTupleValImpl> tupleValImpl = new ScalarizedTupleValImpl(); + tupleValImpl->type = type; + + // Okay, we want to walk through the fields here, and + // generate one variable for each. + for( auto ff : structTypeLayout->fields ) + { + auto fieldVal = createGLSLGlobalVaryingsImpl( + builder, + ff->typeLayout->type, + ff, + ff->typeLayout, + kind, + declarator); + + ScalarizedTupleValImpl::Element element; + element.val = fieldVal; + element.declRef = ff->varDecl; + + tupleValImpl->elements.Add(element); + } + + return ScalarizedVal::tuple(tupleValImpl); + } + } + } + + // Default case is to fall back on the simple behavior + return createSimpleGLSLGlobalVarying(builder, type, varLayout, typeLayout, kind, declarator); + } + + ScalarizedVal createGLSLGlobalVaryings( + IRBuilder* builder, + Type* type, + VarLayout* layout, + LayoutResourceKind kind) + { + return createGLSLGlobalVaryingsImpl(builder, type, layout, layout->typeLayout, kind, nullptr); + } + + ScalarizedVal extractField( + IRBuilder* builder, + ScalarizedVal const& val, + UInt fieldIndex, + DeclRef<Decl> fieldDeclRef) + { + switch( val.flavor ) + { + case ScalarizedVal::Flavor::value: + return ScalarizedVal::value( + builder->emitFieldExtract( + GetType(fieldDeclRef.As<VarDeclBase>()), + val.irValue, + builder->getDeclRefVal(fieldDeclRef))); + + case ScalarizedVal::Flavor::address: + return ScalarizedVal::address( + builder->emitFieldAddress( + GetType(fieldDeclRef.As<VarDeclBase>()), + val.irValue, + builder->getDeclRefVal(fieldDeclRef))); + + case ScalarizedVal::Flavor::tuple: + { + auto tupleVal = val.impl.As<ScalarizedTupleValImpl>(); + return tupleVal->elements[fieldIndex].val; + } + + default: + SLANG_UNEXPECTED("unimplemented"); + return ScalarizedVal(); + } + + } + + void assign( + IRBuilder* builder, + ScalarizedVal const& left, + ScalarizedVal const& right) + { + switch( left.flavor ) + { + case ScalarizedVal::Flavor::address: + switch( right.flavor ) + { + case ScalarizedVal::Flavor::value: + { + builder->emitStore(left.irValue, right.irValue); + } + break; + + default: + SLANG_UNEXPECTED("unimplemented"); + break; + } + break; + + case ScalarizedVal::Flavor::tuple: + { + // We have a tuple, so we are going to need to try and assign + // to each of its constituent fields. + auto leftTupleVal = left.impl.As<ScalarizedTupleValImpl>(); + UInt elementCount = leftTupleVal->elements.Count(); + + for( UInt ee = 0; ee < elementCount; ++ee ) + { + auto rightElementVal = extractField( + builder, + right, + ee, + leftTupleVal->elements[ee].declRef); + assign(builder, leftTupleVal->elements[ee].val, rightElementVal); + } + } + break; + + default: + SLANG_UNEXPECTED("unimplemented"); + break; + } + } + + IRValue* materializeValue( + IRBuilder* builder, + ScalarizedVal const& val) + { + switch( val.flavor ) + { + case ScalarizedVal::Flavor::value: + return val.irValue; + + case ScalarizedVal::Flavor::address: + { + auto loadInst = builder->emitLoad(val.irValue); + return loadInst; + } + break; + + case ScalarizedVal::Flavor::tuple: + { + auto tupleVal = val.impl.As<ScalarizedTupleValImpl>(); + UInt elementCount = tupleVal->elements.Count(); + + List<IRValue*> elementVals; + for( UInt ee = 0; ee < elementCount; ++ee ) + { + auto elementVal = materializeValue(builder, tupleVal->elements[ee].val); + elementVals.Add(elementVal); + } + + return builder->emitConstructorInst( + tupleVal->type, + elementVals.Count(), + elementVals.Buffer()); + } + break; + + default: + SLANG_UNEXPECTED("unimplemented"); + break; + } + } + void legalizeEntryPointForGLSL( Session* session, IRFunc* func, @@ -2149,6 +2504,7 @@ namespace Slang shared.session = session; IRBuilder builder; builder.shared = &shared; + builder.func = func; // We will start by looking at the return type of the // function, because that will enable us to do an @@ -2185,44 +2541,35 @@ namespace Slang // any `returnVal` instructions with // code to write to that variable. - auto resultVariable = addGlobalVariable(module, resultType); - moveValueBefore(resultVariable, func); - - // We need to transfer layout information from the entry point - // down to the variable: - builder.addLayoutDecoration(resultVariable, entryPointLayout->resultLayout); + auto resultGlobal = createGLSLGlobalVaryings( + &builder, + resultType, + entryPointLayout->resultLayout, + LayoutResourceKind::FragmentOutput); for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() ) { + // TODO: This is silly, because we are looking at every instruction, + // when we know that a `returnVal` should only ever appear as a + // terminator... for( auto ii = bb->getFirstInst(); ii; ii = ii->nextInst ) { if(ii->op != kIROp_ReturnVal) continue; IRReturnVal* returnInst = (IRReturnVal*) ii; - IRValue* resultValue = returnInst->getVal(); - + IRValue* returnValue = returnInst->getVal(); + // Make sure we add these instructions to the right block + builder.block = bb; - // `store <resultVariable> <resultValue>` - IRStore* storeInst = createInst<IRStore>( - &builder, - kIROp_Store, - nullptr, - resultVariable, - resultValue); + // Write to our global variable(s) from the value being returned. + assign(&builder, resultGlobal, ScalarizedVal::value(returnValue)); - // `returnVoid` - IRReturnVoid* returnVoid = createInst<IRReturnVoid>( - &builder, - kIROp_ReturnVoid, - nullptr); + // Emit a `returnVoid` to end the block + auto returnVoid = builder.emitReturn(); - // Put the two new instructions before the old one - storeInst->insertBefore(returnInst); - returnVoid->insertBefore(returnInst); - - // and then remove the old one. + // Remove the old `returnVal` instruction. returnInst->removeAndDeallocate(); // Make sure to resume our iteration at an @@ -2237,8 +2584,6 @@ namespace Slang // and turn them into global variables. if( auto firstBlock = func->getFirstBlock() ) { - IRInst* insertBeforeInst = firstBlock->getFirstInst(); - UInt paramCounter = 0; for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() ) { @@ -2266,6 +2611,11 @@ namespace Slang // cases. auto paramType = pp->getType(); + // Any initialization code we insert nees to be at the start + // of the block: + builder.block = firstBlock; + builder.insertBeforeInst = firstBlock->getFirstInst(); + // TODO: We need to distinguish any true pointers in the // user's code from pointers that only exist for // parameter-passing. This `PtrType` here should actually @@ -2275,59 +2625,76 @@ namespace Slang { // Okay, we have the more interesting case here, // where the parameter was being passed by reference. - // This actually makes our life pretty easy, though, - // since we can simply replace any uses of the existing - // pointer with the global variable (since it will - // be a pointer to storage). + // We are going to create a local variable of the appropriate + // type, which will replace the parameter, along with + // one or more global variables for the actual input/output. - // We start by creating the global variable, using - // the pointed-to type: auto valueType = paramPtrType->getValueType(); - auto paramVariable = addGlobalVariable(module, paramType); - moveValueBefore(paramVariable, func); - - // TODO: We need to special-case `in out` variables here, - // because they actually need to be lowered to *two* - // global variables, not just one. We then need - // to emit logic to initialize the output variable - // based on the input at the start of the entry point, - // and then use the output variable thereafter. - // - // TODO: Actually, I need to double-check that it is - // legal in GLSL to use shader input/output parameters - // as temporaries in general; if not then we'd need - // to introduce a temporary no matter what. - - // Next we attach the layout information from the - // original parameter to the new global variable, - // so that we can lay it out correctly when generating - // target code: - builder.addLayoutDecoration(paramVariable, paramLayout); - - // And finally, we go ahead and replace all the - // uses of the parameter (which was a pointer) with - // uses of the new global variable's address. - pp->replaceUsesWith(paramVariable); + + auto localVariable = builder.emitVar(valueType); + auto localVal = ScalarizedVal::address(localVariable); + + if( auto inOutType = paramPtrType->As<InOutType>() ) + { + // In the `in out` case we need to declare two + // sets of global variables: one for the `in` + // side and one for the `out` side. + auto globalInputVal = createGLSLGlobalVaryings(&builder, valueType, paramLayout, LayoutResourceKind::VertexInput); + + assign(&builder, localVal, globalInputVal); + } + + // Any places where the original parameter was used inside + // the function body should instead use the new local variable. + // Since the parameter was a pointer, we use the variable instruction + // itself (which is an `alloca`d pointer) directly: + pp->replaceUsesWith(localVariable); + + // We also need one or more global variabels to write the output to + // when the function is done. We create them here. + auto globalOutputVal = createGLSLGlobalVaryings(&builder, valueType, paramLayout, LayoutResourceKind::FragmentOutput); + + // Now we need to iterate over all the blocks in the function looking + // for any `return*` instructions, so that we can write to the output variable + for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + auto terminatorInst = bb->getLastInst(); + if(!terminatorInst) + continue; + + switch( terminatorInst->op ) + { + default: + continue; + + case kIROp_ReturnVal: + case kIROp_ReturnVoid: + break; + } + + builder.block = bb; + builder.insertBeforeInst = terminatorInst; + + assign(&builder, globalOutputVal, localVal); + } } else { // This is the "easy" case where the parameter wasn't // being passed by reference. We start by just creating - // a variable of the appropriate type, and attaching - // the required layout information to it. - auto paramVariable = addGlobalVariable(module, paramType); - moveValueBefore(paramVariable, func); - builder.addLayoutDecoration(paramVariable, paramLayout); + // one or more global variables to represent the parameter, + // and attach the required layout information to it along + // the way. - // Next we need to replace uses of the parameter with - // references to the variable. We are going to do that - // somewhat naively, by simply loading the variable - // at the start. + auto globalValue = createGLSLGlobalVaryings(&builder, paramType, paramLayout, LayoutResourceKind::VertexInput); - IRInst* loadInst = builder.emitLoad(paramVariable); - loadInst->insertBefore(insertBeforeInst); + // Next we need to replace uses of the parameter with + // references to the variable(s). We are going to do that + // somewhat naively, by simply materializing the + // variables at the start. + IRValue* materialized = materializeValue(&builder, globalValue); - pp->replaceUsesWith(loadInst); + pp->replaceUsesWith(materialized); } } diff --git a/tests/render/cross-compile-entry-point.slang b/tests/render/cross-compile-entry-point.slang index 018947228..7980ce17e 100644 --- a/tests/render/cross-compile-entry-point.slang +++ b/tests/render/cross-compile-entry-point.slang @@ -1,4 +1,4 @@ -//TEST(render):COMPARE_HLSL_CROSS_COMPILE_RENDER: +//TEST(render):COMPARE_HLSL_CROSS_COMPILE_RENDER:-xslang -use-ir // This is a test to ensure that we can cross-compile a complete entry point. |
