diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/core/slang-string.h | 9 | ||||
| -rw-r--r-- | source/slang/bytecode.cpp | 4 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 298 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 2 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 595 | ||||
| -rw-r--r-- | source/slang/ir.h | 63 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 9 |
7 files changed, 944 insertions, 36 deletions
diff --git a/source/core/slang-string.h b/source/core/slang-string.h index 23ab54c23..3f2b85d28 100644 --- a/source/core/slang-string.h +++ b/source/core/slang-string.h @@ -134,6 +134,15 @@ namespace Slang struct UnownedStringSlice { public: + UnownedStringSlice() + : beginData(0) + , endData(0) + {} + + UnownedStringSlice(char const* b, char const* e) + : beginData(b) + , endData(e) + {} char const* begin() const { diff --git a/source/slang/bytecode.cpp b/source/slang/bytecode.cpp index 854d9bf34..8d8d609b9 100644 --- a/source/slang/bytecode.cpp +++ b/source/slang/bytecode.cpp @@ -956,7 +956,7 @@ BytecodeGenerationPtr<BCModule> generateBytecodeForModule( // for the module, where the registers represent the // values being computed at the global scope. UInt symbolCount = 0; - for( auto gv : irModule->globalValues ) + for( auto gv = irModule->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) { Int globalID = Int(symbolCount++); @@ -975,7 +975,7 @@ BytecodeGenerationPtr<BCModule> generateBytecodeForModule( bcModule->symbolCount = symbolCount; bcModule->symbols = bcSymbols; - for( auto gv : irModule->globalValues ) + for( auto gv = irModule->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) { UInt symbolIndex = *context->mapInstToLocalID.TryGetValue(gv); diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 2d42a79ed..938d54d36 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -460,6 +460,12 @@ struct EmitVisitor Emit(text.begin(), text.end()); } + void emit(UnownedStringSlice const& text) + { + Emit(text.begin(), text.end()); + } + + void emit(Name* name) { emit(getText(name)); @@ -3546,6 +3552,14 @@ struct EmitVisitor switch(info.kind) { case LayoutResourceKind::Uniform: + // Explicit offsets require a GLSL extension. + // + // TODO: We really need to fix this so that we + // only output an explicit offset for things + // that are layed out differently than they + // would normally be... + requireGLSLExtension("GL_ARB_enhanced_layouts"); + Emit("layout(offset = "); Emit(info.index); Emit(")\n"); @@ -4466,6 +4480,165 @@ emitDeclImpl(decl, nullptr); emit(" = "); } + class UnmangleContext + { + private: + char const* cursor_ = nullptr; + char const* begin_ = nullptr; + char const* end_ = nullptr; + + bool isDigit(char c) + { + return (c >= '0') && (c <= '9'); + } + + char peek() + { + return *cursor_; + } + + char get() + { + return *cursor_++; + } + + void expect(char c) + { + if(peek() == c) + { + get(); + } + else + { + // ERROR! + SLANG_UNEXPECTED("mangled name error"); + } + } + + void expect(char const* str) + { + while(char c = *str++) + expect(c); + } + + public: + UnmangleContext() + {} + + UnmangleContext(String const& str) + : cursor_(str.begin()) + , begin_(str.begin()) + , end_(str.end()) + {} + + // Call at the beginning of a mangled name, + // to strip off the main prefix + void startUnmangling() + { + expect("_S"); + } + + int readCount() + { + int c = peek(); + if(!isDigit(c)) + { + SLANG_UNEXPECTED("bad name mangling"); + return 0; + } + get(); + + if(c == '0') + return 0; + + int count = 0; + for(;;) + { + count = count*10 + c - '0'; + c = peek(); + if(!isDigit(c)) + return count; + + get(); + } + } + + UnownedStringSlice readSimpleName() + { + UnownedStringSlice result; + for(;;) + { + int c = peek(); + if(!isDigit(c)) + return result; + + // Read the length part + int count = readCount(); + if(count > (end_ - cursor_)) + { + SLANG_UNEXPECTED("bad name mangling"); + return result; + } + + result = UnownedStringSlice(cursor_, cursor_ + count); + cursor_ += count; + } + } + }; + + void emitIntrinsicCallExpr( + EmitContext* context, + IRCall* inst, + IRFunc* func) + { + // TODO: we need to inspect the mangled name, + // and construct a suitable expression from it... + + UnmangleContext um(func->mangledName); + + um.startUnmangling(); + + auto name = um.readSimpleName(); + + // TODO: need to detect if name represents + // a member function, etc. + + emit(name); + emit("("); + UInt argCount = inst->getArgCount(); + for( UInt aa = 1; aa < argCount; ++aa ) + { + if(aa != 1) emit(", "); + emitIROperand(context, inst->getArg(aa)); + } + emit(")"); + } + + void emitIRCallExpr( + EmitContext* context, + IRCall* inst) + { + // We want to detect any call to an intrinsic operation, + // that we can emit it directly without mangling, etc. + auto funcValue = inst->getArg(0); + if(auto irFunc = asTargetIntrinsic(context, funcValue)) + { + emitIntrinsicCallExpr(context, inst, irFunc); + } + else + { + emitIROperand(context, funcValue); + emit("("); + UInt argCount = inst->getArgCount(); + for( UInt aa = 1; aa < argCount; ++aa ) + { + if(aa != 1) emit(", "); + emitIROperand(context, inst->getArg(aa)); + } + emit(")"); + } + } + void emitIRInstExpr( EmitContext* context, IRValue* value) @@ -4544,19 +4717,20 @@ emitDeclImpl(decl, nullptr); #define CASE(OPCODE, OP) \ case OPCODE: \ emitIROperand(context, inst->getArg(0)); \ - emit("" #OP " "); \ + emit(" " #OP " "); \ emitIROperand(context, inst->getArg(1)); \ break CASE(kIROp_Add, +); CASE(kIROp_Sub, -); - CASE(kIROp_Mul, *); CASE(kIROp_Div, /); CASE(kIROp_Mod, %); CASE(kIROp_Lsh, <<); CASE(kIROp_Rsh, >>); + // TODO: Need to pull out component-wise + // comparison cases for matrices/vectors CASE(kIROp_Eql, ==); CASE(kIROp_Neq, !=); CASE(kIROp_Greater, >); @@ -4573,6 +4747,30 @@ emitDeclImpl(decl, nullptr); #undef CASE + // Component-wise ultiplication needs to be special cased, + // because GLSL uses infix `*` to express inner product + // when working with matrices. + case kIROp_Mul: + // Are we targetting GLSL, and is this a matrix product? + if(getTarget(context) == CodeGenTarget::GLSL + && inst->type->As<MatrixExpressionType>()) + { + emit("matrixCompMult("); + emitIROperand(context, inst->getArg(0)); + emit(", "); + emitIROperand(context, inst->getArg(1)); + emit(")"); + } + else + { + // Default handling is to just rely on infix + // `operator*`. + emitIROperand(context, inst->getArg(0)); + emit(" * "); + emitIROperand(context, inst->getArg(1)); + } + break; + case kIROp_Not: { if (inst->getType()->Equals(getSession()->getBoolType())) @@ -4624,15 +4822,7 @@ emitDeclImpl(decl, nullptr); case kIROp_Call: { - emitIROperand(context, inst->getArg(0)); - emit("("); - UInt argCount = inst->getArgCount(); - for( UInt aa = 1; aa < argCount; ++aa ) - { - if(aa != 1) emit(", "); - emitIROperand(context, inst->getArg(aa)); - } - emit(")"); + emitIRCallExpr(context, (IRCall*)inst); } break; @@ -4666,11 +4856,28 @@ emitDeclImpl(decl, nullptr); case kIROp_Mul_Vector_Matrix: case kIROp_Mul_Matrix_Vector: case kIROp_Mul_Matrix_Matrix: - emit("mul("); - emitIROperand(context, inst->getArg(0)); - emit(", "); - emitIROperand(context, inst->getArg(1)); - emit(")"); + if(getTarget(context) == CodeGenTarget::GLSL) + { + // GLSL expresses inner-product multiplications + // with the ordinary infix `*` operator. + // + // Note that the order of the operands is reversed + // compared to HLSL (and Slang's internal representation) + // because the notion of what is a "row" vs. a "column" + // is reversed between HLSL/Slang and GLSL. + // + emitIROperand(context, inst->getArg(1)); + emit(" * "); + emitIROperand(context, inst->getArg(0)); + } + else + { + emit("mul("); + emitIROperand(context, inst->getArg(0)); + emit(", "); + emitIROperand(context, inst->getArg(1)); + emit(")"); + } break; case kIROp_swizzle: @@ -5201,6 +5408,7 @@ emitDeclImpl(decl, nullptr); return nullptr; } +#if 0 void emitGLSLEntryPointFunc( EmitContext* context, IRFunc* func) @@ -5274,6 +5482,7 @@ emitDeclImpl(decl, nullptr); emit("}\n"); } +#endif bool isEntryPoint(IRFunc* func) { @@ -5296,10 +5505,31 @@ emitDeclImpl(decl, nullptr); return !isDefinition(func); } + // Check whether a given value names a target intrinsic, + // and return the IR function representing the instrinsic + // if it does. + IRFunc* asTargetIntrinsic( + EmitContext* ctxt, + IRValue* value) + { + if(!value) + return nullptr; + + if(value->op != kIROp_Func) + return nullptr; + + IRFunc* func = (IRFunc*) value; + if(!isTargetIntrinsic(ctxt, func)) + return nullptr; + + return func; + } + void emitIRFunc( EmitContext* context, IRFunc* func) { +#if 0 if( getTarget(context) == CodeGenTarget::GLSL && isEntryPoint(func) ) { @@ -5312,7 +5542,9 @@ emitDeclImpl(decl, nullptr); emitGLSLEntryPointFunc(context, func); } - else if(!isDefinition(func)) + else +#endif + if(!isDefinition(func)) { // This is just a function declaration, // and so we want to emit it as such. @@ -5431,6 +5663,14 @@ emitDeclImpl(decl, nullptr); emit("uniform "); break; + case LayoutResourceKind::VertexInput: + emit("in "); + break; + + case LayoutResourceKind::FragmentOutput: + emit("out "); + break; + default: continue; } @@ -5520,7 +5760,16 @@ emitDeclImpl(decl, nullptr); emitGLSLLayoutQualifier(*info); } - emit("uniform "); + if(type->As<GLSLShaderStorageBufferType>()) + { + emit("layout(std430) buffer "); + } + // TODO: what to do with HLSL `tbuffer` style buffers? + else + { + emit("layout(std140) uniform "); + } + emit(getIRName(varDecl)); emit("\n{\n"); @@ -5852,7 +6101,7 @@ emitDeclImpl(decl, nullptr); EmitContext* context, IRModule* module) { - for (auto gv : module->globalValues) + for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) { emitIRUsedTypesForValue(context, gv); } @@ -5864,7 +6113,7 @@ emitDeclImpl(decl, nullptr); { emitIRUsedTypesForModule(context, module); - for (auto gv : module->globalValues) + for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) { emitIRGlobalInst(context, gv); } @@ -5906,6 +6155,7 @@ String emitEntryPoint( CodeGenTarget target) { auto translationUnit = entryPoint->getTranslationUnit(); + auto session = entryPoint->compileRequest->mSession; SharedEmitContext sharedContext; sharedContext.target = target; @@ -6007,6 +6257,14 @@ String emitEntryPoint( // need to link in (and then inline) target-specific implementations // for the library functions that the user called. + switch(target) + { + case CodeGenTarget::GLSL: + legalizeEntryPointsForGLSL(session, lowered); + break; + } + + // TODO: do we want to emit directly from IR, or translate the // IR back into AST for emission? diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index 3a7bbb503..6f6af13cb 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -11,6 +11,7 @@ #include "compiler.h" #include "ir.h" #include "syntax.h" +#include "type-layout.h" namespace Slang { @@ -40,6 +41,7 @@ struct IREntryPointDecoration : IRDecoration enum { kDecorationOp = kIRDecorationOp_EntryPoint }; Profile profile; + EntryPointLayout* layout; }; // Associates a compute-shader entry point function diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index ee28227e3..a79dc3992 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -600,26 +600,172 @@ namespace Slang IRModule* IRBuilder::createModule() { - return new IRModule(); + auto module = new IRModule(); + module->session = getSession(); + return module; } + void IRGlobalValue::insertBefore(IRGlobalValue* other) + { + assert(other); + insertBefore(other, other->parentModule); + } + + void IRGlobalValue::insertBefore(IRGlobalValue* other, IRModule* module) + { + assert(other || module); + + if(!other) other = module->firstGlobalValue; + if(!module) module = other->parentModule; + + assert(module); + + auto nn = other; + auto pp = other ? other->prevGlobalValue : nullptr; + + if(pp) + { + pp->nextGlobalValue = this; + } + else + { + module->firstGlobalValue = this; + } + + if(nn) + { + nn->prevGlobalValue = this; + } + else + { + module->lastGlobalValue = this; + } + + this->prevGlobalValue = pp; + this->nextGlobalValue = nn; + this->parentModule = module; + } + + void IRGlobalValue::insertAtStart(IRModule* module) + { + insertBefore(module->firstGlobalValue, module); + } + + void IRGlobalValue::insertAfter(IRGlobalValue* other) + { + assert(other); + insertAfter(other, other->parentModule); + } + + void IRGlobalValue::insertAfter(IRGlobalValue* other, IRModule* module) + { + assert(other || module); + + if(!other) other = module->lastGlobalValue; + if(!module) module = other->parentModule; + + assert(module); + + auto pp = other; + auto nn = other ? other->nextGlobalValue : nullptr; + + if(pp) + { + pp->nextGlobalValue = this; + } + else + { + module->firstGlobalValue = this; + } + + if(nn) + { + nn->prevGlobalValue = this; + } + else + { + module->lastGlobalValue = this; + } + + this->prevGlobalValue = pp; + this->nextGlobalValue = nn; + this->parentModule = module; + } + + void IRGlobalValue::insertAtEnd(IRModule* module) + { + assert(module); + insertAfter(module->lastGlobalValue, module); + } + + void IRGlobalValue::removeFromParent() + { + auto module = parentModule; + if(!module) + return; + + auto pp = this->prevGlobalValue; + auto nn = this->nextGlobalValue; + + if(pp) + { + pp->nextGlobalValue = nn; + } + else + { + module->firstGlobalValue = nn; + } + + if( nn ) + { + nn->prevGlobalValue = pp; + } + else + { + module->lastGlobalValue = pp; + } + } + + void IRGlobalValue::moveToEnd() + { + auto module = parentModule; + removeFromParent(); + insertAtEnd(module); + } + + + + void addGlobalValue( + IRModule* module, + IRGlobalValue* value) + { + if(!module) + return; + + value->parentModule = module; + value->insertAfter(module->lastGlobalValue, module); + } IRFunc* IRBuilder::createFunc() { - return createValue<IRFunc>( + IRFunc* func = createValue<IRFunc>( this, kIROp_Func, nullptr); + addGlobalValue(getModule(), func); + return func; } IRGlobalVar* IRBuilder::createGlobalVar( IRType* valueType) { auto ptrType = getSession()->getPtrType(valueType); - return createValue<IRGlobalVar>( + IRGlobalVar* globalVar = createValue<IRGlobalVar>( this, kIROp_global_var, ptrType); + addGlobalValue(getModule(), globalVar); + return globalVar; } IRBlock* IRBuilder::createBlock() @@ -1646,7 +1792,7 @@ namespace Slang IRDumpContext* context, IRModule* module) { - for (auto gv : module->globalValues) + for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) { dumpIRGlobalValue(context, gv); } @@ -1676,4 +1822,445 @@ namespace Slang } + // + // + // + + void IRValue::replaceUsesWith(IRValue* other) + { + // We will walk through the list of uses for the current + // instruction, and make them point to the other inst. + IRUse* ff = firstUse; + + // No uses? Nothing to do. + if(!ff) + return; + + IRUse* uu = ff; + for(;;) + { + // The uses had better all be uses of this + // instruction, or invariants are broken. + assert(uu->usedValue == this); + + // Swap this use over to use the other value. + uu->usedValue = other; + + // Try to move to the next use, but bail + // out if we are at the last one. + IRUse* next = uu->nextUse; + if( !next ) + break; + + uu = next; + } + + // We are at the last use (and there must + // be at least one, because we handled + // the case of an empty list earlier). + assert(uu); + + // Our job at this point is to splice + // our list of uses onto the other + // value's uses. + // + // If the value already had uses, then + // we need to patch our new list onto + // the front. + if( auto nn = other->firstUse ) + { + uu->nextUse = nn; + nn->prevLink = &uu->nextUse; + } + + // No matter what, our list of + // uses will become the start + // of the list of uses for + // `other` + other->firstUse = ff; + ff->prevLink = &other->firstUse; + + // And `this` will have no uses any more. + this->firstUse = nullptr; + } + + void IRValue::deallocate() + { + // Run destructor to be sure... + this->~IRValue(); + + // And then free the memory + free((void*) this); + } + + // Insert this instruction into the same basic block + // as `other`, right before it. + void IRInst::insertBefore(IRInst* other) + { + // Make sure this instruction has been removed from any previous parent + this->removeFromParent(); + + auto bb = other->parentBlock; + assert(bb); + + auto pp = other->prevInst; + if( pp ) + { + pp->nextInst = this; + } + else + { + bb->firstInst = this; + } + + this->prevInst = pp; + this->nextInst = other; + this->parentBlock = bb; + + other->prevInst = this; + } + + // Remove this instruction from its parent block, + // and then destroy it (it had better have no uses!) + void IRInst::removeFromParent() + { + // If we don't currently have a parent, then + // we are doing fine. + if(!parentBlock) + return; + + auto bb = parentBlock; + auto pp = prevInst; + auto nn = nextInst; + + if(pp) + { + SLANG_ASSERT(pp->parentBlock == bb); + pp->nextInst = nn; + } + else + { + bb->firstInst = nn; + } + + if(nn) + { + SLANG_ASSERT(nn->parentBlock == bb); + nn->prevInst = pp; + } + else + { + bb->lastInst = pp; + } + + prevInst = nullptr; + nextInst = nullptr; + parentBlock = nullptr; + } + + // Remove this instruction from its parent block, + // and then destroy it (it had better have no uses!) + void IRInst::removeAndDeallocate() + { + removeFromParent(); + deallocate(); + } + + + // + // Legalization of entry points for GLSL: + // + + IRGlobalVar* addGlobalVariable( + IRModule* module, + Type* valueType) + { + auto session = module->session; + + SharedIRBuilder shared; + shared.module = module; + shared.session = session; + + IRBuilder builder; + builder.shared = &shared; + + RefPtr<PtrType> ptrType = session->getPtrType(valueType); + + return builder.createGlobalVar(valueType); + } + + void moveValueBefore( + IRGlobalValue* valueToMove, + IRGlobalValue* placeBefore) + { + valueToMove->removeFromParent(); + valueToMove->insertBefore(placeBefore); + } + + void legalizeEntryPointForGLSL( + Session* session, + IRFunc* func, + IREntryPointDecoration* entryPointInfo) + { + auto module = func->parentModule; + + auto entryPointLayout = entryPointInfo->layout; + + // We require that the entry-point function has no uses, + // because otherwise we'd invalidate the signature + // at all existing call sites. + // + // TODO: the right thing to do here is to split any + // function that both gets called as an entry point + // and as an ordinary function. + assert(!func->firstUse); + + // We create a dummy IR builder, since some of + // the functions require it. + // + // TODO: make some of these free functions... + // + SharedIRBuilder shared; + shared.module = module; + shared.session = session; + IRBuilder builder; + builder.shared = &shared; + + // We will start by looking at the return type of the + // function, because that will enable us to do an + // early-out check to avoid more work. + // + // Specifically, we need to check if the function has + // a `void` return type, because there is no work + // to be done on its return value in that case. + auto resultType = func->getResultType(); + if( resultType->Equals(session->getVoidType()) ) + { + // In this case, the function doesn't return a value + // so we don't need to transform its `return` sites. + // + // We can also use this opportunity to quickly + // check if the function has any parameters, and if + // it doesn't use the chance to bail out immediately. + if( func->getParamCount() == 0 ) + { + // This function is already legal for GLSL + // (at least in terms of parameter/result signature), + // so we won't bother doing anything at all. + return; + } + + // If the function does have parameters, then we need + // to let the logic later in this function handle them. + } + else + { + // Function returns a value, so we need + // to introduce a new global variable + // to hold that value, and then replace + // 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); + + for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + for( auto ii = bb->getFirstInst(); ii; ii = ii->nextInst ) + { + if(ii->op != kIROp_ReturnVal) + continue; + + IRReturnVal* returnInst = (IRReturnVal*) ii; + IRValue* resultValue = returnInst->getVal(); + + + + // `store <resultVariable> <resultValue>` + IRStore* storeInst = createInst<IRStore>( + &builder, + kIROp_Store, + nullptr, + resultVariable, + resultValue); + + // `returnVoid` + IRReturnVoid* returnVoid = createInst<IRReturnVoid>( + &builder, + kIROp_ReturnVoid, + nullptr); + + // Put the two new instructions before the old one + storeInst->insertBefore(returnInst); + returnVoid->insertBefore(returnInst); + + // and then remove the old one. + returnInst->removeAndDeallocate(); + + // Make sure to resume our iteration at an + // appropriate instruciton, since we deleted + // the one we had been using. + ii = returnVoid; + } + } + } + + // Next we will walk through any parameters of the entry-point function, + // 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() ) + { + UInt paramIndex = paramCounter++; + + // We assume that the entry-point layout includes information + // on each parameter, and that these arrays are kept aligned. + // Note that this means that any transformations that mess + // with function signatures will need to also update layout info... + // + assert(entryPointLayout->fields.Count() > paramIndex); + auto paramLayout = entryPointLayout->fields[paramIndex]; + + // We need to create a global variable that will replace the parameter. + // It seems superficially obvious that the variable should have + // the same type as the parameter. + // However, if the parameter was a pointer, in order to + // support `out` or `in out` parameter passing, we need + // to be sure to allocate a variable of the pointed-to + // type instead. + // + // We also need to replace uses of the parameter with + // uses of the variable, and the exact logic there + // will differ a bit between the pointer and non-pointer + // cases. + auto paramType = pp->getType(); + + // 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 + // be `OutTypeBase`, but I'm not confident that all + // the other code is handling that correctly... + if(auto paramPtrType = paramType->As<PtrType>() ) + { + // 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 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); + } + 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); + + // 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. + + IRInst* loadInst = builder.emitLoad(paramVariable); + loadInst->insertBefore(insertBeforeInst); + + pp->replaceUsesWith(loadInst); + } + } + + // At this point we should have eliminated all uses of the + // parameters of the entry block. Also, our control-flow + // rules mean that the entry block cannot be the target + // of any branches in the code, so there can't be + // any control-flow ops that try to match the parameter + // list. + // + // We can safely go through and destroy the parameters + // themselves, and then clear out the parameter list. + for( auto pp = firstBlock->getFirstParam(); pp; ) + { + auto next = pp->getNextParam(); + pp->deallocate(); + pp = next; + } + firstBlock->firstParam = nullptr; + firstBlock->lastParam = nullptr; + } + + // Finally, we need to patch up the type of the entry point, + // because it is no longer accurate. + + auto voidFuncType = new FuncType(); + voidFuncType->resultType = session->getVoidType(); + func->type = voidFuncType; + + // TODO: we should technically be constructing + // a new `EntryPointLayout` here to reflect + // the way that things have been moved around. + } + + void legalizeEntryPointsForGLSL( + Session* session, + IRModule* module) + { + // We need to walk through all the global entry point + // declarations, and transform them to comply with + // GLSL rules. + for( auto globalValue = module->getFirstGlobalValue(); globalValue; globalValue = globalValue->getNextValue()) + { + // Is the global value a function? + if(globalValue->op != kIROp_Func) + continue; + IRFunc* func = (IRFunc*) globalValue; + + // Is the function an entry point? + IREntryPointDecoration* entryPointDecoration = func->findDecoration<IREntryPointDecoration>(); + if(!entryPointDecoration) + continue; + + // Okay, we need to legalize this one. + legalizeEntryPointForGLSL(session, func, entryPointDecoration); + } + } + + } diff --git a/source/slang/ir.h b/source/slang/ir.h index 82684ae17..4fd165c33 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -15,6 +15,7 @@ class Decl; class FuncType; class Layout; class Type; +class Session; struct IRFunc; struct IRInst; @@ -133,7 +134,7 @@ struct IRValue Type* getType() { return type; } // The linked list of decorations attached to this value - IRDecoration* firstDecoration; + IRDecoration* firstDecoration = nullptr; // Look up a decoration in the list of decorations IRDecoration* findDecorationImpl(IRDecorationOp op); @@ -144,9 +145,16 @@ struct IRValue } // The first use of this value (start of a linked list) - IRUse* firstUse; + IRUse* firstUse = nullptr; + // Replace all uses of this value with `other`, so + // that this value will now have no uses. + void replaceUsesWith(IRValue* other); + + // Free a value (which needs to have been removed + // from its parent, had its uses eliminated, etc.) + void deallocate(); }; // Instructions are values that can be executed, @@ -180,6 +188,18 @@ struct IRInst : IRValue { return getArgs()[index].usedValue; } + + // Insert this instruction into the same basic block + // as `other`, right before it. + void insertBefore(IRInst* other); + + // Remove this instruction from its parent block, + // but don't delete it, or replace uses. + void removeFromParent(); + + // Remove this instruction from its parent block, + // and then destroy it (it had better have no uses!) + void removeAndDeallocate(); }; typedef int64_t IRIntegerValue; @@ -268,7 +288,27 @@ struct IRBlock : IRValue typedef FuncType IRFuncType; struct IRGlobalValue : IRValue -{}; +{ + IRModule* parentModule; + + IRGlobalValue* nextGlobalValue; + IRGlobalValue* prevGlobalValue; + + IRGlobalValue* getNextValue() { return nextGlobalValue; } + IRGlobalValue* getPrevValue() { return prevGlobalValue; } + + void insertBefore(IRGlobalValue* other); + void insertBefore(IRGlobalValue* other, IRModule* module); + void insertAtStart(IRModule* module); + + void insertAfter(IRGlobalValue* other); + void insertAfter(IRGlobalValue* other, IRModule* module); + void insertAtEnd(IRModule* module); + + void removeFromParent(); + + void moveToEnd(); +}; // A function is a parent to zero or more blocks of instructions. // @@ -311,14 +351,16 @@ struct IRFunc : IRGlobalValue // A module is a parent to functions, global variables, types, etc. struct IRModule : RefObject { - // The designated entry-point function, if any - IRFunc* entryPoint; + // The compilation session in use. + Session* session; // A list of all the functions and other // global values declared in this module. - List<IRGlobalValue*> globalValues; + IRGlobalValue* firstGlobalValue = nullptr; + IRGlobalValue* lastGlobalValue = nullptr; - // TODO: need a symbol of all the global variables too + IRGlobalValue* getFirstGlobalValue() { return firstGlobalValue; } + IRGlobalValue* getlastGlobalValue() { return lastGlobalValue; } }; void printSlangIRAssembly(StringBuilder& builder, IRModule* module); @@ -326,6 +368,13 @@ String getSlangIRAssembly(IRModule* module); void dumpIR(IRModule* module); +// IR transformations + +// Transform shader entry points so that they conform to GLSL rules. +void legalizeEntryPointsForGLSL( + Session* session, + IRModule* module); + } diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 502d63320..b9307b007 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -2117,8 +2117,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // TODO: need to handle global with initializer! } - getBuilder()->getModule()->globalValues.Add(irGlobal); - return globalVal; } @@ -2837,7 +2835,11 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> getBuilder()->addHighLevelDeclDecoration(irFunc, decl); - getBuilder()->getModule()->globalValues.Add(irFunc); + // For convenience, ensure that any additional global + // values that were emitted while outputting the function + // body appear before the function itself in the list + // of global values. + irFunc->moveToEnd(); return LoweredValInfo::simple(irFunc); } @@ -2939,6 +2941,7 @@ static void lowerEntryPointToIR( auto entryPointDecoration = builder->addDecoration<IREntryPointDecoration>(irFunc); entryPointDecoration->profile = profile; + entryPointDecoration->layout = entryPointLayout; // Attach layout information here. builder->addLayoutDecoration(irFunc, entryPointLayout); |
