https://github.com/shader-slang/slang
Tip revision: 5902acdabc4445a65741a7a6a3a95f223e301059 authored by Yong He on 23 January 2024, 07:19:40 UTC
[LSP] Fetch configs directly from didConfigurationChanged message. (#3478)
[LSP] Fetch configs directly from didConfigurationChanged message. (#3478)
Tip revision: 5902acd
slang-emit-c-like.cpp
// slang-emit-c-like.cpp
#include "slang-emit-c-like.h"
#include "../core/slang-writer.h"
#include "../compiler-core/slang-name.h"
#include "../core/slang-stable-hash.h"
#include "slang-ir-bind-existentials.h"
#include "slang-ir-dce.h"
#include "slang-ir-entry-point-uniforms.h"
#include "slang-ir-glsl-legalize.h"
#include "slang-ir-link.h"
#include "slang-ir-restructure-scoping.h"
#include "slang-ir-specialize.h"
#include "slang-ir-specialize-resources.h"
#include "slang-ir-ssa.h"
#include "slang-ir-util.h"
#include "slang-ir-validate.h"
#include "slang-legalize-types.h"
#include "slang-lower-to-ir.h"
#include "slang-mangle.h"
#include "slang-syntax.h"
#include "slang-type-layout.h"
#include "slang-visitor.h"
#include "slang-intrinsic-expand.h"
#include "slang-emit-source-writer.h"
#include "slang-mangled-lexer.h"
#include <assert.h>
namespace Slang {
struct CLikeSourceEmitter::ComputeEmitActionsContext
{
IRInst* moduleInst;
InstHashSet openInsts;
Dictionary<IRInst*, EmitAction::Level> mapInstToLevel;
List<EmitAction>* actions;
};
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! CLikeSourceEmitter !!!!!!!!!!!!!!!!!!!!!!!!!! */
/* static */SourceLanguage CLikeSourceEmitter::getSourceLanguage(CodeGenTarget target)
{
switch (target)
{
default:
case CodeGenTarget::Unknown:
case CodeGenTarget::None:
{
return SourceLanguage::Unknown;
}
case CodeGenTarget::GLSL:
case CodeGenTarget::GLSL_Vulkan:
case CodeGenTarget::GLSL_Vulkan_OneDesc:
{
return SourceLanguage::GLSL;
}
case CodeGenTarget::HLSL:
{
return SourceLanguage::HLSL;
}
case CodeGenTarget::PTX:
case CodeGenTarget::SPIRV:
case CodeGenTarget::SPIRVAssembly:
case CodeGenTarget::DXBytecode:
case CodeGenTarget::DXBytecodeAssembly:
case CodeGenTarget::DXIL:
case CodeGenTarget::DXILAssembly:
{
return SourceLanguage::Unknown;
}
case CodeGenTarget::CSource:
{
return SourceLanguage::C;
}
case CodeGenTarget::CPPSource:
case CodeGenTarget::HostCPPSource:
case CodeGenTarget::PyTorchCppBinding:
{
return SourceLanguage::CPP;
}
case CodeGenTarget::CUDASource:
{
return SourceLanguage::CUDA;
}
}
}
CLikeSourceEmitter::CLikeSourceEmitter(const Desc& desc)
{
m_writer = desc.sourceWriter;
m_sourceLanguage = getSourceLanguage(desc.codeGenContext->getTargetFormat());
SLANG_ASSERT(m_sourceLanguage != SourceLanguage::Unknown);
m_target = desc.codeGenContext->getTargetFormat();
m_codeGenContext = desc.codeGenContext;
m_entryPointStage = desc.entryPointStage;
m_effectiveProfile = desc.effectiveProfile;
}
SlangResult CLikeSourceEmitter::init()
{
return SLANG_OK;
}
void CLikeSourceEmitter::emitFrontMatterImpl(TargetRequest* targetReq)
{
SLANG_UNUSED(targetReq);
}
void CLikeSourceEmitter::emitPreModuleImpl()
{
for (auto prelude : m_requiredPreludes)
{
m_writer->emit(prelude->getStringSlice());
m_writer->emit("\n");
}
}
//
// Types
//
void CLikeSourceEmitter::emitDeclarator(DeclaratorInfo* declarator)
{
if (!declarator) return;
m_writer->emit(" ");
switch (declarator->flavor)
{
case DeclaratorInfo::Flavor::Name:
{
auto nameDeclarator = (NameDeclaratorInfo*)declarator;
m_writer->emitName(*nameDeclarator->nameAndLoc);
}
break;
case DeclaratorInfo::Flavor::SizedArray:
{
auto arrayDeclarator = (SizedArrayDeclaratorInfo*)declarator;
emitDeclarator(arrayDeclarator->next);
m_writer->emit("[");
if(auto elementCount = arrayDeclarator->elementCount)
{
emitVal(elementCount, getInfo(EmitOp::General));
}
m_writer->emit("]");
}
break;
case DeclaratorInfo::Flavor::UnsizedArray:
{
auto arrayDeclarator = (UnsizedArrayDeclaratorInfo*)declarator;
emitDeclarator(arrayDeclarator->next);
m_writer->emit("[]");
}
break;
case DeclaratorInfo::Flavor::Ptr:
{
// TODO: When there are both pointer and array declarators
// as part of a type, paranetheses may be needed in order
// to disambiguate between a pointer-to-array and an
// array-of-poiners.
//
auto ptrDeclarator = (PtrDeclaratorInfo*)declarator;
m_writer->emit("*");
emitDeclarator(ptrDeclarator->next);
}
break;
case DeclaratorInfo::Flavor::Ref:
{
auto refDeclarator = (RefDeclaratorInfo*)declarator;
m_writer->emit("&");
emitDeclarator(refDeclarator->next);
}
break;
case DeclaratorInfo::Flavor::LiteralSizedArray:
{
auto arrayDeclarator = (LiteralSizedArrayDeclaratorInfo*)declarator;
emitDeclarator(arrayDeclarator->next);
m_writer->emit("[");
m_writer->emit(arrayDeclarator->elementCount);
m_writer->emit("]");
}
break;
case DeclaratorInfo::Flavor::Attributed:
{
auto attributedDeclarator = (AttributedDeclaratorInfo*)declarator;
auto instWithAttributes = attributedDeclarator->instWithAttributes;
for(auto attr : instWithAttributes->getAllAttrs())
{
_emitPostfixTypeAttr(attr);
}
emitDeclarator(attributedDeclarator->next);
}
break;
default:
SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unknown declarator flavor");
break;
}
}
void CLikeSourceEmitter::emitSimpleType(IRType* type)
{
emitSimpleTypeImpl(type);
}
/* static */ UnownedStringSlice CLikeSourceEmitter::getDefaultBuiltinTypeName(IROp op)
{
switch (op)
{
case kIROp_VoidType: return UnownedStringSlice("void");
case kIROp_BoolType: return UnownedStringSlice("bool");
case kIROp_Int8Type: return UnownedStringSlice("int8_t");
case kIROp_Int16Type: return UnownedStringSlice("int16_t");
case kIROp_IntType: return UnownedStringSlice("int");
case kIROp_Int64Type: return UnownedStringSlice("int64_t");
case kIROp_IntPtrType: return UnownedStringSlice("intptr_t");
case kIROp_UInt8Type: return UnownedStringSlice("uint8_t");
case kIROp_UInt16Type: return UnownedStringSlice("uint16_t");
case kIROp_UIntType: return UnownedStringSlice("uint");
case kIROp_UInt64Type: return UnownedStringSlice("uint64_t");
case kIROp_UIntPtrType: return UnownedStringSlice("uintptr_t");
case kIROp_HalfType: return UnownedStringSlice("half");
case kIROp_FloatType: return UnownedStringSlice("float");
case kIROp_DoubleType: return UnownedStringSlice("double");
case kIROp_CharType: return UnownedStringSlice("uint8_t");
default: return UnownedStringSlice();
}
}
/* static */IRNumThreadsDecoration* CLikeSourceEmitter::getComputeThreadGroupSize(IRFunc* func, Int outNumThreads[kThreadGroupAxisCount])
{
IRNumThreadsDecoration* decor = func->findDecoration<IRNumThreadsDecoration>();
for (int i = 0; i < 3; ++i)
{
outNumThreads[i] = decor ? Int(getIntVal(decor->getOperand(i))) : 1;
}
return decor;
}
List<IRWitnessTableEntry*> CLikeSourceEmitter::getSortedWitnessTableEntries(IRWitnessTable* witnessTable)
{
List<IRWitnessTableEntry*> sortedWitnessTableEntries;
auto interfaceType = cast<IRInterfaceType>(witnessTable->getConformanceType());
auto witnessTableItems = witnessTable->getChildren();
// Build a dictionary of witness table entries for fast lookup.
Dictionary<IRInst*, IRWitnessTableEntry*> witnessTableEntryDictionary;
for (auto item : witnessTableItems)
{
if (auto entry = as<IRWitnessTableEntry>(item))
{
witnessTableEntryDictionary[entry->getRequirementKey()] = entry;
}
}
// Get a sorted list of entries using RequirementKeys defined in `interfaceType`.
for (UInt i = 0; i < interfaceType->getOperandCount(); i++)
{
auto reqEntry = cast<IRInterfaceRequirementEntry>(interfaceType->getOperand(i));
IRWitnessTableEntry* entry = nullptr;
if (witnessTableEntryDictionary.tryGetValue(reqEntry->getRequirementKey(), entry))
{
sortedWitnessTableEntries.add(entry);
}
else
{
SLANG_UNREACHABLE("interface requirement key not found in witness table.");
}
}
return sortedWitnessTableEntries;
}
void CLikeSourceEmitter::_emitPrefixTypeAttr(IRAttr* attr)
{
SLANG_UNUSED(attr);
// By defualt we will not emit any attributes.
//
// TODO: If `const` ever surfaces as a type attribute in our IR,
// we may need to handle it here.
}
void CLikeSourceEmitter::_emitPostfixTypeAttr(IRAttr* attr)
{
SLANG_UNUSED(attr);
// By defualt we will not emit any attributes.
//
// TODO: If `const` ever surfaces as a type attribute in our IR,
// we may need to handle it here.
}
void CLikeSourceEmitter::_emitType(IRType* type, DeclaratorInfo* declarator)
{
switch (type->getOp())
{
default:
emitSimpleType(type);
emitDeclarator(declarator);
break;
case kIROp_RateQualifiedType:
{
auto rateQualifiedType = cast<IRRateQualifiedType>(type);
_emitType(rateQualifiedType->getValueType(), declarator);
}
break;
case kIROp_ArrayType:
{
auto arrayType = cast<IRArrayType>(type);
SizedArrayDeclaratorInfo arrayDeclarator(declarator, arrayType->getElementCount());
_emitType(arrayType->getElementType(), &arrayDeclarator);
}
break;
case kIROp_UnsizedArrayType:
{
auto arrayType = cast<IRUnsizedArrayType>(type);
UnsizedArrayDeclaratorInfo arrayDeclarator(declarator);
_emitType(arrayType->getElementType(), &arrayDeclarator);
}
break;
case kIROp_AttributedType:
{
auto attributedType = cast<IRAttributedType>(type);
for(auto attr : attributedType->getAllAttrs())
{
_emitPrefixTypeAttr(attr);
}
AttributedDeclaratorInfo attributedDeclarator(declarator, attributedType);
_emitType(attributedType->getBaseType(), &attributedDeclarator);
}
break;
}
}
void CLikeSourceEmitter::emitWitnessTable(IRWitnessTable* witnessTable)
{
SLANG_UNUSED(witnessTable);
}
void CLikeSourceEmitter::emitComWitnessTable(IRWitnessTable* witnessTable)
{
auto classType = witnessTable->getConcreteType();
for (auto ent : witnessTable->getEntries())
{
auto req = ent->getRequirementKey();
auto func = as<IRFunc>(ent->getSatisfyingVal());
if (!func)
continue;
auto resultType = func->getResultType();
auto name = getName(classType) + "::" + getName(req);
emitFuncDecorations(func);
emitType(resultType, name);
m_writer->emit("(");
// Skip declaration of `this` parameter.
auto firstParam = func->getFirstParam()->getNextParam();
for (auto pp = firstParam; pp; pp = pp->getNextParam())
{
if (pp != firstParam)
m_writer->emit(", ");
emitSimpleFuncParamImpl(pp);
}
m_writer->emit(")");
m_writer->emit("\n{\n");
m_writer->indent();
// emit definition for `this` param.
m_writer->emit("auto ");
m_writer->emit(getName(func->getFirstParam()));
m_writer->emit(" = this;\n");
// Need to emit the operations in the blocks of the function
emitFunctionBody(func);
m_writer->dedent();
m_writer->emit("}\n\n");
}
}
void CLikeSourceEmitter::emitInterface(IRInterfaceType* interfaceType)
{
SLANG_UNUSED(interfaceType);
// By default, don't emit anything for interface types.
// This behavior is overloaded by concrete emitters.
}
void CLikeSourceEmitter::emitRTTIObject(IRRTTIObject* rttiObject)
{
SLANG_UNUSED(rttiObject);
// Ignore rtti object by default.
// This is only used in targets that support dynamic dispatching.
}
void CLikeSourceEmitter::defaultEmitInstStmt(IRInst* inst)
{
switch (inst->getOp())
{
case kIROp_AtomicCounterIncrement:
{
auto oldValName = getName(inst);
m_writer->emit("int ");
m_writer->emit(oldValName);
m_writer->emit(";\n");
m_writer->emit("InterlockedAdd(");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(", 1, ");
m_writer->emit(oldValName);
m_writer->emit(");\n");
}
break;
case kIROp_AtomicCounterDecrement:
{
auto oldValName = getName(inst);
m_writer->emit("int ");
m_writer->emit(oldValName);
m_writer->emit(";\n");
m_writer->emit("InterlockedAdd(");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(", -1, ");
m_writer->emit(oldValName);
m_writer->emit(");\n");
}
break;
case kIROp_StructuredBufferGetDimensions:
{
auto count = _generateUniqueName(UnownedStringSlice("_elementCount"));
auto stride = _generateUniqueName(UnownedStringSlice("_stride"));
m_writer->emit("uint ");
m_writer->emit(count);
m_writer->emit(";\n");
m_writer->emit("uint ");
m_writer->emit(stride);
m_writer->emit(";\n");
emitOperand(inst->getOperand(0), leftSide(getInfo(EmitOp::General), getInfo(EmitOp::Postfix)));
m_writer->emit(".GetDimensions(");
m_writer->emit(count);
m_writer->emit(", ");
m_writer->emit(stride);
m_writer->emit(");\n");
emitInstResultDecl(inst);
m_writer->emit("uint2(");
m_writer->emit(count);
m_writer->emit(", ");
m_writer->emit(stride);
m_writer->emit(");\n");
}
break;
default:
diagnoseUnhandledInst(inst);
}
}
void CLikeSourceEmitter::emitTypeImpl(IRType* type, const StringSliceLoc* nameAndLoc)
{
if (nameAndLoc)
{
// We advance here, such that if there is a #line directive to output it will
// be done so before the type name appears.
m_writer->advanceToSourceLocationIfValid(nameAndLoc->loc);
NameDeclaratorInfo nameDeclarator(nameAndLoc);
_emitType(type, &nameDeclarator);
}
else
{
_emitType(type, nullptr);
}
}
void CLikeSourceEmitter::emitType(IRType* type, Name* name)
{
SLANG_ASSERT(name);
StringSliceLoc nameAndLoc(name->text.getUnownedSlice());
emitType(type, &nameAndLoc);
}
void CLikeSourceEmitter::emitType(IRType* type, const String& name)
{
StringSliceLoc nameAndLoc(name.getUnownedSlice());
emitType(type, &nameAndLoc);
}
void CLikeSourceEmitter::emitType(IRType* type)
{
emitType(type, (StringSliceLoc*)nullptr);
}
void CLikeSourceEmitter::emitType(IRType* type, Name* name, SourceLoc const& nameLoc)
{
SLANG_ASSERT(name);
StringSliceLoc nameAndLoc;
nameAndLoc.loc = nameLoc;
nameAndLoc.name = name->text.getUnownedSlice();
emitType(type, &nameAndLoc);
}
void CLikeSourceEmitter::emitType(IRType* type, NameLoc const& nameAndLoc)
{
emitType(type, nameAndLoc.name, nameAndLoc.loc);
}
void CLikeSourceEmitter::emitLivenessImpl(IRInst* inst)
{
auto liveMarker = as<IRLiveRangeMarker>(inst);
if (!liveMarker)
{
return;
}
IRInst* referenced = liveMarker->getReferenced();
SLANG_ASSERT(referenced);
UnownedStringSlice text;
switch (inst->getOp())
{
case kIROp_LiveRangeStart:
{
text = UnownedStringSlice::fromLiteral("SLANG_LIVE_START");
break;
}
case kIROp_LiveRangeEnd:
{
text = UnownedStringSlice::fromLiteral("SLANG_LIVE_END");
break;
}
default: break;
}
m_writer->emit(text);
m_writer->emit("(");
emitOperand(referenced, getInfo(EmitOp::General));
m_writer->emit(")\n");
}
//
// Expressions
//
bool CLikeSourceEmitter::maybeEmitParens(EmitOpInfo& outerPrec, const EmitOpInfo& prec)
{
bool needParens = (prec.leftPrecedence <= outerPrec.leftPrecedence)
|| (prec.rightPrecedence <= outerPrec.rightPrecedence);
if (needParens)
{
m_writer->emit("(");
outerPrec = getInfo(EmitOp::None);
}
return needParens;
}
void CLikeSourceEmitter::maybeCloseParens(bool needClose)
{
if(needClose) m_writer->emit(")");
}
void CLikeSourceEmitter::emitStringLiteral(String const& value)
{
m_writer->emit("\"");
for (auto c : value)
{
// TODO: This needs a more complete implementation,
// especially if we want to support Unicode.
char buffer[] = { c, 0 };
switch (c)
{
default:
m_writer->emit(buffer);
break;
case '\"': m_writer->emit("\\\""); break;
case '\'': m_writer->emit("\\\'"); break;
case '\\': m_writer->emit("\\\\"); break;
case '\n': m_writer->emit("\\n"); break;
case '\r': m_writer->emit("\\r"); break;
case '\t': m_writer->emit("\\t"); break;
}
}
m_writer->emit("\"");
}
void CLikeSourceEmitter::emitVal(IRInst* val, EmitOpInfo const& outerPrec)
{
if(auto type = as<IRType>(val))
{
emitType(type);
}
else
{
emitInstExpr(val, outerPrec);
}
}
UInt CLikeSourceEmitter::getBindingOffsetForKinds(EmitVarChain* chain, LayoutResourceKindFlags kindFlags)
{
UInt offset = 0;
for (auto cc = chain; cc; cc = cc->next)
{
for (auto offsetAttr : cc->varLayout->getOffsetAttrs())
{
// Accumulate offset for all matching kind
if (LayoutResourceKindFlag::make(offsetAttr->getResourceKind()) & kindFlags)
{
offset += offsetAttr->getOffset();
}
}
}
return offset;
}
Index findRegisterSpaceResourceInfo(IRVarLayout* layout);
UInt CLikeSourceEmitter::getBindingSpaceForKinds(EmitVarChain* chain, LayoutResourceKindFlags kindFlags)
{
UInt space = 0;
bool useSubElementSpace = false;
for (auto cc = chain; cc; cc = cc->next)
{
auto varLayout = cc->varLayout;
for (auto offsetAttr : cc->varLayout->getOffsetAttrs())
{
// Accumulate offset for all matching kinds
if (LayoutResourceKindFlag::make(offsetAttr->getResourceKind()) & kindFlags)
{
space += offsetAttr->getSpace();
}
}
if (!useSubElementSpace)
{
auto spaceOffset = findRegisterSpaceResourceInfo(varLayout);
if (spaceOffset != -1)
{
space += spaceOffset;
useSubElementSpace = true;
}
}
else
{
if (auto resInfo = varLayout->findOffsetAttr(LayoutResourceKind::SubElementRegisterSpace))
{
space += resInfo->getOffset();
}
}
}
return space;
}
UInt CLikeSourceEmitter::getBindingOffset(EmitVarChain* chain, LayoutResourceKind kind)
{
UInt offset = 0;
for(auto cc = chain; cc; cc = cc->next)
{
if(auto resInfo = cc->varLayout->findOffsetAttr(kind))
{
offset += resInfo->getOffset();
}
}
return offset;
}
UInt CLikeSourceEmitter::getBindingSpace(EmitVarChain* chain, LayoutResourceKind kind)
{
UInt space = 0;
bool useSubElementSpace = false;
for(auto cc = chain; cc; cc = cc->next)
{
auto varLayout = cc->varLayout;
if(auto resInfo = varLayout->findOffsetAttr(kind))
{
space += resInfo->getSpace();
}
if (!useSubElementSpace)
{
auto spaceOffset = findRegisterSpaceResourceInfo(varLayout);
if (spaceOffset != -1)
{
space += spaceOffset;
useSubElementSpace = true;
}
}
else
{
if (auto resInfo = varLayout->findOffsetAttr(LayoutResourceKind::SubElementRegisterSpace))
{
space += resInfo->getOffset();
}
}
}
return space;
}
UInt CLikeSourceEmitter::allocateUniqueID()
{
return m_uniqueIDCounter++;
}
// IR-level emit logic
UInt CLikeSourceEmitter::getID(IRInst* value)
{
auto& mapIRValueToID = m_mapIRValueToID;
UInt id = 0;
if (mapIRValueToID.tryGetValue(value, id))
return id;
id = allocateUniqueID();
mapIRValueToID.add(value, id);
return id;
}
void CLikeSourceEmitter::appendScrubbedName(const UnownedStringSlice& name, StringBuilder& out)
{
// We will use a plain `U` as a dummy character to insert
// whenever we need to insert things to make a string into
// valid name.
//
const char dummyChar = 'U';
// Special case a name that is the empty string, just in case.
if(name.getLength() == 0)
{
out.appendChar(dummyChar);
return;
}
// Otherwise, we are going to walk over the name byte by byte
// and write some legal characters to the output as we go.
if(getSourceLanguage() == SourceLanguage::GLSL)
{
// GLSL reserves all names that start with `gl_`,
// so if we are in danger of collision, then make
// our name start with a dummy character instead.
if(name.startsWith("gl_"))
{
out.appendChar(dummyChar);
}
}
// We will also detect user-defined names that
// might overlap with our convention for mangled names,
// to avoid an possible collision.
if(name.startsWith("_S"))
{
out.appendChar(dummyChar);
}
// TODO: This is where we might want to consult
// a dictionary of reserved words for the chosen target
//
// if(isReservedWord(name)) { sb.Append(dummyChar); }
//
// We need to track the previous byte in
// order to detect consecutive underscores for GLSL.
int prevChar = -1;
for(auto c : name)
{
// We will treat a dot character or any path separator
// just like an underscore for the purposes of producing
// a scrubbed name, so that we translate `SomeType.someMethod`
// into `SomeType_someMethod`. This increases the readability
// of output code when the input used lots of nesting of
// code under types/namespaces/etc.
//
// By handling this case at the top of this loop, we
// ensure that a `.`-turned-`_` is handled just like
// a `_` in the original name, and will be properly
// scrubbed for GLSL output.
//
switch(c)
{
default:
break;
case '.':
case '\\':
case '/':
c = '_';
break;
}
if(((c >= 'a') && (c <= 'z'))
|| ((c >= 'A') && (c <= 'Z')))
{
// Ordinary ASCII alphabetic characters are assumed
// to always be okay.
}
else if((c >= '0') && (c <= '9'))
{
// We don't want to allow a digit as the first
// byte in a name, since the result wouldn't
// be a valid identifier in many target languages.
if(prevChar == -1)
{
out.appendChar(dummyChar);
}
}
else if(c == '_')
{
// We will collapse any consecutive sequence of `_`
// characters into a single one (this means that
// some names that were unique in the original
// code might not resolve to unique names after
// scrubbing, but that was true in general).
if(prevChar == '_')
{
// Skip this underscore, so we don't output
// more than one in a row.
continue;
}
}
else
{
// If we run into a character that wouldn't normally
// be allowed in an identifier, we need to translate
// it into something that *is* valid.
//
// Our solution for now will be very clumsy: we will
// emit `x` and then the hexadecimal version of
// the byte we were given.
out.appendChar('x');
out.append(uint32_t((unsigned char) c), 16);
// We don't want to apply the default handling below,
// so skip to the top of the loop now.
prevChar = c;
continue;
}
out.appendChar(c);
prevChar = c;
}
if (getSourceLanguage() == SourceLanguage::GLSL)
{
// It looks like the default glslang name limit is 1024, but let's go a little less so there is some wiggle room
const Index maxTokenLength = 1024 - 8;
const Index length = out.getLength();
if (length > maxTokenLength)
{
// We are going to output with a prefix and a hash of the full name
const auto hash = getStableHashCode64(out.getBuffer(), length);
// Two hex chars per byte
const Index hashSize = sizeof(hash) * 2;
// Work out a size that is within range taking into account the hash size and extra chars
Index reducedBaseLength = maxTokenLength - hashSize - 1;
// If it has a trailing _ remove it.
// We know because of scrubbing there can only be single _
reducedBaseLength -= Index(out[reducedBaseLength - 1] == '_');
// Reduce the length
out.reduceLength(reducedBaseLength);
// Let's add a _ to separate from the rest of the name
out.appendChar('_');
// Append the hash in hex
out.append(hash);
SLANG_ASSERT(out.getLength() <= maxTokenLength);
}
}
}
String CLikeSourceEmitter::generateEntryPointNameImpl(IREntryPointDecoration* entryPointDecor)
{
return entryPointDecor->getName()->getStringSlice();
}
String CLikeSourceEmitter::_generateUniqueName(const UnownedStringSlice& name)
{
//
// We need to be careful that the name follows the rules of the target language,
// so there is a "scrubbing" step that needs to be applied here.
//
// We also need to make sure that the name won't collide with other declarations
// that might have the same name hint applied, so we will still unique
// them by appending the numeric ID of the instruction.
//
// TODO: Find cases where we can drop the suffix safely.
//
// TODO: When we start having to handle symbols with external linkage for
// things like DXIL libraries, we will need to *not* use the friendly
// names for stuff that should be link-able.
//
// The name we output will basically be:
//
// <name>_<uniqueID>
//
// Except that we will "scrub" the name first,
// and we will omit the underscore if the (scrubbed)
// name hint already ends with one.
StringBuilder sb;
appendScrubbedName(name, sb);
// Avoid introducing a double underscore
if (!sb.endsWith("_"))
{
sb.append("_");
}
String key = sb.produceString();
UInt& countRef = m_uniqueNameCounters.getOrAddValue(key, 0);
const UInt count = countRef;
countRef = count + 1;
sb.append(Int32(count));
return sb.produceString();
}
String CLikeSourceEmitter::generateName(IRInst* inst)
{
// If the instruction names something
// that should be emitted as a target intrinsic,
// then use that name instead.
UnownedStringSlice intrinsicDef;
if(findTargetIntrinsicDefinition(inst, intrinsicDef))
{
return String(intrinsicDef);
}
// If the instruction reprsents one of the "magic" declarations
// that makes the NVAPI library work, then we want to make sure
// it uses the original name it was declared with, so that our
// generated code will work correctly with either a Slang-compiled
// or directly `#include`d version of those declarations during
// downstream compilation.
//
if(auto nvapiDecor = inst->findDecoration<IRNVAPIMagicDecoration>())
{
return String(nvapiDecor->getName());
}
auto entryPointDecor = inst->findDecoration<IREntryPointDecoration>();
if (entryPointDecor)
{
if (getSourceLanguage() == SourceLanguage::GLSL)
{
// GLSL will always need to use `main` as the
// name for an entry-point function, but other
// targets should try to use the original name.
//
// TODO: always use the original name, and
// use the appropriate options for glslang to
// make it support a non-`main` name.
//
return "main";
}
return generateEntryPointNameImpl(entryPointDecor);
}
// If the instruction has a linkage decoration, just use that.
if (auto externCppDecoration = inst->findDecoration<IRExternCppDecoration>())
{
// Just use the linkages mangled name directly.
return externCppDecoration->getName();
}
// If we have a name hint on the instruction, then we will try to use that
// to provide the basis for the actual name in the output code.
if(auto nameHintDecoration = inst->findDecoration<IRNameHintDecoration>())
{
return _generateUniqueName(nameHintDecoration->getName());
}
// If the instruction has a linkage decoration, just use that.
if(auto linkageDecoration = inst->findDecoration<IRLinkageDecoration>())
{
// Just use the linkages mangled name directly.
return linkageDecoration->getMangledName();
}
switch (inst->getOp())
{
case kIROp_HLSLConstBufferPointerType:
{
StringBuilder sb;
sb << "BufferPointer_";
sb << getName(inst->getOperand(0));
sb << "_" << Int32(getID(inst));
return sb.produceString();
}
default:
break;
}
// Otherwise fall back to a construct temporary name
// for the instruction.
StringBuilder sb;
sb << "_S";
sb << Int32(getID(inst));
return sb.produceString();
}
String CLikeSourceEmitter::getName(IRInst* inst)
{
String name;
if(!m_mapInstToName.tryGetValue(inst, name))
{
name = generateName(inst);
m_mapInstToName.add(inst, name);
}
return name;
}
String CLikeSourceEmitter::getUnmangledName(IRInst* inst)
{
if (auto nameHintDecor = inst->findDecoration<IRNameHintDecoration>())
return nameHintDecor->getName();
return getName(inst);
}
void CLikeSourceEmitter::emitSimpleValueImpl(IRInst* inst)
{
switch(inst->getOp())
{
case kIROp_IntLit:
{
auto litInst = static_cast<IRConstant*>(inst);
IRBasicType* type = as<IRBasicType>(inst->getDataType());
if (type)
{
switch (type->getBaseType())
{
default:
case BaseType::Int8:
{
m_writer->emit("int8_t(");
m_writer->emit(int8_t(litInst->value.intVal));
m_writer->emit(")");
return;
}
case BaseType::UInt8:
{
m_writer->emit("uint8_t(");
m_writer->emit(UInt(uint8_t(litInst->value.intVal)));
m_writer->emit("U");
m_writer->emit(")");
break;
}
case BaseType::Int16:
{
m_writer->emit("int16_t(");
m_writer->emit(int16_t(litInst->value.intVal));
m_writer->emit(")");
return;
}
case BaseType::UInt16:
{
m_writer->emit("uint16_t(");
m_writer->emit(UInt(uint16_t(litInst->value.intVal)));
m_writer->emit("U");
m_writer->emit(")");
break;
}
case BaseType::Int:
{
m_writer->emit("int(");
m_writer->emit(int32_t(litInst->value.intVal));
m_writer->emit(")");
return;
}
case BaseType::UInt:
{
m_writer->emit(UInt(uint32_t(litInst->value.intVal)));
m_writer->emit("U");
break;
}
case BaseType::Int64:
{
m_writer->emitInt64(int64_t(litInst->value.intVal));
m_writer->emit("LL");
break;
}
case BaseType::UInt64:
{
SLANG_COMPILE_TIME_ASSERT(sizeof(litInst->value.intVal) >= sizeof(uint64_t));
m_writer->emitUInt64(uint64_t(litInst->value.intVal));
m_writer->emit("ULL");
break;
}
case BaseType::IntPtr:
{
#if SLANG_PTR_IS_64
m_writer->emit("int64_t(");
m_writer->emitInt64(int64_t(litInst->value.intVal));
m_writer->emit(")");
#else
m_writer->emit("int(");
m_writer->emit(int(litInst->value.intVal));
m_writer->emit(")");
#endif
break;
}
case BaseType::UIntPtr:
{
#if SLANG_PTR_IS_64
m_writer->emit("uint64_t(");
m_writer->emitUInt64(uint64_t(litInst->value.intVal));
m_writer->emit(")");
#else
m_writer->emit(UInt(uint32_t(litInst->value.intVal)));
m_writer->emit("U");
#endif
break;
}
}
}
else
{
// If no type... just output what we have
m_writer->emit(litInst->value.intVal);
}
break;
}
case kIROp_FloatLit:
m_writer->emit(((IRConstant*) inst)->value.floatVal);
break;
case kIROp_BoolLit:
{
bool val = ((IRConstant*)inst)->value.intVal != 0;
m_writer->emit(val ? "true" : "false");
}
break;
default:
SLANG_UNIMPLEMENTED_X("val case for emit");
break;
}
}
bool CLikeSourceEmitter::shouldFoldInstIntoUseSites(IRInst* inst)
{
// Certain opcodes should never/always be folded in
switch( inst->getOp() )
{
default:
break;
// Never fold these in, because they represent declarations
//
case kIROp_Var:
case kIROp_GlobalVar:
case kIROp_GlobalConstant:
case kIROp_GlobalParam:
case kIROp_Param:
case kIROp_Func:
case kIROp_Alloca:
case kIROp_Store:
return false;
// Never fold these, because their result cannot be computed
// as a sub-expression (they must be emitted as a declaration
// or statement).
case kIROp_UpdateElement:
case kIROp_DefaultConstruct:
return false;
// Always fold these in, because they are trivial
//
case kIROp_IntLit:
case kIROp_FloatLit:
case kIROp_BoolLit:
case kIROp_CapabilityConjunction:
case kIROp_CapabilityDisjunction:
return true;
// Always fold these in, because their results
// cannot be represented in the type system of
// our current targets.
//
// TODO: when we add C/C++ as an optional target,
// we could consider lowering insts that result
// in pointers directly.
//
case kIROp_FieldAddress:
case kIROp_GetElementPtr:
case kIROp_Specialize:
case kIROp_LookupWitness:
case kIROp_GetValueFromBoundInterface:
return true;
case kIROp_GetVulkanRayTracingPayloadLocation:
return true;
}
// Layouts and attributes are only present to annotate other
// instructions, and should not be emitted as anything in
// source code.
//
if(as<IRLayout>(inst))
return true;
if(as<IRAttr>(inst))
return true;
switch( inst->getOp() )
{
default:
break;
// HACK: don't fold these in because we currently lower
// them to initializer lists, which aren't allowed in
// general expression contexts.
//
case kIROp_MakeStruct:
case kIROp_MakeArray:
case kIROp_swizzleSet:
case kIROp_MakeArrayFromElement:
return false;
}
// Instructions with specific result *types* will usually
// want to be folded in, because they aren't allowed as types
// for temporary variables.
auto type = inst->getDataType();
// We treat instructions that yield a type as things we should *always* fold.
//
// TODO: In general, at the point where we emit code we do not expect to
// find types being constructed locally (inside function bodies), but this
// can end up happening because of interaction between different features.
// Notably, if a generic function gets force-inlined early in codegen,
// then any types it constructs will be inlined into the body of the caller
// by default.
//
if(as<IRType>(inst) || as<IRTypeKind>(type))
return true;
// Unwrap any layers of array-ness from the type, so that
// we can look at the underlying data type, in case we
// should *never* expose a value of that type
while (auto arrayType = as<IRArrayTypeBase>(type))
{
type = arrayType->getElementType();
}
// Don't allow temporaries of pointer types to be created,
// if target langauge doesn't support pointers.
if(as<IRPtrTypeBase>(type))
{
if (!doesTargetSupportPtrTypes())
return true;
}
// First we check for uniform parameter groups,
// because a `cbuffer` or GLSL `uniform` block
// does not have a first-class type that we can
// pass around.
//
// TODO: We need to ensure that type legalization
// cleans up cases where we use a parameter group
// or parameter block type as a function parameter...
//
if(as<IRUniformParameterGroupType>(type))
{
// TODO: we need to be careful here, because
// HLSL shader model 6 allows these as explicit
// types.
return true;
}
//
// The stream-output and patch types need to be handled
// too, because they are not really first class (especially
// not in GLSL, but they also seem to confuse the HLSL
// compiler when they get used as temporaries).
//
else if (as<IRHLSLStreamOutputType>(type))
{
return true;
}
else if (as<IRHLSLPatchType>(type))
{
return true;
}
// GLSL doesn't allow texture/resource types to
// be used as first-class values, so we need
// to fold them into their use sites in all cases
if (getSourceLanguage() == SourceLanguage::GLSL)
{
if(as<IRResourceTypeBase>(type))
{
return true;
}
else if(as<IRHLSLStructuredBufferTypeBase>(type))
{
return true;
}
else if(as<IRUntypedBufferResourceType>(type))
{
return true;
}
else if(as<IRSamplerStateTypeBase>(type))
{
return true;
}
else if(as<IRMeshOutputType>(type))
{
return true;
}
if (as<IRHitObjectType>(type))
{
return true;
}
}
// If the instruction is at global scope, then it might represent
// a constant (e.g., the value of an enum case).
//
if(as<IRModuleInst>(inst->getParent()))
{
if(!inst->mightHaveSideEffects())
return true;
}
// Always hold if inst is a call into an [__alwaysFoldIntoUseSite] function.
if (auto call = as<IRCall>(inst))
{
auto callee = call->getCallee();
if (getResolvedInstForDecorations(callee)->findDecoration<IRAlwaysFoldIntoUseSiteDecoration>())
{
return true;
}
}
// Having dealt with all of the cases where we *must* fold things
// above, we can now deal with the more general cases where we
// *should not* fold things.
// Don't fold something with no users:
if(!inst->hasUses())
return false;
// Don't fold something that has multiple users:
if(inst->hasMoreThanOneUse())
return false;
// Don't fold something that might have side effects:
if(inst->mightHaveSideEffects())
return false;
// Don't fold instructions that are marked `[precise]`.
// This could in principle be extended to any other
// decorations that affect the semantics of an instruction
// in ways that require a temporary to be introduced.
//
if(inst->findDecoration<IRPreciseDecoration>())
return false;
// In general, undefined value should be emitted as an uninitialized
// variable, so we shouldn't fold it.
// However, we cannot emit all undefined values a separate variable
// definition for certain types on certain targets (e.g. `out TriangleStream<T>`
// for GLSL), so we check this only after all those special cases are
// considered.
//
if (inst->getOp() == kIROp_undefined)
return false;
// Okay, at this point we know our instruction must have a single use.
auto use = inst->firstUse;
SLANG_ASSERT(use);
SLANG_ASSERT(!use->nextUse);
auto user = use->getUser();
// Check if the use is a call using a target intrinsic that uses the parameter more than once
// in the intrinsic definition.
if (auto callInst = as<IRCall>(user))
{
const auto funcValue = callInst->getCallee();
// Let's see if this instruction is a intrinsic call
// This is significant, because we can within a target intrinsics definition multiple accesses to the same
// parameter. This is not indicated into the call, and can lead to output code computes something multiple
// times as it is folding into the expression of the the target intrinsic, which we don't want.
UnownedStringSlice intrinsicDef;
if (findTargetIntrinsicDefinition(funcValue, intrinsicDef))
{
// Find the index of the original instruction, to see if it's multiply used.
IRUse* args = callInst->getArgs();
const Index paramIndex = Index(use - args);
SLANG_ASSERT(paramIndex >= 0 && paramIndex < Index(callInst->getArgCount()));
// Look through the slice to seeing how many times this parameters is used (signified via the $0...$9)
{
UnownedStringSlice slice = intrinsicDef;
const char* cur = slice.begin();
const char* end = slice.end();
// Count the amount of uses
Index useCount = 0;
while (cur < end)
{
const char c = *cur;
if (c == '$' && cur + 1 < end && cur[1] >= '0' && cur[1] <= '9')
{
const Index index = Index(cur[1] - '0');
useCount += Index(index == paramIndex);
cur += 2;
}
else
{
cur++;
}
}
// If there is more than one use can't fold.
if (useCount > 1)
{
return false;
}
}
}
}
// If this is a call to a ResourceType's member function, don't fold for readability.
if (auto call = as<IRCall>(inst))
{
auto callee = getResolvedInstForDecorations(call->getCallee());
if (callee->findDecoration<IRTargetIntrinsicDecoration>())
{
auto funcType = as<IRFuncType>(callee->getDataType());
if (funcType)
{
if (funcType->getParamCount() > 0)
{
auto firstParamType = funcType->getParamType(0);
if (as<IRResourceTypeBase>(firstParamType))
return false;
if (as<IRHLSLStructuredBufferTypeBase>(firstParamType))
return false;
if (as<IRUntypedBufferResourceType>(firstParamType))
return false;
if (as<IRSamplerStateTypeBase>(firstParamType))
return false;
}
}
}
}
// We'd like to figure out if it is safe to fold our instruction into `user`
// First, let's make sure they are in the same block/parent:
if(inst->getParent() != user->getParent())
return false;
// Now let's look at all the instructions between this instruction
// and the user. If any of them might have side effects, then lets
// bail out now.
for(auto ii = inst->getNextInst(); ii != user; ii = ii->getNextInst())
{
if(!ii)
{
// We somehow reached the end of the block without finding
// the user, which doesn't make sense if uses dominate
// defs. Let's just play it safe and bail out.
return false;
}
if(ii->mightHaveSideEffects())
return false;
}
// As a safeguard, we should not allow an instruction that references
// a block parameter to be folded into a unconcditonal branch
// (which includes arguments for the parameters of the target block).
//
// For simplicity, we will just disallow folding of intructions
// into an unconditonal branch completely, and leave a more refined
// version of this check for later.
//
if(as<IRUnconditionalBranch>(user))
return false;
// Okay, if we reach this point then the user comes later in
// the same block, and there are no instructions with side
// effects in between, so it seems safe to fold things in.
return true;
}
void CLikeSourceEmitter::emitDereferenceOperand(IRInst* inst, EmitOpInfo const& outerPrec)
{
EmitOpInfo newOuterPrec = outerPrec;
if (doesTargetSupportPtrTypes())
{
switch (inst->getOp())
{
case kIROp_Var:
// If `inst` is a variable, dereferencing it is equivalent to just
// emit its name. i.e. *&var ==> var.
// We apply this peep hole optimization here to reduce the clutter of
// resulting code.
m_writer->emit(getName(inst));
return;
case kIROp_FieldAddress:
{
auto innerPrec = getInfo(EmitOp::Postfix);
bool innerNeedClose = maybeEmitParens(newOuterPrec, innerPrec);
auto ii = as<IRFieldAddress>(inst);
auto base = ii->getBase();
if (isPtrToClassType(base->getDataType()))
emitDereferenceOperand(base, leftSide(newOuterPrec, innerPrec));
else
emitOperand(base, leftSide(newOuterPrec, innerPrec));
m_writer->emit("->");
m_writer->emit(getName(ii->getField()));
maybeCloseParens(innerNeedClose);
return;
}
default:
break;
}
auto dereferencePrec = EmitOpInfo::get(EmitOp::Prefix);
bool needClose = maybeEmitParens(newOuterPrec, dereferencePrec);
m_writer->emit("*");
emitOperand(inst, rightSide(newOuterPrec, dereferencePrec));
maybeCloseParens(needClose);
}
else
{
emitOperand(inst, outerPrec);
}
}
void CLikeSourceEmitter::emitVarExpr(IRInst* inst, EmitOpInfo const& outerPrec)
{
if (doesTargetSupportPtrTypes())
{
auto prec = getInfo(EmitOp::Prefix);
auto newOuterPrec = outerPrec;
bool needClose = maybeEmitParens(newOuterPrec, prec);
m_writer->emit("&");
m_writer->emit(getName(inst));
maybeCloseParens(needClose);
}
else
{
m_writer->emit(getName(inst));
}
}
void CLikeSourceEmitter::emitOperandImpl(IRInst* inst, EmitOpInfo const& outerPrec)
{
if( shouldFoldInstIntoUseSites(inst) )
{
emitInstExpr(inst, outerPrec);
return;
}
switch(inst->getOp())
{
case kIROp_Var:
case kIROp_GlobalVar:
emitVarExpr(inst, outerPrec);
break;
default:
m_writer->emit(getName(inst));
break;
}
}
void CLikeSourceEmitter::emitArgs(IRInst* inst)
{
UInt argCount = inst->getOperandCount();
IRUse* args = inst->getOperands();
m_writer->emit("(");
for(UInt aa = 0; aa < argCount; ++aa)
{
if(aa != 0) m_writer->emit(", ");
emitOperand(args[aa].get(), getInfo(EmitOp::General));
}
m_writer->emit(")");
}
void CLikeSourceEmitter::emitRateQualifiersAndAddressSpace(IRInst* value)
{
const auto rate = value->getRate();
const auto ptrTy = composeGetters<IRPtrTypeBase>(value, &IRInst::getDataType);
const auto addressSpace = ptrTy ? ptrTy->getAddressSpace() : -1;
if (rate || addressSpace != -1)
{
emitRateQualifiersAndAddressSpaceImpl(rate, addressSpace);
}
}
void CLikeSourceEmitter::emitInstResultDecl(IRInst* inst)
{
auto type = inst->getDataType();
if(!type)
return;
if (as<IRVoidType>(type))
return;
emitTempModifiers(inst);
emitRateQualifiersAndAddressSpace(inst);
if(as<IRModuleInst>(inst->getParent()))
{
// "Ordinary" instructions at module scope are constants
switch (getSourceLanguage())
{
case SourceLanguage::CUDA:
case SourceLanguage::HLSL:
case SourceLanguage::C:
case SourceLanguage::CPP:
m_writer->emit("static ");
break;
default:
break;
}
m_writer->emit("const ");
}
emitType(type, getName(inst));
m_writer->emit(" = ");
}
IRTargetSpecificDecoration* CLikeSourceEmitter::findBestTargetDecoration(IRInst* inInst)
{
return Slang::findBestTargetDecoration(inInst, getTargetCaps());
}
IRTargetIntrinsicDecoration* CLikeSourceEmitter::_findBestTargetIntrinsicDecoration(IRInst* inInst)
{
return as<IRTargetIntrinsicDecoration>(findBestTargetDecoration(inInst));
}
/* static */bool CLikeSourceEmitter::isOrdinaryName(UnownedStringSlice const& name)
{
char const* cursor = name.begin();
char const*const end = name.end();
// Consume an optional `.` at the start, which indicates
// the ordinary name is for a member function.
if(cursor < end && *cursor == '.')
cursor++;
// Must have at least one char, and first char can't be a digit
if (cursor >= end || CharUtil::isDigit(cursor[0]))
return false;
for(; cursor < end; ++cursor)
{
const auto c = *cursor;
if (CharUtil::isAlphaOrDigit(c) || c == '_')
{
continue;
}
// We allow :: for scope
if (c == ':' && cursor + 1 < end && cursor[1] == ':')
{
++cursor;
continue;
}
return false;
}
return true;
}
void CLikeSourceEmitter::emitIntrinsicCallExpr(IRCall* inst, UnownedStringSlice intrinsicDefinition, EmitOpInfo const& inOuterPrec)
{
emitIntrinsicCallExprImpl(inst, intrinsicDefinition, inOuterPrec);
}
void CLikeSourceEmitter::emitIntrinsicCallExprImpl(
IRCall* inst,
UnownedStringSlice intrinsicDefinition,
EmitOpInfo const& inOuterPrec)
{
auto outerPrec = inOuterPrec;
IRUse* args = inst->getOperands();
Index argCount = inst->getOperandCount();
// First operand was the function to be called
args++;
argCount--;
auto name = intrinsicDefinition;
if(isOrdinaryName(name))
{
// Simple case: it is just an ordinary name, so we call it like a builtin.
auto prec = getInfo(EmitOp::Postfix);
bool needClose = maybeEmitParens(outerPrec, prec);
// The definition string may be an ordinary name prefixed with `.`
// to indicate that the operation should be called as a member
// function on its first operand.
//
if(name[0] == '.')
{
emitOperand(args[0].get(), leftSide(outerPrec, prec));
m_writer->emit(".");
name = UnownedStringSlice(name.begin() + 1, name.end());
args++;
argCount--;
}
m_writer->emit(name);
m_writer->emit("(");
for (Index aa = 0; aa < argCount; ++aa)
{
if (aa != 0) m_writer->emit(", ");
emitOperand(args[aa].get(), getInfo(EmitOp::General));
}
m_writer->emit(")");
maybeCloseParens(needClose);
return;
}
else if(name == ".operator[]")
{
// The user is invoking a built-in subscript operator
//
// TODO: We might want to remove this bit of special-casing
// in favor of making all subscript operations in the standard
// library explicitly declare how they lower. On the flip
// side, that would require modifications to a very large
// number of declarations.
auto prec = getInfo(EmitOp::Postfix);
bool needClose = maybeEmitParens(outerPrec, prec);
Int argIndex = 0;
emitOperand(args[argIndex++].get(), leftSide(outerPrec, prec));
m_writer->emit("[");
emitOperand(args[argIndex++].get(), getInfo(EmitOp::General));
m_writer->emit("]");
if(argIndex < argCount)
{
m_writer->emit(" = ");
emitOperand(args[argIndex++].get(), getInfo(EmitOp::General));
}
maybeCloseParens(needClose);
return;
}
else
{
IntrinsicExpandContext context(this);
context.emit(inst, args, argCount, name);
}
}
void CLikeSourceEmitter::_emitCallArgList(IRCall* inst, int startingOperandIndex)
{
bool isFirstArg = true;
m_writer->emit("(");
UInt argCount = inst->getOperandCount();
for (UInt aa = startingOperandIndex; aa < argCount; ++aa)
{
auto operand = inst->getOperand(aa);
if (as<IRVoidType>(operand->getDataType()))
continue;
// TODO: [generate dynamic dispatch code for generics]
// Pass RTTI object here. Ignore type argument for now.
if (as<IRType>(operand))
continue;
if (!isFirstArg)
m_writer->emit(", ");
else
isFirstArg = false;
emitOperand(inst->getOperand(aa), getInfo(EmitOp::General));
}
m_writer->emit(")");
}
void CLikeSourceEmitter::emitComInterfaceCallExpr(IRCall* inst, EmitOpInfo const& inOuterPrec)
{
auto funcValue = inst->getOperand(0);
auto object = funcValue->getOperand(0);
auto methodKey = funcValue->getOperand(1);
auto prec = getInfo(EmitOp::Postfix);
auto outerPrec = inOuterPrec;
bool needClose = maybeEmitParens(outerPrec, prec);
emitOperand(object, leftSide(outerPrec, prec));
m_writer->emit("->");
m_writer->emit(getName(methodKey));
_emitCallArgList(inst, 2);
maybeCloseParens(needClose);
}
bool CLikeSourceEmitter::findTargetIntrinsicDefinition(IRInst* callee, UnownedStringSlice& outDefinition)
{
return Slang::findTargetIntrinsicDefinition(callee, getTargetCaps(), outDefinition);
}
void CLikeSourceEmitter::emitCallExpr(IRCall* inst, EmitOpInfo outerPrec)
{
auto funcValue = inst->getOperand(0);
// Does this function declare any requirements.
handleRequiredCapabilities(funcValue);
// Detect if this is a call into a COM interface method.
if (funcValue->getOp() == kIROp_LookupWitness)
{
auto operand0Type = funcValue->getOperand(0)->getDataType();
switch (operand0Type->getOp())
{
case kIROp_WitnessTableIDType:
case kIROp_WitnessTableType:
if (as<IRWitnessTableTypeBase>(operand0Type)
->getConformanceType()
->findDecoration<IRComInterfaceDecoration>())
{
emitComInterfaceCallExpr(inst, outerPrec);
return;
}
break;
case kIROp_ComPtrType:
case kIROp_PtrType:
case kIROp_NativePtrType:
emitComInterfaceCallExpr(inst, outerPrec);
return;
}
}
// We want to detect any call to an intrinsic operation,
// that we can emit it directly without mangling, etc.
UnownedStringSlice intrinsicDefinition;
auto resolvedFunc = getResolvedInstForDecorations(funcValue);
if (findTargetIntrinsicDefinition(resolvedFunc, intrinsicDefinition))
{
// Make sure we register all required preludes for emit.
if (auto func = as<IRFunc>(resolvedFunc))
{
for (auto block : func->getBlocks())
{
for (auto ii : block->getChildren())
{
if (auto requirePrelude = as<IRRequirePrelude>(ii))
{
auto preludeTextInst = as<IRStringLit>(requirePrelude->getOperand(0));
if (preludeTextInst)
m_requiredPreludes.add(preludeTextInst);
}
}
}
}
emitIntrinsicCallExpr(inst, intrinsicDefinition, outerPrec);
}
else
{
auto prec = getInfo(EmitOp::Postfix);
bool needClose = maybeEmitParens(outerPrec, prec);
emitOperand(funcValue, leftSide(outerPrec, prec));
_emitCallArgList(inst);
maybeCloseParens(needClose);
}
}
void CLikeSourceEmitter::emitInstExpr(IRInst* inst, const EmitOpInfo& inOuterPrec)
{
// Try target specific impl first
if (tryEmitInstExprImpl(inst, inOuterPrec))
{
return;
}
defaultEmitInstExpr(inst, inOuterPrec);
}
void CLikeSourceEmitter::emitInstStmt(IRInst* inst)
{
// Try target specific impl first
if (tryEmitInstStmtImpl(inst))
{
return;
}
defaultEmitInstStmt(inst);
}
void CLikeSourceEmitter::diagnoseUnhandledInst(IRInst* inst)
{
getSink()->diagnose(inst, Diagnostics::unimplemented, "unexpected IR opcode during code emit");
}
bool CLikeSourceEmitter::hasExplicitConstantBufferOffset(IRInst* cbufferType)
{
auto type = as<IRUniformParameterGroupType>(cbufferType);
if (!type)
return false;
if (as<IRGLSLShaderStorageBufferType>(cbufferType))
return false;
auto structType = as<IRStructType>(type->getElementType());
if (!structType)
return false;
for (auto ff : structType->getFields())
{
if (ff->getKey()->findDecoration<IRPackOffsetDecoration>())
return true;
}
return false;
}
bool CLikeSourceEmitter::isSingleElementConstantBuffer(IRInst* cbufferType)
{
auto type = as<IRUniformParameterGroupType>(cbufferType);
if (!type)
return false;
if (as<IRGLSLShaderStorageBufferType>(cbufferType))
return false;
auto structType = as<IRStructType>(type->getElementType());
if (structType)
return false;
return true;
}
void CLikeSourceEmitter::defaultEmitInstExpr(IRInst* inst, const EmitOpInfo& inOuterPrec)
{
EmitOpInfo outerPrec = inOuterPrec;
bool needClose = false;
switch(inst->getOp())
{
case kIROp_GlobalHashedStringLiterals:
/* Don't need to to output anything for this instruction - it's used for reflecting string literals that
are hashed with 'getStringHash' */
break;
case kIROp_RTTIPointerType:
break;
case kIROp_undefined:
case kIROp_DefaultConstruct:
m_writer->emit(getName(inst));
break;
case kIROp_IntLit:
case kIROp_FloatLit:
case kIROp_BoolLit:
emitSimpleValue(inst);
break;
case kIROp_MakeVector:
case kIROp_MakeMatrix:
case kIROp_VectorReshape:
case kIROp_CastFloatToInt:
case kIROp_CastIntToFloat:
case kIROp_IntCast:
case kIROp_FloatCast:
// Simple constructor call
emitType(inst->getDataType());
emitArgs(inst);
break;
case kIROp_MakeMatrixFromScalar:
{
emitType(inst->getDataType());
auto matrixType = as<IRMatrixType>(inst->getDataType());
SLANG_RELEASE_ASSERT(matrixType);
auto columnCount = as<IRIntLit>(matrixType->getColumnCount());
SLANG_RELEASE_ASSERT(columnCount);
auto rowCount = as<IRIntLit>(matrixType->getRowCount());
SLANG_RELEASE_ASSERT(rowCount);
m_writer->emit("(");
for (IRIntegerValue i = 0; i < rowCount->getValue() * columnCount->getValue(); i++)
{
if (i != 0)
m_writer->emit(", ");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
}
m_writer->emit(")");
}
break;
case kIROp_AllocObj:
m_writer->emit("new ");
m_writer->emit(getName(inst->getDataType()));
m_writer->emit("()");
break;
case kIROp_MakeUInt64:
m_writer->emit("((");
emitType(inst->getDataType());
m_writer->emit("(");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
m_writer->emit(") << 32) + ");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(")");
break;
case kIROp_MakeVectorFromScalar:
case kIROp_MatrixReshape:
case kIROp_CastPtrToInt:
case kIROp_CastIntToPtr:
{
// Simple constructor call
auto prec = getInfo(EmitOp::Prefix);
needClose = maybeEmitParens(outerPrec, prec);
m_writer->emit("(");
emitType(inst->getDataType());
m_writer->emit(")");
emitOperand(inst->getOperand(0), rightSide(outerPrec,prec));
break;
}
case kIROp_FieldExtract:
{
// Extract field from aggregate
IRFieldExtract* fieldExtract = (IRFieldExtract*) inst;
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
auto base = fieldExtract->getBase();
emitOperand(base, leftSide(outerPrec, prec));
if (base->getDataType()->getOp() == kIROp_ClassType)
m_writer->emit("->");
else
m_writer->emit(".");
m_writer->emit(getName(fieldExtract->getField()));
break;
}
case kIROp_FieldAddress:
{
// Extract field "address" from aggregate
IRFieldAddress* ii = (IRFieldAddress*) inst;
if (doesTargetSupportPtrTypes())
{
auto prec = getInfo(EmitOp::Prefix);
needClose = maybeEmitParens(outerPrec, prec);
m_writer->emit("&");
outerPrec = rightSide(outerPrec, prec);
auto innerPrec = getInfo(EmitOp::Postfix);
bool innerNeedClose = maybeEmitParens(outerPrec, innerPrec);
auto base = ii->getBase();
if (isPtrToClassType(base->getDataType()))
emitDereferenceOperand(base, leftSide(outerPrec, innerPrec));
else
emitOperand(base, leftSide(outerPrec, innerPrec));
m_writer->emit("->");
m_writer->emit(getName(ii->getField()));
maybeCloseParens(innerNeedClose);
}
else
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
auto skipBase = isD3DTarget(getTargetReq()) &&
hasExplicitConstantBufferOffset(ii->getBase()->getDataType());
if (!skipBase)
{
auto base = ii->getBase();
emitOperand(base, leftSide(outerPrec, prec));
m_writer->emit(".");
}
m_writer->emit(getName(ii->getField()));
}
break;
}
// Comparisons
case kIROp_Eql:
case kIROp_Neq:
case kIROp_Greater:
case kIROp_Less:
case kIROp_Geq:
case kIROp_Leq:
{
const auto emitOp = getEmitOpForOp(inst->getOp());
auto prec = getInfo(emitOp);
needClose = maybeEmitParens(outerPrec, prec);
emitOperand(inst->getOperand(0), leftSide(outerPrec, prec));
m_writer->emit(" ");
m_writer->emit(prec.op);
m_writer->emit(" ");
emitOperand(inst->getOperand(1), rightSide(outerPrec, prec));
break;
}
// Binary ops
case kIROp_Add:
case kIROp_Sub:
case kIROp_Div:
case kIROp_IRem:
case kIROp_FRem:
case kIROp_Lsh:
case kIROp_Rsh:
case kIROp_BitXor:
case kIROp_BitOr:
case kIROp_BitAnd:
case kIROp_And:
case kIROp_Or:
case kIROp_Mul:
{
const auto emitOp = getEmitOpForOp(inst->getOp());
const auto info = getInfo(emitOp);
needClose = maybeEmitParens(outerPrec, info);
emitOperand(inst->getOperand(0), leftSide(outerPrec, info));
m_writer->emit(" ");
m_writer->emit(info.op);
m_writer->emit(" ");
emitOperand(inst->getOperand(1), rightSide(outerPrec, info));
break;
}
// Unary
case kIROp_Not:
case kIROp_Neg:
case kIROp_BitNot:
{
IRInst* operand = inst->getOperand(0);
const auto emitOp = getEmitOpForOp(inst->getOp());
const auto prec = getInfo(emitOp);
needClose = maybeEmitParens(outerPrec, prec);
switch (inst->getOp())
{
case kIROp_BitNot:
{
// If it's a BitNot, but the data type is bool special case to !
m_writer->emit(as<IRBoolType>(inst->getDataType()) ? "!" : prec.op);
break;
}
case kIROp_Not:
{
m_writer->emit(prec.op);
break;
}
case kIROp_Neg:
{
// Emit a space after the unary -, so if we are followed by a negative literal we don't end up with --
// which some downstream compilers determine to be decrement.
m_writer->emit("- ");
break;
}
}
emitOperand(operand, rightSide(prec, outerPrec));
break;
}
case kIROp_Load:
{
auto base = inst->getOperand(0);
emitDereferenceOperand(base, outerPrec);
if (isKhronosTarget(getTargetReq()) && isSingleElementConstantBuffer(base->getDataType()))
{
m_writer->emit("._data");
}
}
break;
case kIROp_StructuredBufferLoad:
case kIROp_RWStructuredBufferLoad:
{
auto base = inst->getOperand(0);
emitOperand(base, outerPrec);
m_writer->emit(".Load(");
emitOperand(inst->getOperand(1), EmitOpInfo());
m_writer->emit(")");
}
break;
case kIROp_StructuredBufferLoadStatus:
case kIROp_RWStructuredBufferLoadStatus:
{
auto base = inst->getOperand(0);
emitOperand(base, outerPrec);
m_writer->emit(".Load(");
emitOperand(inst->getOperand(1), EmitOpInfo());
m_writer->emit(", ");
emitOperand(inst->getOperand(2), EmitOpInfo());
m_writer->emit(")");
}
break;
case kIROp_RWStructuredBufferGetElementPtr:
{
auto base = inst->getOperand(0);
emitOperand(base, outerPrec);
m_writer->emit("[");
emitOperand(inst->getOperand(1), EmitOpInfo());
m_writer->emit("]");
}
break;
case kIROp_RWStructuredBufferStore:
{
auto base = inst->getOperand(0);
emitOperand(base, EmitOpInfo());
m_writer->emit(".Store(");
emitOperand(inst->getOperand(1), EmitOpInfo());
m_writer->emit(", ");
emitOperand(inst->getOperand(2), EmitOpInfo());
m_writer->emit(")");
}
break;
case kIROp_StructuredBufferAppend:
{
auto outer = getInfo(EmitOp::General);
emitOperand(inst->getOperand(0), leftSide(outer, getInfo(EmitOp::Postfix)));
m_writer->emit(".Append(");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
m_writer->emit(")");
}
break;
case kIROp_StructuredBufferConsume:
{
auto outer = getInfo(EmitOp::General);
emitOperand(inst->getOperand(0), leftSide(outer, getInfo(EmitOp::Postfix)));
m_writer->emit(".Consume()");
}
break;
case kIROp_Call:
{
emitCallExpr((IRCall*)inst, outerPrec);
}
break;
case kIROp_GroupMemoryBarrierWithGroupSync:
m_writer->emit("GroupMemoryBarrierWithGroupSync()");
break;
case kIROp_getNativeStr:
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
emitOperand(inst->getOperand(0), leftSide(outerPrec, prec));
if (as<IRPtrTypeBase>(inst->getOperand(0)->getDataType()))
m_writer->emit("->");
else
m_writer->emit(".");
m_writer->emit("getBuffer()");
break;
}
case kIROp_MakeString:
{
m_writer->emit("String(");
emitOperand(inst->getOperand(0), EmitOpInfo());
m_writer->emit(")");
break;
}
case kIROp_GetNativePtr:
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
emitOperand(inst->getOperand(0), leftSide(outerPrec, prec));
m_writer->emit(".get()");
break;
}
case kIROp_GetManagedPtrWriteRef:
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
emitDereferenceOperand(inst->getOperand(0), leftSide(outerPrec, prec));
m_writer->emit(".writeRef()");
break;
}
case kIROp_ManagedPtrAttach:
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
emitDereferenceOperand(inst->getOperand(0), leftSide(outerPrec, prec));
m_writer->emit(".attach(");
emitOperand(inst->getOperand(1), EmitOpInfo());
m_writer->emit(")");
break;
}
case kIROp_ManagedPtrDetach:
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
emitOperand(inst->getOperand(0), leftSide(outerPrec, prec));
m_writer->emit(".detach()");
break;
}
case kIROp_GetElement:
case kIROp_GetElementPtr:
case kIROp_ImageSubscript:
// HACK: deal with translation of GLSL geometry shader input arrays.
if(auto decoration = inst->getOperand(0)->findDecoration<IRGLSLOuterArrayDecoration>())
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
m_writer->emit(decoration->getOuterArrayName());
m_writer->emit("[");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
m_writer->emit("].");
emitOperand(inst->getOperand(0), rightSide(prec, outerPrec));
break;
}
else
{
if (inst->getOp() == kIROp_GetElementPtr && doesTargetSupportPtrTypes())
{
const auto info = getInfo(EmitOp::Prefix);
needClose = maybeEmitParens(outerPrec, info);
m_writer->emit("&");
auto rightSidePrec = rightSide(outerPrec, info);
auto postfixInfo = getInfo(EmitOp::Postfix);
bool rightSideNeedClose = maybeEmitParens(rightSidePrec, postfixInfo);
if (isPtrToArrayType(inst->getOperand(0)->getDataType()))
emitDereferenceOperand(inst->getOperand(0), leftSide(rightSidePrec, postfixInfo));
else
emitOperand(inst->getOperand(0), leftSide(rightSidePrec, postfixInfo));
m_writer->emit("[");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
m_writer->emit("]");
maybeCloseParens(rightSideNeedClose);
break;
}
else
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
emitOperand(inst->getOperand(0), leftSide(outerPrec, prec));
m_writer->emit("[");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
m_writer->emit("]");
}
}
break;
case kIROp_swizzle:
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
auto ii = (IRSwizzle*)inst;
emitOperand(ii->getBase(), leftSide(outerPrec, prec));
m_writer->emit(".");
const Index elementCount = Index(ii->getElementCount());
for (Index ee = 0; ee < elementCount; ++ee)
{
IRInst* irElementIndex = ii->getElementIndex(ee);
SLANG_RELEASE_ASSERT(irElementIndex->getOp() == kIROp_IntLit);
IRConstant* irConst = (IRConstant*)irElementIndex;
UInt elementIndex = (UInt)irConst->value.intVal;
SLANG_RELEASE_ASSERT(elementIndex < 4);
char const* kComponents[] = { "x", "y", "z", "w" };
m_writer->emit(kComponents[elementIndex]);
}
}
break;
case kIROp_Specialize:
{
emitOperand(inst->getOperand(0), outerPrec);
}
break;
case kIROp_WrapExistential:
{
// Normally `WrapExistential` shouldn't exist in user code at this point.
// The only exception is when the user is calling a stdlib generic
// function that has an existential type argument, for example
// `StructuredBuffer<ISomething>.Load()`.
// We can safely ignore the `wrapExistential` operation in this case.
emitOperand(inst->getOperand(0), outerPrec);
}
break;
case kIROp_Select:
{
auto prec = getInfo(EmitOp::Conditional);
needClose = maybeEmitParens(outerPrec, prec);
emitOperand(inst->getOperand(0), leftSide(outerPrec, prec));
m_writer->emit(" ? ");
emitOperand(inst->getOperand(1), prec);
m_writer->emit(" : ");
emitOperand(inst->getOperand(2), rightSide(prec, outerPrec));
}
break;
case kIROp_Param:
m_writer->emit(getName(inst));
break;
case kIROp_MakeArray:
case kIROp_MakeStruct:
{
// TODO: initializer-list syntax may not always
// be appropriate, depending on the context
// of the expression.
m_writer->emit("{ ");
UInt argCount = inst->getOperandCount();
for (UInt aa = 0; aa < argCount; ++aa)
{
if (aa != 0) m_writer->emit(", ");
emitOperand(inst->getOperand(aa), getInfo(EmitOp::General));
}
m_writer->emit(" }");
}
break;
case kIROp_MakeArrayFromElement:
{
// TODO: initializer-list syntax may not always
// be appropriate, depending on the context
// of the expression.
m_writer->emit("{ ");
UInt argCount =
(UInt)cast<IRIntLit>(cast<IRArrayType>(inst->getDataType())->getElementCount())
->getValue();
for (UInt aa = 0; aa < argCount; ++aa)
{
if (aa != 0) m_writer->emit(", ");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
}
m_writer->emit(" }");
}
break;
case kIROp_BitCast:
{
// Note: we are currently emitting casts as plain old
// C-style casts, which may not always perform a bitcast.
//
// TODO: This operation should map to an intrinsic to be
// provided in a prelude for C/C++, so that the target
// can easily emit code for whatever the best possible
// bitcast is on the platform.
auto prec = getInfo(EmitOp::Prefix);
needClose = maybeEmitParens(outerPrec, prec);
m_writer->emit("(");
emitType(inst->getDataType());
m_writer->emit(")");
m_writer->emit("(");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(")");
}
break;
case kIROp_GlobalConstant:
case kIROp_GetValueFromBoundInterface:
emitOperand(inst->getOperand(0), outerPrec);
break;
case kIROp_ByteAddressBufferLoad:
m_writer->emit("(");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(").Load<");
emitType(inst->getDataType());
m_writer->emit(" >(");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
m_writer->emit(")");
break;
case kIROp_ByteAddressBufferStore:
{
auto prec = getInfo(EmitOp::Postfix);
needClose = maybeEmitParens(outerPrec, prec);
emitOperand(inst->getOperand(0), leftSide(outerPrec, prec));
m_writer->emit(".Store(");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
m_writer->emit(",");
emitOperand(inst->getOperand(2), getInfo(EmitOp::General));
m_writer->emit(")");
}
break;
case kIROp_PackAnyValue:
{
m_writer->emit("packAnyValue<");
m_writer->emit(getIntVal(cast<IRAnyValueType>(inst->getDataType())->getSize()));
m_writer->emit(",");
emitType(inst->getOperand(0)->getDataType());
m_writer->emit(">(");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(")");
break;
}
case kIROp_UnpackAnyValue:
{
m_writer->emit("unpackAnyValue<");
m_writer->emit(getIntVal(cast<IRAnyValueType>(inst->getOperand(0)->getDataType())->getSize()));
m_writer->emit(",");
emitType(inst->getDataType());
m_writer->emit(">(");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(")");
break;
}
case kIROp_GpuForeach:
{
auto operand = inst->getOperand(2);
if (as<IRFunc>(operand))
{
//emitOperand(operand->findDecoration<IREntryPointDecoration>(), getInfo(EmitOp::General));
emitOperand(operand, getInfo(EmitOp::General));
}
else
{
SLANG_UNEXPECTED("Expected 3rd operand to be a function");
}
m_writer->emit("_wrapper(");
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(", ");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
UInt argCount = inst->getOperandCount();
for (UInt aa = 3; aa < argCount; ++aa)
{
m_writer->emit(", ");
emitOperand(inst->getOperand(aa), getInfo(EmitOp::General));
}
m_writer->emit(")");
break;
}
case kIROp_GetStringHash:
{
auto getStringHashInst = as<IRGetStringHash>(inst);
auto stringLit = getStringHashInst->getStringLit();
if (stringLit)
{
auto slice = stringLit->getStringSlice();
m_writer->emit(getStableHashCode32(slice.begin(), slice.getLength()).hash);
}
else
{
// Couldn't handle
diagnoseUnhandledInst(inst);
}
break;
}
default:
diagnoseUnhandledInst(inst);
break;
}
maybeCloseParens(needClose);
}
void CLikeSourceEmitter::emitInst(IRInst* inst)
{
try
{
_emitInst(inst);
}
// Don't emit any context message for an explicit `AbortCompilationException`
// because it should only happen when an error is already emitted.
catch(const AbortCompilationException&) { throw; }
catch(...)
{
noteInternalErrorLoc(inst->sourceLoc);
throw;
}
}
void CLikeSourceEmitter::_emitInst(IRInst* inst)
{
if (shouldFoldInstIntoUseSites(inst))
{
return;
}
// Specially handle params. The issue here is around PHI nodes, and that they do not
// have source loc information, by default, but we don't want to force outputting a #line.
if (inst->getOp() == kIROp_Param)
{
m_writer->advanceToSourceLocationIfValid(inst->sourceLoc);
}
else
{
m_writer->advanceToSourceLocation(inst->sourceLoc);
}
switch(inst->getOp())
{
default:
emitInstResultDecl(inst);
emitInstExpr(inst, getInfo(EmitOp::General));
m_writer->emit(";\n");
break;
case kIROp_DebugSource:
case kIROp_DebugLine:
break;
// Insts that needs to be emitted as code blocks.
case kIROp_CudaKernelLaunch:
case kIROp_AtomicCounterIncrement:
case kIROp_AtomicCounterDecrement:
case kIROp_StructuredBufferGetDimensions:
emitInstStmt(inst);
break;
case kIROp_LiveRangeStart:
case kIROp_LiveRangeEnd:
emitLiveness(inst);
break;
case kIROp_undefined:
case kIROp_DefaultConstruct:
{
auto type = inst->getDataType();
_emitInstAsDefaultInitializedVar(inst, type);
}
break;
case kIROp_AllocateOpaqueHandle:
break;
case kIROp_Var:
{
auto var = cast<IRVar>(inst);
emitVar(var);
}
break;
case kIROp_Store:
{
auto store = cast<IRStore>(inst);
emitStore(store);
}
break;
case kIROp_Param:
// Don't emit parameters, since they are declared as part of the function.
break;
case kIROp_FieldAddress:
// skip during code emit, since it should be
// folded into use site(s)
break;
case kIROp_Return:
m_writer->emit("return");
if (((IRReturn*)inst)->getVal()->getOp() != kIROp_VoidLit)
{
m_writer->emit(" ");
emitOperand(((IRReturn*) inst)->getVal(), getInfo(EmitOp::General));
}
m_writer->emit(";\n");
break;
case kIROp_discard:
m_writer->emit("discard;\n");
break;
case kIROp_swizzleSet:
{
auto ii = (IRSwizzleSet*)inst;
emitInstResultDecl(inst);
emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
m_writer->emit(";\n");
auto subscriptOuter = getInfo(EmitOp::General);
auto subscriptPrec = getInfo(EmitOp::Postfix);
bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec);
emitOperand(inst, leftSide(subscriptOuter, subscriptPrec));
m_writer->emit(".");
UInt elementCount = ii->getElementCount();
for (UInt ee = 0; ee < elementCount; ++ee)
{
IRInst* irElementIndex = ii->getElementIndex(ee);
SLANG_RELEASE_ASSERT(irElementIndex->getOp() == kIROp_IntLit);
IRConstant* irConst = (IRConstant*)irElementIndex;
UInt elementIndex = (UInt)irConst->value.intVal;
SLANG_RELEASE_ASSERT(elementIndex < 4);
char const* kComponents[] = { "x", "y", "z", "w" };
m_writer->emit(kComponents[elementIndex]);
}
maybeCloseParens(needCloseSubscript);
m_writer->emit(" = ");
emitOperand(inst->getOperand(1), getInfo(EmitOp::General));
m_writer->emit(";\n");
}
break;
case kIROp_SwizzledStore:
{
auto subscriptOuter = getInfo(EmitOp::General);
auto subscriptPrec = getInfo(EmitOp::Postfix);
bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec);
auto ii = cast<IRSwizzledStore>(inst);
emitDereferenceOperand(ii->getDest(), leftSide(subscriptOuter, subscriptPrec));
m_writer->emit(".");
UInt elementCount = ii->getElementCount();
for (UInt ee = 0; ee < elementCount; ++ee)
{
IRInst* irElementIndex = ii->getElementIndex(ee);
SLANG_RELEASE_ASSERT(irElementIndex->getOp() == kIROp_IntLit);
IRConstant* irConst = (IRConstant*)irElementIndex;
UInt elementIndex = (UInt)irConst->value.intVal;
SLANG_RELEASE_ASSERT(elementIndex < 4);
char const* kComponents[] = { "x", "y", "z", "w" };
m_writer->emit(kComponents[elementIndex]);
}
maybeCloseParens(needCloseSubscript);
m_writer->emit(" = ");
emitOperand(ii->getSource(), getInfo(EmitOp::General));
m_writer->emit(";\n");
}
break;
case kIROp_UpdateElement:
{
auto ii = (IRUpdateElement*)inst;
auto subscriptOuter = getInfo(EmitOp::General);
auto subscriptPrec = getInfo(EmitOp::Postfix);
emitInstResultDecl(inst);
if (auto arrayType = as<IRArrayType>(inst->getDataType()))
{
auto arraySize = as<IRIntLit>(arrayType->getElementCount());
SLANG_RELEASE_ASSERT(arraySize);
m_writer->emit("{");
for (UInt i = 0; i < (UInt)arraySize->getValue(); i++)
{
if (i > 0)
m_writer->emit(", ");
emitOperand(ii->getOldValue(), leftSide(subscriptOuter, subscriptPrec));
m_writer->emit("[");
m_writer->emit(i);
m_writer->emit("]");
}
m_writer->emit("}");
}
else
{
emitOperand(ii->getOldValue(), getInfo(EmitOp::General));
}
m_writer->emit(";\n");
emitOperand(ii, leftSide(subscriptOuter, subscriptPrec));
for (UInt i = 0; i < ii->getAccessKeyCount(); i++)
{
auto key = ii->getAccessKey(i);
if (as<IRStructKey>(key))
{
m_writer->emit(".");
m_writer->emit(getName(key));
}
else
{
m_writer->emit("[");
emitOperand(key, getInfo(EmitOp::General));
m_writer->emit("]");
}
}
m_writer->emit(" = ");
emitOperand(ii->getElementValue(), getInfo(EmitOp::General));
m_writer->emit(";\n");
}
break;
}
}
void CLikeSourceEmitter::emitStore(IRStore* store)
{
if (store->getPrevInst() == store->getOperand(0) && store->getOperand(0)->getOp() == kIROp_Var)
{
// If we are storing into a `var` that is defined right before the store, we have
// already folded the store in the initialization of the `var`, so we can skip here.
//
return;
}
_emitStoreImpl(store);
}
void CLikeSourceEmitter::_emitStoreImpl(IRStore* store)
{
auto srcVal = store->getVal();
auto dstPtr = store->getPtr();
auto prec = getInfo(EmitOp::Assign);
emitDereferenceOperand(dstPtr, leftSide(getInfo(EmitOp::General), prec));
m_writer->emit(" = ");
emitOperand(srcVal, rightSide(prec, getInfo(EmitOp::General)));
m_writer->emit(";\n");
}
void CLikeSourceEmitter::_emitInstAsDefaultInitializedVar(IRInst* inst, IRType* type)
{
emitType(type, getName(inst));
// On targets that support empty initializers, we will emit it.
switch (this->getTarget())
{
case CodeGenTarget::CPPSource:
case CodeGenTarget::HostCPPSource:
case CodeGenTarget::PyTorchCppBinding:
case CodeGenTarget::CUDASource:
m_writer->emit(" = {}");
break;
}
m_writer->emit(";\n");
}
void CLikeSourceEmitter::emitSemanticsUsingVarLayout(IRVarLayout* varLayout)
{
if(auto semanticAttr = varLayout->findAttr<IRSemanticAttr>())
{
// Note: We force the semantic name stored in the IR to
// upper-case here because that is what existing Slang
// tests had assumed and continue to rely upon.
//
// The original rationale for switching to uppercase was
// canonicalization for reflection (users can't accidentally
// write code that works for `COLOR` but not for `Color`),
// but it would probably be more ideal for our output code
// to give the semantic name as close to how it was originally spelled
// spelled as possible.
//
// TODO: Try removing this step and fixing up the test cases
// to see if we are happier with an approach that doesn't
// force uppercase.
//
String name = semanticAttr->getName();
name = name.toUpper();
m_writer->emit(" : ");
m_writer->emit(name);
if(auto index = semanticAttr->getIndex())
{
m_writer->emit(index);
}
}
}
void CLikeSourceEmitter::emitSemantics(IRInst* inst, bool allowOffsetLayout)
{
emitSemanticsImpl(inst, allowOffsetLayout);
}
void CLikeSourceEmitter::emitLayoutSemantics(IRInst* inst, char const* uniformSemanticSpelling)
{
emitLayoutSemanticsImpl(inst, uniformSemanticSpelling);
}
void CLikeSourceEmitter::emitRegion(Region* inRegion)
{
// We will use a loop so that we can process sequential (simple)
// regions iteratively rather than recursively.
// This is effectively an emulation of tail recursion.
Region* region = inRegion;
while(region)
{
// What flavor of region are we trying to emit?
switch(region->getFlavor())
{
case Region::Flavor::Simple:
{
// A simple region consists of a basic block followed
// by another region.
//
auto simpleRegion = (SimpleRegion*) region;
// We start by outputting all of the non-terminator
// instructions in the block.
//
auto block = simpleRegion->block;
auto terminator = block->getTerminator();
for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->getNextInst())
{
emitInst(inst);
}
// Next we have to deal with the terminator instruction
// itself. In many cases, the terminator will have been
// turned into a block of its own, but certain cases
// of terminators are simple enough that we just fold
// them into the current block.
//
m_writer->advanceToSourceLocation(terminator->sourceLoc);
switch(terminator->getOp())
{
default:
// Don't do anything with the terminator, and assume
// its behavior has been folded into the next region.
break;
case kIROp_Return:
case kIROp_discard:
// For extremely simple terminators, we just handle
// them here, so that we don't have to allocate
// separate `Region`s for them.
emitInst(terminator);
break;
}
// If the terminator required a full region to represent
// its behavior in a structured form, then we will move
// along to that region now.
//
// We do this iteratively rather than recursively, by
// jumping back to the top of our loop with a new
// value for `region`.
//
region = simpleRegion->nextRegion;
continue;
}
// Break and continue regions are trivial to handle, as long as we
// don't need to consider multi-level break/continue (which we
// don't for now).
case Region::Flavor::Break:
m_writer->emit("break;\n");
break;
case Region::Flavor::Continue:
m_writer->emit("continue;\n");
break;
case Region::Flavor::If:
{
auto ifRegion = (IfRegion*) region;
emitIfDecorationsImpl(ifRegion->ifElseInst);
// TODO: consider simplifying the code in
// the case where `ifRegion == null`
// so that we output `if(!condition) { elseRegion }`
// instead of the current `if(condition) {} else { elseRegion }`
m_writer->emit("if(");
emitOperand(ifRegion->getCondition(), getInfo(EmitOp::General));
m_writer->emit(")\n{\n");
m_writer->indent();
emitRegion(ifRegion->thenRegion);
m_writer->dedent();
m_writer->emit("}\n");
// Don't emit the `else` region if it would be empty
//
if(auto elseRegion = ifRegion->elseRegion)
{
m_writer->emit("else\n{\n");
m_writer->indent();
emitRegion(elseRegion);
m_writer->dedent();
m_writer->emit("}\n");
}
// Continue with the region after the `if`.
//
// TODO: consider just constructing a `SimpleRegion`
// around an `IfRegion` to handle this sequencing,
// rather than making `IfRegion` serve as both a
// conditional and a sequence.
//
region = ifRegion->nextRegion;
continue;
}
break;
case Region::Flavor::Loop:
{
auto loopRegion = (LoopRegion*) region;
auto loopInst = loopRegion->loopInst;
// If the user applied an explicit decoration to the loop,
// to control its unrolling behavior, then pass that
// along in the output code (if the target language
// supports the semantics of the decoration).
//
if (auto loopControlDecoration = loopInst->findDecoration<IRLoopControlDecoration>())
{
emitLoopControlDecorationImpl(loopControlDecoration);
}
m_writer->emit("for(;;)\n{\n");
m_writer->indent();
emitRegion(loopRegion->body);
m_writer->dedent();
m_writer->emit("}\n");
// Continue with the region after the loop
region = loopRegion->nextRegion;
continue;
}
case Region::Flavor::Switch:
{
auto switchRegion = (SwitchRegion*) region;
emitSwitchDecorationsImpl(switchRegion->switchInst);
// Emit the start of our statement.
m_writer->emit("switch(");
emitOperand(switchRegion->getCondition(), getInfo(EmitOp::General));
m_writer->emit(")\n{\n");
auto defaultCase = switchRegion->defaultCase;
for(auto currentCase : switchRegion->cases)
{
for(auto caseVal : currentCase->values)
{
m_writer->emit("case ");
emitOperand(caseVal, getInfo(EmitOp::General));
m_writer->emit(":\n");
}
if(currentCase.Ptr() == defaultCase)
{
m_writer->emit("default:\n");
}
m_writer->indent();
m_writer->emit("{\n");
m_writer->indent();
emitRegion(currentCase->body);
m_writer->dedent();
m_writer->emit("}\n");
m_writer->dedent();
}
m_writer->emit("}\n");
// Continue with the region after the `switch`
region = switchRegion->nextRegion;
continue;
}
break;
}
break;
}
}
void CLikeSourceEmitter::emitRegionTree(RegionTree* regionTree)
{
emitRegion(regionTree->rootRegion);
}
bool CLikeSourceEmitter::isDefinition(IRFunc* func)
{
// For now, we use a simple approach: a function is
// a definition if it has any blocks, and a declaration otherwise.
return func->getFirstBlock() != nullptr;
}
void CLikeSourceEmitter::emitEntryPointAttributes(IRFunc* irFunc, IREntryPointDecoration* entryPointDecor)
{
emitEntryPointAttributesImpl(irFunc, entryPointDecor);
}
void CLikeSourceEmitter::emitFunctionBody(IRGlobalValueWithCode* code)
{
// Compute a structured region tree that can represent
// the control flow of our function.
//
RefPtr<RegionTree> regionTree = generateRegionTreeForFunc(code, getSink());
// Now that we've computed the region tree, we have
// an opportunity to perform some last-minute transformations
// on the code to make sure it follows our rules.
//
// TODO: it would be better to do these transformations earlier,
// so that we can, e.g., dump the final IR code *before* emission
// starts, but that gets a bit complicated because we also want
// to have the region tree available without having to recompute it.
//
// For now we are just going to do things the expedient way, but
// eventually we should allow an IR module to have side-band
// storage for derived structures like the region tree (and logic
// for invalidating them when a transformation would break them).
//
fixValueScoping(regionTree, [this](IRInst* inst) {return shouldFoldInstIntoUseSites(inst); });
// Now emit high-level code from that structured region tree.
//
emitRegionTree(regionTree);
}
void CLikeSourceEmitter::emitSimpleFuncParamImpl(IRParam* param)
{
auto paramName = getName(param);
auto paramType = param->getDataType();
if(auto layoutDecoration = param->findDecoration<IRLayoutDecoration>() )
{
auto layout = as<IRVarLayout>(layoutDecoration->getLayout());
SLANG_ASSERT(layout);
if(layout->usesResourceKind(LayoutResourceKind::VaryingInput)
|| layout->usesResourceKind(LayoutResourceKind::VaryingOutput))
{
emitInterpolationModifiers(param, paramType, layout);
emitMeshShaderModifiers(param);
}
}
emitParamType(paramType, paramName);
emitSemantics(param);
}
void CLikeSourceEmitter::emitSimpleFuncParamsImpl(IRFunc* func)
{
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(")");
}
void CLikeSourceEmitter::emitSimpleFuncImpl(IRFunc* func)
{
auto resultType = func->getResultType();
// Deal with decorations that need
// to be emitted as attributes
if ( IREntryPointDecoration* entryPointDecor = func->findDecoration<IREntryPointDecoration>())
{
emitEntryPointAttributes(func, entryPointDecor);
}
// Deal with required features/capabilities of the function
//
handleRequiredCapabilitiesImpl(func);
emitFunctionPreambleImpl(func);
auto name = getName(func);
emitFuncDecorations(func);
emitType(resultType, name);
emitSimpleFuncParamsImpl(func);
emitSemantics(func);
// TODO: encode declaration vs. definition
if(isDefinition(func))
{
m_writer->emit("\n{\n");
m_writer->indent();
// 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 CLikeSourceEmitter::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))
{
m_writer->emit("out ");
type = outType->getValueType();
}
else if( auto inOutType = as<IRInOutType>(type))
{
m_writer->emit("inout ");
type = inOutType->getValueType();
}
else if( auto refType = as<IRRefType>(type))
{
// Note: There is no HLSL/GLSL equivalent for by-reference parameters,
// so we don't actually expect to encounter these in user code.
m_writer->emit("inout ");
type = refType->getValueType();
}
else if (auto constRefType = as<IRConstRefType>(type))
{
type = constRefType->getValueType();
}
emitType(type, name);
}
void CLikeSourceEmitter::emitFuncDecl(IRFunc* func)
{
auto name = getName(func);
emitFuncDecl(func, name);
}
void CLikeSourceEmitter::emitFuncDecl(IRFunc* func, const String& name)
{
// We don't want to emit declarations for operations
// that only appear in the IR as stand-ins for built-in
// operations on that target.
if (isTargetIntrinsic(func))
return;
// Finally, don't emit a declaration for an entry point,
// because it might need meta-data attributes attached
// to it, and the HLSL compiler will get upset if the
// forward declaration doesn't *also* have those
// attributes.
if(asEntryPoint(func))
return;
// A function declaration doesn't have any IR basic blocks,
// and as a result it *also* doesn't have the IR `param` instructions,
// so we need to emit a declaration entirely from the type.
auto funcType = func->getDataType();
auto resultType = func->getResultType();
emitFuncDecorations(func);
emitType(resultType, name);
m_writer->emit("(");
auto paramCount = funcType->getParamCount();
for(UInt pp = 0; pp < paramCount; ++pp)
{
if(pp != 0)
m_writer->emit(", ");
String paramName;
paramName.append("_");
paramName.append(Int32(pp));
auto paramType = funcType->getParamType(pp);
emitParamType(paramType, paramName);
}
m_writer->emit(");\n\n");
}
IREntryPointLayout* CLikeSourceEmitter::getEntryPointLayout(IRFunc* func)
{
if( auto layoutDecoration = func->findDecoration<IRLayoutDecoration>() )
{
return as<IREntryPointLayout>(layoutDecoration->getLayout());
}
return nullptr;
}
IREntryPointLayout* CLikeSourceEmitter::asEntryPoint(IRFunc* func)
{
if (auto layoutDecoration = func->findDecoration<IRLayoutDecoration>())
{
if (auto entryPointLayout = as<IREntryPointLayout>(layoutDecoration->getLayout()))
{
return entryPointLayout;
}
}
return nullptr;
}
bool CLikeSourceEmitter::isTargetIntrinsic(IRInst* inst)
{
// A function is a target intrinsic if and only if
// it has a suitable decoration marking it as a
// target intrinsic for the current compilation target.
//
UnownedStringSlice intrinsicDef;
return findTargetIntrinsicDefinition(inst, intrinsicDef);
}
bool shouldWrappInExternCBlock(IRFunc* func)
{
for (auto decor : func->getDecorations())
{
switch (decor->getOp())
{
case kIROp_ExternCDecoration:
case kIROp_CudaKernelDecoration:
return true;
}
}
return false;
}
void CLikeSourceEmitter::emitFunc(IRFunc* func)
{
// Target-intrinsic functions should never be emitted
// even if they happen to have a body.
//
if (isTargetIntrinsic(func))
return;
bool shouldCloseExternCBlock = shouldWrappInExternCBlock(func);
if (shouldCloseExternCBlock)
{
// If this is a C++ `extern "C"` function, then we need to emit
// it as a C function, since that is what the C++ compiler will
// expect.
//
m_writer->emit("extern \"C\" {\n");
}
if(!isDefinition(func))
{
// This is just a function declaration,
// and so we want to emit it as such.
//
emitFuncDecl(func);
}
else
{
// The common case is that what we
// have is just an ordinary function,
// and we can emit it as such.
//
emitSimpleFunc(func);
}
if (shouldCloseExternCBlock)
{
m_writer->emit("}\n");
}
}
void CLikeSourceEmitter::emitFuncDecorationsImpl(IRFunc* func)
{
for(auto decoration : func->getDecorations())
{
emitFuncDecorationImpl(decoration);
}
}
void CLikeSourceEmitter::emitStruct(IRStructType* structType)
{
// If the selected `struct` type is actually an intrinsic
// on our target, then we don't want to emit anything at all.
if(isTargetIntrinsic(structType))
{
return;
}
m_writer->emit("struct ");
emitPostKeywordTypeAttributes(structType);
m_writer->emit(getName(structType));
emitStructDeclarationsBlock(structType, false);
m_writer->emit(";\n\n");
}
void CLikeSourceEmitter::emitStructDeclarationsBlock(IRStructType* structType, bool allowOffsetLayout)
{
m_writer->emit("\n{\n");
m_writer->indent();
for(auto ff : structType->getFields())
{
auto fieldKey = ff->getKey();
auto fieldType = ff->getFieldType();
// Filter out fields with `void` type that might
// have been introduced by legalization.
if(as<IRVoidType>(fieldType))
continue;
// Note: GLSL doesn't support interpolation modifiers on `struct` fields
if( getSourceLanguage() != SourceLanguage::GLSL )
{
emitInterpolationModifiers(fieldKey, fieldType, nullptr);
}
if (allowOffsetLayout)
{
if (auto packOffsetDecoration = fieldKey->findDecoration<IRPackOffsetDecoration>())
{
emitPackOffsetModifier(fieldKey, fieldType, packOffsetDecoration);
}
}
emitType(fieldType, getName(fieldKey));
emitSemantics(fieldKey, allowOffsetLayout);
m_writer->emit(";\n");
}
m_writer->dedent();
m_writer->emit("}");
}
void CLikeSourceEmitter::emitClass(IRClassType* classType)
{
// If the selected `class` type is actually an intrinsic
// on our target, then we don't want to emit anything at all.
if (isTargetIntrinsic(classType))
{
return;
}
List<IRWitnessTable*> comWitnessTables;
for (auto child : classType->getDecorations())
{
if (auto decoration = as<IRCOMWitnessDecoration>(child))
{
comWitnessTables.add(cast<IRWitnessTable>(decoration->getWitnessTable()));
}
}
m_writer->emit("class ");
emitPostKeywordTypeAttributes(classType);
m_writer->emit(getName(classType));
if (comWitnessTables.getCount() == 0)
{
m_writer->emit(" : public RefObject");
}
else
{
m_writer->emit(" : public ComObject");
for (auto wt : comWitnessTables)
{
m_writer->emit(", public ");
m_writer->emit(getName(wt->getConformanceType()));
}
}
m_writer->emit("\n{\n");
m_writer->emit("public:\n");
m_writer->indent();
if (comWitnessTables.getCount())
{
m_writer->emit("SLANG_COM_OBJECT_IUNKNOWN_ALL\n");
m_writer->emit("void* getInterface(const Guid & uuid)\n{\n");
m_writer->indent();
m_writer->emit("if (uuid == ISlangUnknown::getTypeGuid()) return static_cast<ISlangUnknown*>(this);\n");
for (auto wt : comWitnessTables)
{
auto interfaceName = getName(wt->getConformanceType());
m_writer->emit("if (uuid == ");
m_writer->emit(interfaceName);
m_writer->emit("::getTypeGuid())\n");
m_writer->indent();
m_writer->emit("return static_cast<");
m_writer->emit(interfaceName);
m_writer->emit("*>(this);\n");
m_writer->dedent();
}
m_writer->emit("return nullptr;\n");
m_writer->dedent();
m_writer->emit("}\n");
}
for (auto ff : classType->getFields())
{
auto fieldKey = ff->getKey();
auto fieldType = ff->getFieldType();
// Filter out fields with `void` type that might
// have been introduced by legalization.
if (as<IRVoidType>(fieldType))
continue;
emitInterpolationModifiers(fieldKey, fieldType, nullptr);
emitType(fieldType, getName(fieldKey));
emitSemantics(fieldKey);
m_writer->emit(";\n");
}
// Emit COM method declarations.
for (auto wt : comWitnessTables)
{
for (auto wtEntry : wt->getChildren())
{
auto req = as<IRWitnessTableEntry>(wtEntry);
if (!req) continue;
auto func = as<IRFunc>(req->getSatisfyingVal());
if (!func) continue;
m_writer->emit("virtual SLANG_NO_THROW ");
emitType(func->getResultType(), "SLANG_MCALL " + getName(req->getRequirementKey()));
m_writer->emit("(");
auto param = func->getFirstParam();
param = param->getNextParam();
for (; param; param = param->getNextParam())
{
emitParamType(param->getFullType(), getName(param));
}
m_writer->emit(") override;\n");
}
}
m_writer->dedent();
m_writer->emit("};\n\n");
}
void CLikeSourceEmitter::emitInterpolationModifiers(IRInst* varInst, IRType* valueType, IRVarLayout* layout)
{
emitInterpolationModifiersImpl(varInst, valueType, layout);
}
void CLikeSourceEmitter::emitMeshShaderModifiers(IRInst* varInst)
{
emitMeshShaderModifiersImpl(varInst);
}
/// Emit modifiers that should apply even for a declaration of an SSA temporary.
void CLikeSourceEmitter::emitTempModifiers(IRInst* temp)
{
if(temp->findDecoration<IRPreciseDecoration>())
{
m_writer->emit("precise ");
}
}
void CLikeSourceEmitter::emitVarModifiers(IRVarLayout* layout, IRInst* varDecl, IRType* varType)
{
// TODO(JS): We could push all of this onto the target impls, and then not need so many virtual hooks.
emitVarDecorationsImpl(varDecl);
emitTempModifiers(varDecl);
if (!layout)
return;
emitMatrixLayoutModifiersImpl(layout);
// Target specific modifier output
emitImageFormatModifierImpl(varDecl, varType);
if(layout->usesResourceKind(LayoutResourceKind::VaryingInput)
|| layout->usesResourceKind(LayoutResourceKind::VaryingOutput))
{
emitInterpolationModifiers(varDecl, varType, layout);
emitMeshShaderModifiers(varDecl);
}
// Output target specific qualifiers
emitLayoutQualifiersImpl(layout);
}
void CLikeSourceEmitter::emitArrayBrackets(IRType* inType)
{
// A declaration may require zero, one, or
// more array brackets. When writing out array
// brackets from left to right, they represent
// the structure of the type from the "outside"
// in (that is, if we have a 5-element array of
// 3-element arrays we should output `[5][3]`),
// because of C-style declarator rules.
//
// This conveniently means that we can print
// out all the array brackets with a looping
// rather than a recursive structure.
//
// We will peel the input type like an onion,
// looking at one layer at a time until we
// reach a non-array type in the middle.
//
IRType* type = inType;
for(;;)
{
if(auto arrayType = as<IRArrayType>(type))
{
m_writer->emit("[");
emitVal(arrayType->getElementCount(), getInfo(EmitOp::General));
m_writer->emit("]");
// Continue looping on the next layer in.
//
type = arrayType->getElementType();
}
else if(auto unsizedArrayType = as<IRUnsizedArrayType>(type))
{
m_writer->emit("[]");
// Continue looping on the next layer in.
//
type = unsizedArrayType->getElementType();
}
else
{
// This layer wasn't an array, so we are done.
//
return;
}
}
}
void CLikeSourceEmitter::emitParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type)
{
emitParameterGroupImpl(varDecl, type);
}
void CLikeSourceEmitter::emitVar(IRVar* varDecl)
{
auto allocatedType = varDecl->getDataType();
auto varType = allocatedType->getValueType();
// auto addressSpace = allocatedType->getAddressSpace();
#if 0
switch( varType->op )
{
case kIROp_ConstantBufferType:
case kIROp_TextureBufferType:
emitIRParameterGroup(ctx, varDecl, (IRUniformBufferType*) varType);
return;
default:
break;
}
#endif
// Need to emit appropriate modifiers here.
auto layout = getVarLayout(varDecl);
emitVarModifiers(layout, varDecl, varType);
#if 0
switch (addressSpace)
{
default:
break;
case kIRAddressSpace_GroupShared:
emit("groupshared ");
break;
}
#endif
emitRateQualifiersAndAddressSpace(varDecl);
emitType(varType, getName(varDecl));
emitSemantics(varDecl);
emitLayoutSemantics(varDecl);
// TODO: ideally this logic should scan ahead to see if it can find a `store`
// instruction that writes to the `var`, within the same block, such that all
// of the intervening instructions are safe to fold.
//
if (auto store = as<IRStore>(varDecl->getNextInst()))
{
if (store->getPtr() == varDecl)
{
_emitInstAsVarInitializerImpl(store->getVal());
}
}
m_writer->emit(";\n");
}
void CLikeSourceEmitter::_emitInstAsVarInitializerImpl(IRInst* inst)
{
m_writer->emit(" = ");
emitOperand(inst, getInfo(EmitOp::General));
}
bool _isFoldableValue(IRInst* val)
{
if (val->getParent() && val->getParent()->getOp() == kIROp_Module)
return true;
switch (val->getOp())
{
case kIROp_MakeArray:
case kIROp_MakeVector:
case kIROp_MakeMatrix:
case kIROp_MakeStruct:
case kIROp_MakeVectorFromScalar:
case kIROp_MakeArrayFromElement:
case kIROp_MakeMatrixFromScalar:
case kIROp_CastIntToFloat:
case kIROp_CastFloatToInt:
case kIROp_IntCast:
case kIROp_FloatCast:
{
for (UInt i = 0; i < val->getOperandCount(); i++)
if (!_isFoldableValue(val->getOperand(i)))
return false;
return true;
}
default:
return false;
}
}
void CLikeSourceEmitter::emitGlobalVar(IRGlobalVar* varDecl)
{
auto allocatedType = varDecl->getDataType();
auto varType = allocatedType->getValueType();
String initFuncName;
IRInst* initVal = nullptr;
if (auto firstBlock = varDecl->getFirstBlock())
{
// A global variable with code means it has an initializer
// associated with it.
if (auto returnInst = as<IRReturn>(firstBlock->getTerminator()))
{
// If the initializer can be conveniently emitted as an
// expression, we will do that.
if (_isFoldableValue(returnInst->getVal()))
{
initVal = returnInst->getVal();
}
}
if (!initVal)
{
emitFunctionPreambleImpl(varDecl);
// If we can't emit the initializer as an expression,
// we will emit it as a separate function.
//
// TODO: the C language does not allow defining
// functions that return arrays, so if we have an
// array type here, we are going to generate invalid
// code.
initFuncName = getName(varDecl);
initFuncName.append("_init");
m_writer->emit("\n");
emitType(varType, initFuncName);
m_writer->emit("()\n{\n");
m_writer->indent();
emitFunctionBody(varDecl);
m_writer->dedent();
m_writer->emit("}\n");
}
}
// An ordinary global variable won't have a layout
// associated with it, since it is not a shader
// parameter.
//
SLANG_ASSERT(!getVarLayout(varDecl));
IRVarLayout* layout = nullptr;
// An ordinary global variable (which is not a
// shader parameter) may need special
// modifiers to indicate it as such.
//
switch (getSourceLanguage())
{
case SourceLanguage::HLSL:
// HLSL requires the `static` modifier on any
// global variables; otherwise they are assumed
// to be uniforms.
m_writer->emit("static ");
break;
default:
break;
}
emitVarModifiers(layout, varDecl, varType);
emitRateQualifiersAndAddressSpace(varDecl);
emitType(varType, getName(varDecl));
// TODO: These shouldn't be needed for ordinary
// global variables.
//
emitSemantics(varDecl);
emitLayoutSemantics(varDecl);
if (varDecl->getFirstBlock())
{
m_writer->emit(" = ");
if (initVal)
{
emitInstExpr(initVal, EmitOpInfo());
}
else
{
m_writer->emit(initFuncName);
m_writer->emit("()");
}
}
m_writer->emit(";\n\n");
}
void CLikeSourceEmitter::emitGlobalParam(IRGlobalParam* varDecl)
{
auto rawType = varDecl->getDataType();
auto varType = rawType;
if( auto outType = as<IROutTypeBase>(varType) )
{
varType = outType->getValueType();
}
if (as<IRVoidType>(varType))
return;
// When a global shader parameter represents a "parameter group"
// (either a constant buffer or a parameter block with non-resource
// data in it), we will prefer to emit it as an ordinary `cbuffer`
// declaration or `uniform` block, even when emitting HLSL for
// D3D profiles that support the explicit `ConstantBuffer<T>` type.
//
// Alternatively, we could make this choice based on profile, and
// prefer `ConstantBuffer<T>` on profiles that support it and/or when
// the input code used that syntax.
//
if (auto paramBlockType = as<IRUniformParameterGroupType>(varType))
{
emitParameterGroup(varDecl, paramBlockType);
return;
}
// Try target specific ways to emit.
if (tryEmitGlobalParamImpl(varDecl, varType))
{
return;
}
// Need to emit appropriate modifiers here.
// We expect/require all shader parameters to
// have some kind of layout information associated with them.
//
auto layout = getVarLayout(varDecl);
SLANG_ASSERT(layout);
emitVarModifiers(layout, varDecl, varType);
emitRateQualifiersAndAddressSpace(varDecl);
emitType(varType, getName(varDecl));
emitSemantics(varDecl);
emitLayoutSemantics(varDecl);
// A shader parameter cannot have an initializer,
// so we do need to consider emitting one here.
m_writer->emit(";\n\n");
}
void CLikeSourceEmitter::emitGlobalInst(IRInst* inst)
{
emitGlobalInstImpl(inst);
}
static bool _shouldSkipFuncEmit(IRInst* func)
{
// Skip emitting a func if it is a COM interface wrapper implementation and used
// only by the witness table. We will emit this func differently than normal funcs
// and this is handled by `emitComWitnessTable`.
if (func->hasMoreThanOneUse()) return false;
if (func->firstUse)
{
if (auto entry = as<IRWitnessTableEntry>(func->firstUse->getUser()))
{
if (auto table = as<IRWitnessTable>(entry->getParent()))
{
if (auto interfaceType = table->getConformanceType())
{
if (interfaceType->findDecoration<IRComInterfaceDecoration>())
{
return true;
}
}
}
}
}
return false;
}
void CLikeSourceEmitter::emitGlobalInstImpl(IRInst* inst)
{
m_writer->advanceToSourceLocation(inst->sourceLoc);
switch(inst->getOp())
{
case kIROp_GlobalHashedStringLiterals:
/* Don't need to to output anything for this instruction - it's used for reflecting string literals that
are hashed with 'getStringHash' */
break;
case kIROp_InterfaceRequirementEntry:
// Don't emit anything for interface requirement at global level.
// They are handled in `emitInterface`.
break;
case kIROp_Func:
if (!_shouldSkipFuncEmit(inst))
{
emitFunc((IRFunc*) inst);
}
break;
case kIROp_GlobalVar:
emitGlobalVar((IRGlobalVar*) inst);
break;
case kIROp_GlobalParam:
emitGlobalParam((IRGlobalParam*) inst);
break;
case kIROp_Var:
emitVar((IRVar*) inst);
break;
case kIROp_StructType:
emitStruct(cast<IRStructType>(inst));
break;
case kIROp_ClassType:
emitClass(cast<IRClassType>(inst));
break;
case kIROp_InterfaceType:
emitInterface(cast<IRInterfaceType>(inst));
break;
case kIROp_WitnessTable:
emitWitnessTable(cast<IRWitnessTable>(inst));
break;
case kIROp_RTTIObject:
emitRTTIObject(cast<IRRTTIObject>(inst));
break;
default:
// We have an "ordinary" instruction at the global
// scope, and we should therefore emit it using the
// rules for other ordinary instructions.
//
// Such an instruction represents (part of) the value
// for a global constants.
//
emitInst(inst);
break;
}
}
void CLikeSourceEmitter::ensureInstOperand(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel)
{
if(!inst) return;
if(inst->getParent() == ctx->moduleInst)
{
ensureGlobalInst(ctx, inst, requiredLevel);
}
}
void CLikeSourceEmitter::ensureInstOperandsRec(ComputeEmitActionsContext* ctx, IRInst* inst)
{
ensureInstOperand(ctx, inst->getFullType());
UInt operandCount = inst->operandCount;
auto requiredLevel = EmitAction::Definition;
switch (inst->getOp())
{
case kIROp_PtrType:
{
auto ptrType = static_cast<IRPtrType*>(inst);
auto valueType = ptrType->getValueType();
if (ctx->openInsts.contains(valueType))
{
requiredLevel = EmitAction::ForwardDeclaration;
}
else
{
requiredLevel = EmitAction::Definition;
}
break;
}
case kIROp_NativePtrType:
requiredLevel = EmitAction::ForwardDeclaration;
break;
case kIROp_LookupWitness:
case kIROp_FieldExtract:
case kIROp_FieldAddress:
{
auto opType = inst->getOperand(0)->getDataType();
if (auto nativePtrType = as<IRNativePtrType>(opType))
{
ensureInstOperand(ctx, nativePtrType->getValueType(), requiredLevel);
}
break;
}
default:
break;
}
if (auto comWitnessDecoration = as<IRCOMWitnessDecoration>(inst))
{
// A COMWitnessDecoration marks the interface inheritance of a class.
// We need to make sure the implemented interface is emited before the class.
// The witness table itself doesn't matter.
if (auto witnessTable = as<IRWitnessTable>(comWitnessDecoration->getWitnessTable()))
{
ensureInstOperand(ctx, witnessTable->getConformanceType(), requiredLevel);
}
requiredLevel = EmitAction::ForwardDeclaration;
}
for(UInt ii = 0; ii < operandCount; ++ii)
{
// TODO: there are some special cases we can add here,
// to avoid outputting full definitions in cases that
// can get by with forward declarations.
//
// For example, true pointer types should (in principle)
// only need the type they point to to be forward-declared.
// Similarly, a `call` instruction only needs the callee
// to be forward-declared, etc.
ensureInstOperand(ctx, inst->getOperand(ii), requiredLevel);
}
for(auto child : inst->getDecorationsAndChildren())
{
ensureInstOperandsRec(ctx, child);
}
}
void CLikeSourceEmitter::ensureGlobalInst(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel)
{
// Skip certain instructions that don't affect output.
switch(inst->getOp())
{
case kIROp_Generic:
return;
case kIROp_ThisType:
return;
default:
break;
}
if (as<IRBasicType>(inst))
return;
// Certain inst ops will always emit as definition.
switch (inst->getOp())
{
case kIROp_NativePtrType:
// Pointer type will have their value type emited as forward declaration,
// but the pointer type itself should be considered emitted as definition.
requiredLevel = EmitAction::Level::Definition;
break;
default:
break;
}
// Have we already processed this instruction?
EmitAction::Level existingLevel;
if(ctx->mapInstToLevel.tryGetValue(inst, existingLevel))
{
// If we've already emitted it suitably,
// then don't worry about it.
if(existingLevel >= requiredLevel)
return;
}
EmitAction action;
action.level = requiredLevel;
action.inst = inst;
if(requiredLevel == EmitAction::Level::Definition)
{
if(ctx->openInsts.contains(inst))
{
SLANG_UNEXPECTED("circularity during codegen");
return;
}
ctx->openInsts.add(inst);
ensureInstOperandsRec(ctx, inst);
ctx->openInsts.remove(inst);
}
ctx->mapInstToLevel[inst] = requiredLevel;
// Skip instructions that don't correspond to an independent entity in output.
switch (inst->getOp())
{
case kIROp_InterfaceRequirementEntry:
{
return;
}
default:
break;
}
ctx->actions->add(action);
}
void CLikeSourceEmitter::computeEmitActions(IRModule* module, List<EmitAction>& ioActions)
{
ComputeEmitActionsContext ctx;
ctx.moduleInst = module->getModuleInst();
ctx.actions = &ioActions;
ctx.openInsts = InstHashSet(module);
for(auto inst : module->getGlobalInsts())
{
// Emit all resource-typed objects first. This is to avoid an odd scenario in HLSL
// where not using a resource type in a resource definition before the same type
// is used for a function parameter causes HLSL to complain about an 'incomplete type'
//
if ( isResourceType(inst->getDataType()) )
{
ensureGlobalInst(&ctx, inst, EmitAction::Level::Definition);
}
}
for(auto inst : module->getGlobalInsts())
{
if( as<IRType>(inst) )
{
// Don't emit a type unless it is actually used or is marked exported.
if (!inst->findDecoration<IRHLSLExportDecoration>())
continue;
}
// Skip resource types in this pass.
if ( isResourceType(inst->getDataType()) )
continue;
ensureGlobalInst(&ctx, inst, EmitAction::Level::Definition);
}
}
void CLikeSourceEmitter::emitForwardDeclaration(IRInst* inst)
{
switch (inst->getOp())
{
case kIROp_Func:
emitFuncDecl(cast<IRFunc>(inst));
break;
case kIROp_StructType:
m_writer->emit("struct ");
m_writer->emit(getName(inst));
m_writer->emit(";\n");
break;
case kIROp_InterfaceType:
{
if (inst->findDecoration<IRComInterfaceDecoration>())
{
m_writer->emit("struct ");
m_writer->emit(getName(inst));
m_writer->emit(";\n");
}
break;
}
default:
SLANG_UNREACHABLE("emit forward declaration");
}
}
void CLikeSourceEmitter::executeEmitActions(List<EmitAction> const& actions)
{
for(auto action : actions)
{
switch(action.level)
{
case EmitAction::Level::ForwardDeclaration:
emitForwardDeclaration(action.inst);
break;
case EmitAction::Level::Definition:
emitGlobalInst(action.inst);
break;
}
}
}
void CLikeSourceEmitter::emitModuleImpl(IRModule* module, DiagnosticSink* sink)
{
// The IR will usually come in an order that respects
// dependencies between global declarations, but this
// isn't guaranteed, so we need to be careful about
// the order in which we emit things.
SLANG_UNUSED(sink);
List<EmitAction> actions;
computeEmitActions(module, actions);
executeEmitActions(actions);
}
} // namespace Slang