summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorTim Foley <tfoley@nvidia.com>2017-09-22 08:24:14 -0700
committerTim Foley <tfoley@nvidia.com>2017-09-22 14:18:44 -0700
commit1972fa3616af55c223096e9b11465f580530d88f (patch)
tree990e9f62c9ece4ec9996866480d6dd469c4f982f /source
parentb206af702cbc8cc42c73052ad690d69984ecd7b7 (diff)
More work on IR-based lowering and cross-compilation
None of these changes are made "live" at the moment. I'm just trying to get them checked in to avoid divering too far from `master` at any point during development. - Add basic emit logic to produce GLSL from the IR in a few cases (the existing IR emit logic was ad hoc and HLSL-specific) - When lowering a function declaration, walk up its chain of parent declarations to collect additional parameters as needed - When lowering a call, make sure to add generic arguments that come from the declaration reference being called - Attach a "mangled name" to symbols when lowering, so that we can eventually use that name to resolve things for linkage. - After the above work, I had to apply some fixups to make sure that generic arguments *don't* get added when the user is calling an `__intrinsic_op` function, since those should map 1-to-1 down to instructions with just their ordinary parameter list. A big open question right now is whether I should continue to represent the generic arguments as just part of the ordinary argument list for a function, or split them out into separate `applyGeneric` and `apply` steps. A strongly related question is whether a declaration with generic parameters should lower into a single declaration, or one declaration nested inside an outer generic declaration. A good future step at this point would be to eliminate a lot of the `__intrinsic_op` stuff in favor of having the builtin functions include their own definitions, which might be in terms of a new expression-level construct for writing inline IR operations. This can't be done until the existing AST-to-AST path is no longer needed for cross-compilation purposes. More immediate next steps here: - We need a way to round-trip calls to external declaration that get handled by this mangled-name logic. Basically, if we are asked to output HLSL and we see a call to `_S...GetDimensions...(float4, t, a, ...)` we need to be able to walk the mangled name and get back to `t.getDimensions(a, ...)` without a whole lot of manual definitions to make things round-trip. - In the other case, where a declaration isn't built-in for the chosen target, we need to be able to load a module of target-specific definitions (which will somehow map back to symbols with certain mangled names) and then look these up (by mangled name) and then load/link/inline them into the user's IR to satisfy requirements in their code.
Diffstat (limited to 'source')
-rw-r--r--source/slang/core.meta.slang8
-rw-r--r--source/slang/core.meta.slang.cpp8
-rw-r--r--source/slang/emit.cpp355
-rw-r--r--source/slang/ir-inst-defs.h74
-rw-r--r--source/slang/ir-insts.h12
-rw-r--r--source/slang/ir.cpp76
-rw-r--r--source/slang/ir.h15
-rw-r--r--source/slang/lower-to-ir.cpp800
-rw-r--r--source/slang/mangle.cpp190
-rw-r--r--source/slang/mangle.h15
-rw-r--r--source/slang/slang.vcxproj2
-rw-r--r--source/slang/slang.vcxproj.filters2
12 files changed, 1414 insertions, 143 deletions
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang
index fc2d27b08..d7f306096 100644
--- a/source/slang/core.meta.slang
+++ b/source/slang/core.meta.slang
@@ -94,6 +94,7 @@ sb << " typedef T Element;\n";
// Declare initializer taking a single scalar of the elemnt type
sb << " __implicit_conversion(" << kConversionCost_ScalarToVector << ")\n";
+sb << " __intrinsic_op(" << kIROp_constructorVectorFromScalar << ")\n";
sb << " __init(T value);\n";
sb << "};\n";
@@ -424,7 +425,12 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt)
sb << ")\")\n";
- sb << "__intrinsic_op\n";
+
+
+ // TIM: Making `GetDimensions` *not* be marked as
+ // an intrinsic, just so we can see how defining
+ // things as `extern` functions would work.
+// sb << "__intrinsic_op\n";
}
diff --git a/source/slang/core.meta.slang.cpp b/source/slang/core.meta.slang.cpp
index 8395f11f5..cf2052d3c 100644
--- a/source/slang/core.meta.slang.cpp
+++ b/source/slang/core.meta.slang.cpp
@@ -95,6 +95,7 @@ sb << " typedef T Element;\n";
// Declare initializer taking a single scalar of the elemnt type
sb << " __implicit_conversion(" << kConversionCost_ScalarToVector << ")\n";
+sb << " __intrinsic_op(" << kIROp_constructVectorFromScalar << ")\n";
sb << " __init(T value);\n";
sb << "};\n";
@@ -426,7 +427,12 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt)
sb << ")\")\n";
- sb << "__intrinsic_op\n";
+
+
+ // TIM: Making `GetDimensions` *not* be marked as
+ // an intrinsic, just so we can see how defining
+ // things as `extern` functions would work.
+// sb << "__intrinsic_op\n";
}
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index 6231b4ca1..26b3b925b 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -4070,12 +4070,26 @@ emitDeclImpl(decl, nullptr);
{
auto textureType = (IRTextureType*) type;
- // TODO: actually look at the flavor and emit the right name
- emit("Texture2D");
+ switch (context->shared->target)
+ {
+ case CodeGenTarget::HLSL:
+ // TODO: actually look at the flavor and emit the right name
+ emit("Texture2D");
+ emit("<");
+ emitIRType(context, textureType->getElementType(), nullptr);
+ emit(">");
+ break;
- emit("<");
- emitIRType(context, textureType->getElementType(), nullptr);
- emit(">");
+ case CodeGenTarget::GLSL:
+ // TODO: actually look at the flavor and emit the right name
+ // TODO: look at element type to emit the right prefix
+ emit("texture2D");
+ break;
+
+ default:
+ SLANG_UNEXPECTED("codegen target");
+ break;
+ }
}
break;
@@ -4122,6 +4136,16 @@ emitDeclImpl(decl, nullptr);
}
break;
+ case kIROp_TypeType:
+ {
+ // Note: this should actually be an error case, since
+ // type-level operands shouldn't be exposed in generated
+ // code, but I'm allowing this now to make the output
+ // a bit more clear.
+ emit("Type");
+ }
+ break;
+
default:
SLANG_UNIMPLEMENTED_X("type case for emit");
@@ -4130,15 +4154,70 @@ emitDeclImpl(decl, nullptr);
}
+ CodeGenTarget getTarget(EmitContext* context)
+ {
+ return context->shared->target;
+ }
+
+ void emitGLSLTypePrefix(
+ EmitContext* context,
+ IRType* type)
+ {
+ switch(type->op)
+ {
+ case kIROp_UInt32Type:
+ emit("u");
+ break;
+
+ case kIROp_Int32Type:
+ emit("i");
+ break;
+
+ case kIROp_BoolType:
+ emit("b");
+ break;
+
+ case kIROp_Float32Type:
+ // no prefix
+ break;
+
+ case kIROp_Float64Type:
+ emit("d");
+ break;
+
+ // TODO: we should handle vector and matrix types here
+ // and recurse into them to find the elemnt type,
+ // just as a convenience.
+
+ default:
+ SLANG_UNEXPECTED("case for GLSL type prefix");
+ break;
+ }
+ }
void emitIRVectorType(
EmitContext* context,
IRVectorType* type)
{
- // TODO: this is a GLSL-vs-HLSL decision point
+ switch(getTarget(context))
+ {
+ case CodeGenTarget::GLSL:
+ // HLSL style: `<elementTypePrefix>vec<elementCount>`
+ // e.g., `ivec4`
+ //
+ emitGLSLTypePrefix(context, type->getElementType());
+ emit("vec");
+ emitIRSimpleValue(context, type->getElementCount());
+ break;
- emitIRSimpleType(context, type->getElementType());
- emitIRSimpleValue(context, type->getElementCount());
+ default:
+ // HLSL style: `<elementTypeName><elementCount>`
+ // e.g., `int4`
+ //
+ emitIRSimpleType(context, type->getElementType());
+ emitIRSimpleValue(context, type->getElementCount());
+ break;
+ }
}
void emitIRMatrixType(
@@ -4238,6 +4317,14 @@ emitDeclImpl(decl, nullptr);
// types.
return true;
+ case kIROp_TextureType:
+ // 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(getTarget(context) == CodeGenTarget::GLSL)
+ return true;
+ break;
+
default:
break;
}
@@ -4331,7 +4418,7 @@ emitDeclImpl(decl, nullptr);
case kIROp_Construct:
// Simple constructor call
- if( inst->getArgCount() == 2 )
+ if( inst->getArgCount() == 2 && getTarget(context) == CodeGenTarget::HLSL)
{
// Need to emit as cast for HLSL
emit("(");
@@ -4346,6 +4433,23 @@ emitDeclImpl(decl, nullptr);
}
break;
+ case kIROp_constructVectorFromScalar:
+ // Simple constructor call
+ if( getTarget(context) == CodeGenTarget::HLSL )
+ {
+ emit("(");
+ emitIRType(context, inst->getType());
+ emit(")");
+ }
+ else
+ {
+ emitIRType(context, inst->getType());
+ }
+ emit("(");
+ emitIROperand(context, inst->getArg(1));
+ emit(")");
+ break;
+
case kIROp_FieldExtract:
{
// Extract field from aggregate
@@ -4421,6 +4525,7 @@ emitDeclImpl(decl, nullptr);
break;
case kIROp_Sample:
+ // argument 0 is the instruction's type
emitIROperand(context, inst->getArg(1));
emit(".Sample(");
emitIROperand(context, inst->getArg(2));
@@ -4430,6 +4535,7 @@ emitDeclImpl(decl, nullptr);
break;
case kIROp_SampleGrad:
+ // argument 0 is the instruction's type
emitIROperand(context, inst->getArg(1));
emit(".SampleGrad(");
emitIROperand(context, inst->getArg(2));
@@ -4892,7 +4998,15 @@ emitDeclImpl(decl, nullptr);
}
}
- void emitIRFunc(
+ // Is an IR function a definition? (otherwise it is a declaration)
+ bool 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 emitIRSimpleFunc(
EmitContext* context,
IRFunc* func)
{
@@ -4934,8 +5048,7 @@ emitDeclImpl(decl, nullptr);
emitIRSemantics(context, func);
// TODO: encode declaration vs. definition
- bool isDefinition = true;
- if(isDefinition)
+ if(isDefinition(func))
{
emit("\n{\n");
@@ -4943,25 +5056,194 @@ emitDeclImpl(decl, nullptr);
emitIRStmtsForBlocks(context, func->getFirstBlock(), nullptr);
-#if 0
- for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
+ emit("}\n");
+ }
+ else
+ {
+ emit(";\n");
+ }
+ }
+
+ void emitIRFuncDecl(
+ EmitContext* context,
+ IRFunc* func)
+ {
+ // 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->getType();
+ auto resultType = func->getResultType();
+
+ auto name = getName(func);
+
+ emitIRType(context, resultType, name);
+
+ emit("(");
+ auto paramCount = funcType->getParamCount();
+ for(UInt pp = 0; pp < paramCount; ++pp)
+ {
+ if(pp != 0)
+ emit(", ");
+
+ String paramName;
+ paramName.append("_");
+ paramName.append(pp);
+ auto paramType = funcType->getParamType(pp);
+
+ // An `out` or `inout` parameter will have been
+ // encoded as a parameter of pointer type, so
+ // we need to decode that here.
+ //
+ if( paramType->op == kIROp_PtrType )
{
- // TODO: need to handle control flow and so forth...
- for( auto ii = bb->firstChild; ii; ii = ii->nextInst )
- {
- emitIRInst(context, ii);
- }
+ auto ptrType = (IRPtrType*) paramType;
+
+ // TODO: we need a way to distinguish `out`
+ // from `inout`. The easiest way to do
+ // that might be to have each be a distinct
+ // sub-case of `IRPtrType` - this would also
+ // ensure that they can be distinguished from
+ // real pointers when the user means to use
+ // them.
+
+ emit("out ");
+
+ paramType = ptrType->getValueType();
}
-#endif
- emit("}\n");
+ emitIRType(context, paramType, paramName);
}
- else
+ emit(");\n");
+ }
+
+ EntryPointLayout* getEntryPointLayout(
+ EmitContext* context,
+ IRFunc* func)
+ {
+ if( auto layoutDecoration = func->findDecoration<IRLayoutDecoration>() )
{
+ return dynamic_cast<EntryPointLayout*>(layoutDecoration->layout);
+ }
+ return nullptr;
+ }
+
+ void emitGLSLEntryPointFunc(
+ EmitContext* context,
+ IRFunc* func)
+ {
+ auto funcType = func->getType();
+ auto resultType = func->getResultType();
+
+ auto entryPointLayout = getEntryPointLayout(context, func);
+ assert(entryPointLayout);
+
+ // TODO: need to deal with decorations on the entry point
+ // that should be turned into global-scope `layout` qualifiers.
+
+ // TODO: emit kernel inputs and outputs to globals.
+
+ // Emit a global `out` declaration to hold the output from our shader
+ // kernel.
+ //
+ // TODO: need to generate unique names beter than this
+ //
+ // TODO: need to handle the case where the output is
+ // a structure (should that be fixed up at the IR level,
+ // or here?).
+ // Best option might be to translate the entry-point
+ // result parameter into an `out` parameter, so that
+ // we can handle those uniformly.
+ //
+ String resultName = getName(func) + "_result";
+ emitGLSLLayoutQualifiers(entryPointLayout->resultLayout);
+ emit("out ");
+ emitIRType(context, resultType, resultName);
+ emit(";\n");
+
+ // Emit global `in` and/or `out` declarations for the
+ // parameters of our shader kernel.
+ //
+ // TODO: We need to make sure these names don't collide with anything.
+ //
+ // TODO: We need to handle scalarization here.
+ //
+ auto firstParam = func->getFirstParam();
+ for( auto pp = firstParam; pp; pp = pp->getNextParam() )
+ {
+ // TODO: actually handle `out` parameters here.
+
+ auto paramLayout = getVarLayout(context, pp);
+ auto paramName = getName(pp);
+ emitGLSLLayoutQualifiers(paramLayout);
+ emit("in ");
+ emitIRType(context, pp->getType(), paramName);
emit(";\n");
}
+
+ // Now that we've emitted our parameter declarations,
+ // we can start to emit the body of the entry point:
+ //
+ emit("void main()\n{\n");
+
+ // We had better not be trying to output an entry
+ // point from a declaration rather than a definition.
+ assert(isDefinition(func));
+
+ // At the most basic, we just want to emit the operations in
+ // the entry point function directly, but with the small catch
+ // that if there was a `return` statement in there somewhere,
+ // we need to turn that into a write to our output variable.
+ //
+ // TODO: yeah, that should get cleared up at the IR level...
+ //
+ emitIRStmtsForBlocks(context, func->getFirstBlock(), nullptr);
+
+ emit("}\n");
+ }
+
+ bool isEntryPoint(IRFunc* func)
+ {
+ if(func->findDecoration<IREntryPointDecoration>())
+ return true;
+
+ return false;
+ }
+
+ void emitIRFunc(
+ EmitContext* context,
+ IRFunc* func)
+ {
+ if( getTarget(context) == CodeGenTarget::GLSL
+ && isEntryPoint(func) )
+ {
+ // We have a shader entry point, and that
+ // requires a different strategy for source
+ // code generation in GLSL, because the
+ // parameters/result of the entry point
+ // need to be translated into globals.
+ //
+
+ emitGLSLEntryPointFunc(context, func);
+ }
+ else if(!isDefinition(func))
+ {
+ // This is just a function declaration,
+ // and so we want to emit it as such.
+ // (Or maybe not emit it at all).
+ //
+ emitIRFuncDecl(context, func);
+ }
+ else
+ {
+ // The common case is that what we
+ // have is just an ordinary function,
+ // and we can emit it as such.
+ emitIRSimpleFunc(context, func);
+ }
}
+
void emitIRStruct(
EmitContext* context,
IRStructDecl* structType)
@@ -5006,7 +5288,31 @@ emitDeclImpl(decl, nullptr);
emit("row_major ");
break;
}
+ }
+
+ if (context->shared->target == CodeGenTarget::GLSL)
+ {
+ // Layout-related modifiers need to come before the declaration,
+ // so deal with them here.
+ emitGLSLLayoutQualifiers(layout);
+
+ // try to emit an appropriate leading qualifier
+ for (auto rr : layout->resourceInfos)
+ {
+ switch (rr.kind)
+ {
+ case LayoutResourceKind::Uniform:
+ case LayoutResourceKind::ShaderResource:
+ case LayoutResourceKind::DescriptorTableSlot:
+ emit("uniform ");
+ break;
+ default:
+ continue;
+ }
+
+ break;
+ }
}
}
@@ -5290,6 +5596,11 @@ String emitEntryPoint(
// debugging:
// dumpIR(lowered);
+ // TODO: depending on the target we are trying to generate code for,
+ // we may need to apply certain transformations, and we may also
+ // need to link in (and then inline) target-specific implementations
+ // for the library functions that the user called.
+
// TODO: do we want to emit directly from IR, or translate the
// IR back into AST for emission?
diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h
index deaf02d56..60dc353ac 100644
--- a/source/slang/ir-inst-defs.h
+++ b/source/slang/ir-inst-defs.h
@@ -21,10 +21,54 @@ INST(MatrixType, Mat, 3, 0)
INST(arrayType, Array, 2, 0)
INST(BoolType, Bool, 0, 0)
-INST(Float32Type, Float32, 0, 0)
-INST(Int32Type, Int32, 0, 0)
-INST(UInt32Type, UInt32, 0, 0)
+
+INST(Float16Type, Float16, 0, 0)
+INST(Float32Type, Float32, 0, 0)
+INST(Float64Type, Float64, 0, 0)
+
+// Signed integer types.
+// Note that `IntPtr` represents a pointer-sized integer type,
+// and will end up being equivalent to either `Int32` or `Int64`
+// when it comes time to actually generate code.
+//
+INST(Int8Type, Int8, 0, 0)
+INST(Int16Type, Int16, 0, 0)
+INST(Int32Type, Int32, 0, 0)
+INST(IntPtrType, IntPtr, 0, 0)
+INST(Int64Type, Int64, 0, 0)
+
+// Unlike a lot of other IRs, we retain a distinction between
+// signed and unsigned integer types, simply because many of
+// the target languages we need to generate code for also
+// keep this distinction, and it will help us generate variable
+// declarations that will be friendly to debuggers.
+//
+// TODO: We may want to reconsider this choice simply because
+// some targets (e.g., those based on C++) may have undefined
+// behavior around operations on signed integers that are
+// well-defined (two's complement) on unsigned integers. In
+// those cases we either want to default to unsigned integers,
+// and then cast around the few ops that care about the difference,
+// or else we want to keep using the orignal types, but need
+// to cast around any ordinary math operations on signed types.
+//
+INST(UInt8Type, Int8, 0, 0)
+INST(UInt16Type, Int16, 0, 0)
+INST(UInt32Type, Int32, 0, 0)
+INST(UIntPtrType, IntPtr, 0, 0)
+INST(UInt64Type, Int64, 0, 0)
+
+// A user-defined structure declaration at the IR level.
+// Unlike in the AST where there is a distinction between
+// a `StructDecl` and a `DeclRefType` that refers to it,
+// at the IR level the struct declaration and the type
+// are the same IR instruction.
+//
+// This is a parent instruction that holds zero or more
+// `field` instructions.
+//
INST(StructType, Struct, 0, PARENT)
+
INST(FuncType, Func, 0, 0)
INST(PtrType, Ptr, 1, 0)
INST(TextureType, Texture, 2, 0)
@@ -35,6 +79,18 @@ INST(TextureBufferType, TextureBuffer, 1, 0)
INST(structuredBufferType, StructuredBuffer, 1, 0)
INST(readWriteStructuredBufferType, RWStructuredBuffer, 1, 0)
+// A type use to represent an earlier generic parameter in
+// a signature. For example, given an AST declaration like:
+//
+// func Foo<T, U>(int a, T b) -> U;
+//
+// The lowered function type would be something like:
+//
+// T U a b
+// (Type, Type, Int32, GenericParameterType<0>) -> GenericParameterType<1>
+//
+INST(GenericParameterType, GenericParameterType, 1, 0)
+
INST(boolConst, boolConst, 0, 0)
INST(IntLit, integer_constant, 0, 0)
INST(FloatLit, float_constant, 0, 0)
@@ -62,6 +118,18 @@ INST(FieldAddress, get_field_addr, 2, 0)
INST(getElement, getElement, 2, 0)
INST(getElementPtr, getElementPtr, 2, 0)
+// Construct a vector from a scalar
+//
+// %dst = constructVectorFromScalar %T %N %val
+//
+// where
+// - `T` is a `Type`
+// - `N` is a (compile-time) `Int`
+// - `val` is a `T`
+// - dst is a `Vec<T,N>`
+//
+INST(constructVectorFromScalar, constructVectorFromScalar, 3, 0)
+
// A swizzle of a vector:
//
// %dst = swizzle %src %idx0 %idx1 ...
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index 846e87bb6..1e9638ade 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -62,6 +62,13 @@ struct IRLoopControlDecoration : IRDecoration
IRLoopControl mode;
};
+struct IRMangledNameDecoration : IRDecoration
+{
+ enum { kDecorationOp = kIRDecorationOp_MangledName };
+
+ String mangledName;
+};
+
// Types
struct IRVectorType : IRType
@@ -93,6 +100,10 @@ struct IRArrayType : IRType
IRInst* getElementCount() { return elementCount.usedValue; }
};
+struct IRGenericParameterType : IRType
+{
+ IRUse index;
+};
struct IRFuncType : IRType
@@ -387,6 +398,7 @@ struct IRBuilder
IRValue* columnCount);
IRType* getArrayType(IRType* elementType, IRValue* elementCount);
IRType* getArrayType(IRType* elementType);
+ IRType* getGenericParameterType(UInt index);
IRType* getTypeType();
IRType* getVoidType();
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index 2c6395903..3e12ef1a2 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -83,7 +83,14 @@ namespace Slang
auto entryBlock = getFirstBlock();
if(!entryBlock) return nullptr;
- auto firstInst = entryBlock->firstChild;
+ return entryBlock->getFirstParam();
+ }
+
+ // IRBlock
+
+ IRParam* IRBlock::getFirstParam()
+ {
+ auto firstInst = firstChild;
if(!firstInst) return nullptr;
if(firstInst->op != kIROp_Param)
@@ -92,6 +99,7 @@ namespace Slang
return (IRParam*) firstInst;
}
+
// IRParam
IRParam* IRParam::getNextParam()
@@ -781,6 +789,18 @@ namespace Slang
return getArrayType(elementType, nullptr);
}
+ IRType* IRBuilder::getGenericParameterType(UInt index)
+ {
+ auto indexVal = getIntValue(getBaseType(BaseType::Int), index);
+
+ return findOrEmitInst<IRGenericParameterType>(
+ this,
+ kIROp_GenericParameterType,
+ getTypeType(),
+ indexVal);
+
+ }
+
IRType* IRBuilder::getTypeType()
{
@@ -1442,6 +1462,11 @@ namespace Slang
{
dump(context, "<null>");
}
+ else if( auto mangled = inst->findDecoration<IRMangledNameDecoration>() )
+ {
+ dump(context, "@");
+ dump(context, mangled->mangledName.Buffer());
+ }
else if(inst->id)
{
dump(context, "%");
@@ -1526,6 +1551,21 @@ namespace Slang
dumpID(context, type);
break;
+ case kIROp_FuncType:
+ {
+ auto funcType = (IRFuncType*) type;
+ UInt paramCount = funcType->getParamCount();
+ dump(context, "(");
+ for( UInt pp = 0; pp < paramCount; ++pp )
+ {
+ if(pp != 0) dump(context, ", ");
+ dumpType(context, funcType->getParamType(pp));
+ }
+ dump(context, ") -> ");
+ dumpType(context, funcType->getResultType());
+ }
+ break;
+
default:
{
dump(context, opInfo.name);
@@ -1619,20 +1659,8 @@ namespace Slang
dumpIndent(context);
dump(context, "func ");
dumpID(context, func);
- dump(context, "(\n");
- context->indent++;
- for (auto pp = func->getFirstParam(); pp; pp = pp->getNextParam())
- {
- if (pp != func->getFirstParam())
- dump(context, ",\n");
-
- dumpIndent(context);
- dump(context, "param ");
- dumpID(context, pp);
- dumpInstTypeClause(context, pp->getType());
- }
- context->indent--;
- dump(context, ")\n");
+ dumpInstTypeClause(context, func->getType());
+ dump(context, "\n");
dumpIndent(context);
dump(context, "{\n");
@@ -1665,6 +1693,24 @@ namespace Slang
context->indent--;
dump(context, "block ");
dumpID(context, block);
+
+ if( block->getFirstParam() )
+ {
+ dump(context, "(");
+ context->indent++;
+ for (auto pp = block->getFirstParam(); pp; pp = pp->getNextParam())
+ {
+ if (pp != block->getFirstParam())
+ dump(context, ",\n");
+
+ dumpIndent(context);
+ dump(context, "param ");
+ dumpID(context, pp);
+ dumpInstTypeClause(context, pp->getType());
+ }
+ context->indent--;
+ dump(context, ")\n");
+ }
dump(context, ":\n");
context->indent++;
diff --git a/source/slang/ir.h b/source/slang/ir.h
index bc73ffb66..a557e22f5 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -148,6 +148,7 @@ enum IRDecorationOp : uint16_t
kIRDecorationOp_EntryPoint,
kIRDecorationOp_ComputeThreadGroupSize,
kIRDecorationOp_LoopControl,
+ kIRDecorationOp_MangledName,
};
// A "decoration" that gets applied to an instruction.
@@ -269,6 +270,13 @@ struct IRParentInst : IRInst
IRInst* lastChild;
};
+// A function parameter is represented by an instruction
+// in the entry block of a function.
+struct IRParam : IRInst
+{
+ IRParam* getNextParam();
+};
+
// A basic block is a parent instruction that adds the constraint
// that all the children need to be "ordinary" instructions (so
// no function declarations, or nested blocks). We also expect
@@ -284,13 +292,8 @@ struct IRBlock : IRParentInst
IRBlock* getNextBlock() { return (IRBlock*) nextInst; }
IRFunc* getParent() { return (IRFunc*)parent; }
-};
-// A function parameter is represented by an instruction
-// in the entry block of a function.
-struct IRParam : IRInst
-{
- IRParam* getNextParam();
+ IRParam* getFirstParam();
};
struct IRFuncType;
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp
index 6f64a2215..464d5d50a 100644
--- a/source/slang/lower-to-ir.cpp
+++ b/source/slang/lower-to-ir.cpp
@@ -5,6 +5,7 @@
#include "ir.h"
#include "ir-insts.h"
+#include "mangle.h"
#include "type-layout.h"
#include "visitor.h"
@@ -80,6 +81,7 @@ struct BoundSubscriptInfo : ExtendedValueInfo
DeclRef<SubscriptDecl> declRef;
IRType* type;
List<IRValue*> args;
+ UInt genericArgCount;
};
// Some cases of `ExtendedValueInfo` need to
@@ -288,6 +290,11 @@ struct IRGenContext
SharedIRGenContext* shared;
IRBuilder* irBuilder;
+
+ Session* getSession()
+ {
+ return shared->entryPoint->compileRequest->mSession;
+ }
};
LoweredValInfo ensureDecl(
@@ -430,7 +437,9 @@ LoweredValInfo emitCallToDeclRef(
IRType* type,
DeclRef<Decl> funcDeclRef,
UInt argCount,
- IRValue* const* args)
+ IRValue* const* args,
+ // How many of the arguments are generic args?
+ UInt genericArgCount)
{
auto builder = context->irBuilder;
@@ -473,6 +482,7 @@ LoweredValInfo emitCallToDeclRef(
boundSubscript->declRef = subscriptDeclRef;
boundSubscript->type = type;
boundSubscript->args.AddRange(args, argCount);
+ boundSubscript->genericArgCount = genericArgCount;
context->shared->extValues.Add(boundSubscript);
@@ -488,6 +498,12 @@ LoweredValInfo emitCallToDeclRef(
auto funcDecl = funcDeclRef.getDecl();
if(auto intrinsicOpModifier = funcDecl->FindModifier<IntrinsicOpModifier>())
{
+ // We don't want to include generic arguments when calling an
+ // intrinsic for now, because we assume that they already
+ // handle the parameterization internally.
+ args += genericArgCount;
+ argCount -= genericArgCount;
+
auto op = getIntrinsicOp(funcDecl, intrinsicOpModifier);
if (Int(op) < 0)
@@ -544,6 +560,14 @@ LoweredValInfo emitCallToDeclRef(
// HACK: we know all constructors are builtins for now,
// so we need to emit them as a call to the corresponding
// builtin operation.
+ //
+ // TODO: these should all either be intrinsic operations,
+ // or calls to library functions.
+
+ // Skip the generic arguments, so they don't screw up
+ // other logic.
+ args += genericArgCount;
+ argCount -= genericArgCount;
return LoweredValInfo::simple(builder->emitConstructorInst(type, argCount, args));
}
@@ -556,9 +580,10 @@ LoweredValInfo emitCallToDeclRef(
IRGenContext* context,
IRType* type,
DeclRef<Decl> funcDeclRef,
- List<IRValue*> const& args)
+ List<IRValue*> const& args,
+ UInt genericArgCount)
{
- return emitCallToDeclRef(context, type, funcDeclRef, args.Count(), args.Buffer());
+ return emitCallToDeclRef(context, type, funcDeclRef, args.Count(), args.Buffer(), genericArgCount);
}
IRValue* getSimpleVal(IRGenContext* context, LoweredValInfo lowered)
@@ -587,7 +612,8 @@ top:
context,
boundSubscriptInfo->type,
getter,
- boundSubscriptInfo->args);
+ boundSubscriptInfo->args,
+ boundSubscriptInfo->genericArgCount);
goto top;
}
@@ -1096,6 +1122,53 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
}
}
+ // In the context of a call, we need to add the generic
+ // arguments to the call to the list of IR-level arguments
+ // we are going to pass through. We do that by looking
+ // at the specializations attached to the declaration
+ // reference itself.
+ //
+ void addGenericArgs(
+ InvokeExpr* expr,
+ DeclRef<Decl> declRef,
+ List<IRValue*>* ioArgs)
+ {
+ auto subst = declRef.substitutions;
+ if(!subst)
+ return;
+
+ auto genericDecl = subst->genericDecl;
+
+ // If there is an outer layer of generic arguments
+ // (e.g., we are calling a generic method nested
+ // inside a generic type) then we need to add
+ // the outer arguments *before* those from the
+ // inner context.
+ //
+ DeclRef<Decl> outerDeclRef;
+ outerDeclRef.decl = genericDecl;
+ outerDeclRef.substitutions = subst->outer;
+ addGenericArgs(expr, outerDeclRef, ioArgs);
+
+ // Okay, now we can process the argument list at this
+ // level.
+ //
+ // TODO: there could be a sticking point here because
+ // this logic needs to agree with the logic that adds
+ // generic *parameters* to the callee, and right now
+ // we have a mismatch that constraint witnesses are
+ // explicit in the parmaeter list, but not in
+ // declaration references as currently constructed
+ // inside the front-end.
+ //
+ for(auto arg : subst->args)
+ {
+ auto loweredVal = lowerVal(context, arg);
+ addArgs(context, ioArgs, loweredVal);
+ }
+ }
+
+
// After a call to a function with `out` or `in out`
// parameters, we may need to copy data back into
// the l-value locations used for output arguments.
@@ -1233,6 +1306,74 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
}
}
+ struct ResolvedCallInfo
+ {
+ DeclRef<Decl> funcDeclRef;
+ Expr* baseExpr = nullptr;
+ };
+
+ // Try to resolve a the function expression for a call
+ // into a reference to a specific declaration, along
+ // with some contextual information about the declaration
+ // we are calling.
+ bool tryResolveDeclRefForCall(
+ RefPtr<Expr> funcExpr,
+ ResolvedCallInfo* outInfo)
+ {
+ // TODO: unwrap any "identity" expressions that might
+ // be wrapping the callee.
+
+ // First look to see if the expression references a
+ // declaration at all.
+ auto declRefExpr = funcExpr.As<DeclRefExpr>();
+ if(!declRefExpr)
+ return false;
+
+ // A little bit of future proofing here: if we ever
+ // allow higher-order functions, then we might be
+ // calling through a variable/field that has a function
+ // type, but is not itself a function.
+ // In such a case we should be careful to not statically
+ // resolve things.
+ //
+ if(auto callableDecl = dynamic_cast<CallableDecl*>(declRefExpr->declRef.getDecl()))
+ {
+ // Okay, the declaration is directly callable, so we can continue.
+ }
+ else
+ {
+ // The callee declaration isn't itself a callable (it must have
+ // a funciton type, though).
+ return false;
+ }
+
+ // Now we can look at the specific kinds of declaration references,
+ // and try to tease them apart.
+ if (auto memberFuncExpr = funcExpr.As<MemberExpr>())
+ {
+ outInfo->funcDeclRef = memberFuncExpr->declRef;
+ outInfo->baseExpr = memberFuncExpr->BaseExpression;
+ return true;
+ }
+ else if (auto staticMemberFuncExpr = funcExpr.As<StaticMemberExpr>())
+ {
+ outInfo->funcDeclRef = staticMemberFuncExpr->declRef;
+ return true;
+ }
+ else if (auto varExpr = funcExpr.As<VarExpr>())
+ {
+ outInfo->funcDeclRef = varExpr->declRef;
+ return true;
+ }
+ else
+ {
+ // Seems to be a case of declaration-reference we don't know about.
+ SLANG_UNEXPECTED("unknown declaration reference kind");
+ return false;
+ }
+ }
+
+
LoweredValInfo visitInvokeExpr(InvokeExpr* expr)
{
auto type = lowerSimpleType(context, expr->type);
@@ -1262,31 +1403,33 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
List<OutArgumentFixup> argFixups;
auto funcExpr = expr->FunctionExpr;
- if (auto memberFuncExpr = funcExpr.As<MemberExpr>())
+ ResolvedCallInfo resolvedInfo;
+ if( tryResolveDeclRefForCall(funcExpr, &resolvedInfo) )
{
- auto loweredBaseVal = lowerRValueExpr(context, memberFuncExpr->BaseExpression);
- addArgs(context, &irArgs, loweredBaseVal);
-
- auto funcDeclRef = memberFuncExpr->declRef;
+ // In this case we know exaclty what declaration we
+ // are going to call, and so we can resolve things
+ // appropriately.
+ auto funcDeclRef = resolvedInfo.funcDeclRef;
+ auto baseExpr = resolvedInfo.baseExpr;
+
+ // First come any generic arguments:
+ addGenericArgs(expr, funcDeclRef, &irArgs);
+ UInt genericArgCount = irArgs.Count();
+
+ // Next comes the `this` argument if we are calling
+ // a member function:
+ if( baseExpr )
+ {
+ auto loweredBaseVal = lowerRValueExpr(context, baseExpr);
+ addArgs(context, &irArgs, loweredBaseVal);
+ }
+ // Then we have the "direct" arguments to the call.
+ // These may include `out` and `inout` arguments that
+ // require "fixup" work on the other side.
+ //
addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
- auto result = emitCallToDeclRef(context, type, funcDeclRef, irArgs);
- applyOutArgumentFixups(argFixups);
- return result;
- }
- else if (auto staticMemberFuncExpr = funcExpr.As<StaticMemberExpr>())
- {
- auto funcDeclRef = staticMemberFuncExpr->declRef;
- addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
- auto result = emitCallToDeclRef(context, type, funcDeclRef, irArgs);
- applyOutArgumentFixups(argFixups);
- return result;
- }
- else if (auto varExpr = funcExpr.As<VarExpr>())
- {
- auto funcDeclRef = varExpr->declRef;
- addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
- auto result = emitCallToDeclRef(context, type, funcDeclRef, irArgs);
+ auto result = emitCallToDeclRef(context, type, funcDeclRef, irArgs, genericArgCount);
applyOutArgumentFixups(argFixups);
return result;
}
@@ -1925,7 +2068,8 @@ top:
context,
builder->getVoidType(),
setterDeclRef,
- allArgs);
+ allArgs,
+ subscriptInfo->genericArgCount);
return;
}
@@ -1966,6 +2110,25 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
SLANG_UNIMPLEMENTED_X("decl catch-all");
}
+ LoweredValInfo visitDeclGroup(DeclGroup* declGroup)
+ {
+ // To lowere a group of declarations, we just
+ // lower each one individually.
+ //
+ for (auto decl : declGroup->decls)
+ {
+ // Note: I am directly invoking `dispatch` here,
+ // instead of `ensureDecl` just to try and
+ // make sure that we don't accidentally
+ // emit things to an outer context.
+ //
+ // TODO: make sure that can't happen anyway.
+ dispatch(decl);
+ }
+
+ return LoweredValInfo();
+ }
+
LoweredValInfo visitSubscriptDecl(SubscriptDecl* decl)
{
// A subscript operation may encompass one or more
@@ -2096,98 +2259,451 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
return LoweredValInfo::simple(irStruct);
}
- LoweredValInfo visitFunctionDeclBase(FunctionDeclBase* decl)
+ // Sometimes we need to refer to a declaration the way that it would be specialized
+ // inside the context where it is declared (e.g., with generic parameters filled in
+ // using their archetypes).
+ //
+ RefPtr<Substitutions> createDefaultSubstitutions(Decl* decl)
{
- IRBuilder subBuilderStorage = *getBuilder();
- IRBuilder* subBuilder = &subBuilderStorage;
+ auto dd = decl->ParentDecl;
+ while( dd )
+ {
+ if( auto genericDecl = dynamic_cast<GenericDecl*>(dd) )
+ {
+ auto session = context->getSession();
- // need to create an IR function here
+ RefPtr<Substitutions> subst = new Substitutions();
+ subst->genericDecl = genericDecl;
+ subst->outer = createDefaultSubstitutions(genericDecl);
- IRFunc* irFunc = subBuilder->createFunc();
- subBuilder->parentInst = irFunc;
+ for( auto mm : genericDecl->Members )
+ {
+ if( auto genericTypeParamDecl = mm.As<GenericTypeParamDecl>() )
+ {
+ subst->args.Add(DeclRefType::Create(session, makeDeclRef(genericTypeParamDecl.Ptr())));
+ }
+ else if( auto genericValueParamDecl = mm.As<GenericValueParamDecl>() )
+ {
+ subst->args.Add(new GenericParamIntVal(makeDeclRef(genericValueParamDecl.Ptr())));
+ }
+ }
- IRBlock* entryBlock = subBuilder->emitBlock();
- subBuilder->parentInst = entryBlock;
+ return subst;
+ }
+ dd = dd->ParentDecl;
+ }
+ return nullptr;
+ }
+ DeclRef<Decl> createDefaultSpecializedDeclRefImpl(Decl* decl)
+ {
+ DeclRef<Decl> declRef;
+ declRef.decl = decl;
+ declRef.substitutions = createDefaultSubstitutions(decl);
+ return declRef;
+ }
+ //
+ // The client should actually call the templated wrapper, to preserve type information.
+ template<typename D>
+ DeclRef<D> createDefaultSpecializedDeclRef(D* decl)
+ {
+ return createDefaultSpecializedDeclRefImpl(decl).As<D>();
+ }
- IRGenContext subContextStorage = *context;
- IRGenContext* subContext = &subContextStorage;
- subContext->irBuilder = subBuilder;
- // set up sub context for generating our new function
+ // When lowering something callable (most commonly a function declaration),
+ // we need to construct an appropriate parameter list for the IR function
+ // that folds in any contributions from both the declaration itself *and*
+ // its parent declaration(s).
+ //
+ // For example, given code like:
+ //
+ // struct Foo { int bar(float y) { ... } };
+ //
+ // we need to generate IR-level code something like:
+ //
+ // func Foo_bar(Foo this, float y) -> int;
+ //
+ // that is, the `this` parameter has become explicit.
+ //
+ // The same applies to generic parameters, and these
+ // should apply even if the nested declaration is `static`:
+ //
+ // struct Foo<T> { static int bar(T y) { ... } };
+ //
+ // becomes:
+ //
+ // func Foo_bar<T>(T y) -> int;
+ //
+ // In order to implement this, we are going to do a recursive
+ // walk over a declaration and its parents, collecting separate
+ // lists of ordinary and generic parameters that will need
+ // to be included in the final declaration's parameter list.
+ //
+ // When doing code generation for an ordinary value parameter,
+ // we mostly care about its type, and then also its "direction"
+ // (`in`, `out`, `in out`). We sometimes need acess to the
+ // original declaration so that we can inspect it for meta-data,
+ // but in some cases there is no such declaration (e.g., a `this`
+ // parameter doesn't get an explicit declaration in the AST).
+ // To handle this we break out the relevant data into derived
+ // structures:
+ //
+ enum ParameterDirection
+ {
+ kParameterDirection_In,
+ kParameterDirection_Out,
+ kParameterDirection_InOut,
+ };
+ struct ParameterInfo
+ {
+ Type* type;
+ ParameterDirection direction;
+ VarDeclBase* decl;
+ };
+ //
+ // We need a way to compute the appropriate `ParameterDirection` for a
+ // declared parameter:
+ //
+ ParameterDirection getParameterDirection(VarDeclBase* paramDecl)
+ {
+ if( paramDecl->HasModifier<InOutModifier>() )
+ {
+ // The AST specified `inout`:
+ return kParameterDirection_InOut;
+ }
+ if (paramDecl->HasModifier<OutModifier>())
+ {
+ // We saw an `out` modifier, so now we need
+ // to check if there was a paired `in`.
+ if(paramDecl->HasModifier<InModifier>())
+ return kParameterDirection_InOut;
+ else
+ return kParameterDirection_Out;
+ }
+ else
+ {
+ // No direction modifier, or just `in`:
+ return kParameterDirection_In;
+ }
+ }
+ // We need a way to be able to create a `ParameterInfo` given the declaration
+ // of a parameter:
+ //
+ ParameterInfo getParameterInfo(VarDeclBase* paramDecl)
+ {
+ ParameterInfo info;
+ info.type = paramDecl->getType();
+ info.decl = paramDecl;
+ info.direction = getParameterDirection(paramDecl);
+ return info;
+ }
+ //
+
+ // Here's the declaration for the type to hold the lists:
+ struct ParameterLists
+ {
+ List<ParameterInfo> params;
+ List<Decl*> genericParams;
+ };
+ //
+ // Because there might be a `static` declaration somewhere
+ // along the lines, we need to be careful to prohibit adding
+ // non-generic parameters in some cases.
+ enum ParameterListCollectMode
+ {
+ // Collect everything: ordinary and generic parameters.
+ kParameterListCollectMode_Default,
+
+
+ // Only collect generic parameters.
+ kParameterListCollectMode_Static,
+ };
+ //
+ // We also need to be able to detect whether a declaration is
+ // either explicitly or implicitly treated as `static`:
+ bool isMemberDeclarationEffectivelyStatic(
+ Decl* decl,
+ ContainerDecl* parentDecl)
+ {
+ // Anything explicitly marked `static` counts.
+ //
+ // There is a subtle detail here with a global-scope `static`
+ // variable not really meaning `static` in the same way, but
+ // it doesn't matter because the module shouldn't introduce
+ // any parameters we care about.
+ if(decl->HasModifier<HLSLStaticModifier>())
+ return true;
+
+ // Next we need to deal with cases where a declaration is
+ // effectively `static` even if the language doesn't make
+ // the user say so. Most languages make the default assumption
+ // that nested types are `static` even if they don't say
+ // so (Java is an exception here, perhaps due to some
+ // includence from the Scandanavian OOP tradition).
+ if(dynamic_cast<AggTypeDecl*>(decl))
+ return true;
+
+ // Things nested inside functions may have dependencies
+ // on values from the enclosing scope, but this needs to
+ // be dealt with via "capture" so they are also effectively
+ // `static`
+ if(dynamic_cast<FunctionDeclBase*>(parentDecl))
+ return true;
+
+ return false;
+ }
+ // We also need to be able to detect whether a declaration is
+ // either explicitly or implicitly treated as `static`:
+ ParameterListCollectMode getModeForCollectingParentParameters(
+ Decl* decl,
+ ContainerDecl* parentDecl)
+ {
+ // If we have a `static` parameter, then it is obvious
+ // that we should use the `static` mode
+ if(isMemberDeclarationEffectivelyStatic(decl, parentDecl))
+ return kParameterListCollectMode_Static;
+
+ // Otherwise, let's default to collecting everything
+ return kParameterListCollectMode_Default;
+ }
+ //
+ // When dealing with a member function, we need to be able to add the `this`
+ // parameter for the enclosing type:
+ //
+ void addThisParameter(
+ Type* type,
+ ParameterLists* ioParameterLists)
+ {
+ // For now we make any `this` parameter default to `in`. Eventually
+ // we should add a way for the user to opt in to mutability of `this`
+ // in cases where they really want it.
+ //
+ // Note: an alternative here might be to have the built-in types like
+ // `Texture2D` actually use `class` declarations and say that the
+ // `this` parameter for a class type is always `in`, while `struct`
+ // types can default to `in out`.
+ ParameterDirection direction = kParameterDirection_In;
+
+ ParameterInfo info;
+ info.type = type;
+ info.decl = nullptr;
+ info.direction = direction;
+
+ ioParameterLists->params.Add(info);
+ }
+ void addThisParameter(
+ AggTypeDecl* typeDecl,
+ ParameterLists* ioParameterLists)
+ {
+ // We need to construct an appopriate declaration-reference
+ // for the type declaration we were given. In particular,
+ // we need to specialize it for any generic parameters
+ // that are in scope here.
+ auto declRef = createDefaultSpecializedDeclRef(typeDecl);
+ auto type = DeclRefType::Create(context->getSession(), declRef);
+ addThisParameter(
+ type,
+ ioParameterLists);
+ }
+ //
+ // And here is our function that will do the recursive walk:
+ void collectParameterLists(
+ Decl* decl,
+ ParameterLists* ioParameterLists,
+ ParameterListCollectMode mode)
+ {
+ // The parameters introduced by any "parent" declarations
+ // will need to come first, so we'll deal with that
+ // logic here.
+ if( auto parentDecl = decl->ParentDecl )
+ {
+ // Compute the mode to use when collecting parameters from
+ // the outer declaration. The most important question here
+ // is whether parameters of the outer declaration should
+ // also count as parameters of the inner declaration.
+ ParameterListCollectMode innerMode = getModeForCollectingParentParameters(decl, parentDecl);
+
+ // Don't down-grade our `static`-ness along the chain.
+ if(innerMode < mode)
+ innerMode = mode;
+
+ // Now collect any parameters from the parent declaration itself
+ collectParameterLists(parentDecl, ioParameterLists, innerMode);
+
+ // We also need to consider whether the inner declaration needs to have a `this`
+ // parameter corresponding to the outer declaration.
+ if( innerMode != kParameterListCollectMode_Static )
+ {
+ if( auto aggTypeDecl = dynamic_cast<AggTypeDecl*>(parentDecl) )
+ {
+ addThisParameter(aggTypeDecl, ioParameterLists);
+ }
+ else if( auto extensionDecl = dynamic_cast<ExtensionDecl*>(parentDecl) )
+ {
+ addThisParameter(extensionDecl->targetType, ioParameterLists);
+ }
+ }
+ }
+
+ // Once we've added any parameters based on parent declarations,
+ // we can see if this declaration itself introduces parameters.
+ //
+ if( auto callableDecl = dynamic_cast<CallableDecl*>(decl) )
+ {
+ // Don't collect parameters from the outer scope if
+ // we are in a `static` context.
+ if( mode == kParameterListCollectMode_Default )
+ {
+ for( auto paramDecl : callableDecl->GetParameters() )
+ {
+ ioParameterLists->params.Add(getParameterInfo(paramDecl));
+ }
+ }
+ }
+ else if( auto genericDecl = dynamic_cast<GenericDecl*>(decl) )
+ {
+ for( auto memberDecl : genericDecl->Members )
+ {
+ if( auto genericTypeParamDecl = memberDecl.As<GenericTypeParamDecl>() )
+ {
+ ioParameterLists->genericParams.Add(genericTypeParamDecl);
+ }
+ else if( auto genericValueParamDecl = memberDecl.As<GenericValueParamDecl>() )
+ {
+ ioParameterLists->genericParams.Add(genericValueParamDecl);
+ }
+ else if( auto genericConstraintDel = memberDecl.As<GenericTypeConstraintDecl>() )
+ {
+ // When lowering to the IR we need to reify the constraints on
+ // a generic parameter as concrete parameters of their own.
+ // These parameter will usually be satisfied by passing a "witness"
+ // as the argument to correspond to the parameter.
+ //
+ // TODO: it is possible that all witness parameters should come
+ // after the other generic parameters, and thus should be collected
+ // in a third list.
+ //
+ ioParameterLists->genericParams.Add(genericConstraintDel);
+ }
+ }
+ }
+
+ }
+
+ void trySetMangledName(
+ IRInst* inst,
+ Decl* decl)
+ {
+ // We want to generate a mangled name for the given declaration and attach
+ // it to the instruction.
+ //
+ // TODO: we probably want to start be doing an early-exit in cases
+ // where it doesn't make sense to attach a mangled name (e.g., because
+ // the declaration in question shouldn't have linkage).
+ //
+
+ String mangledName = getMangledName(decl);
+
+ auto decoration = getBuilder()->addDecoration<IRMangledNameDecoration>(inst);
+ decoration->mangledName = mangledName;
+ }
+
+
+ LoweredValInfo visitFunctionDeclBase(FunctionDeclBase* decl)
+ {
+ // Collect the parameter lists we will use for our new function.
+ ParameterLists parameterLists;
+ collectParameterLists(decl, &parameterLists, kParameterListCollectMode_Default);
+
+ // TODO: if there are any generic parameters in the collected list, then
+ // we need to output an IR function with generic parameters (or a generic
+ // with a nested function... the exact representation is still TBD).
- CallableDecl* declForParameters = decl;
+ // In most cases the return type for a declaration can be read off the declaration
+ // itself, but things get a bit more complicated when we have to deal with
+ // accessors for subscript declarations (and eventually for properties).
+ //
+ // We compute a declaration to use for looking up the return type here:
CallableDecl* declForReturnType = decl;
if (auto accessorDecl = dynamic_cast<AccessorDecl*>(decl))
{
+ // We are some kind of accessor, so the parent declaration should
+ // know the correct return type to expose.
+ //
auto parentDecl = accessorDecl->ParentDecl;
if (auto subscriptDecl = dynamic_cast<SubscriptDecl*>(parentDecl))
{
- declForParameters = subscriptDecl;
declForReturnType = subscriptDecl;
}
}
- List<IRType*> paramTypes;
+ IRBuilder subBuilderStorage = *getBuilder();
+ IRBuilder* subBuilder = &subBuilderStorage;
- for( auto paramDecl : declForParameters->GetParameters() )
- {
- IRType* irParamType = lowerSimpleType(context, paramDecl->getType());
+ IRGenContext subContextStorage = *context;
+ IRGenContext* subContext = &subContextStorage;
+ subContext->irBuilder = subBuilder;
- LoweredValInfo paramVal;
+ // need to create an IR function here
- if (paramDecl->HasModifier<OutModifier>()
- || paramDecl->HasModifier<InOutModifier>())
- {
- // The parameter is being used for input/output purposes,
- // so it will lower to an actual parameter with a pointer type.
- //
- // TODO: Is this the best representation we can use?
+ IRFunc* irFunc = subBuilder->createFunc();
+ subBuilder->parentInst = irFunc;
- auto irPtrType = subBuilder->getPtrType(irParamType);
- paramTypes.Add(irPtrType);
+ trySetMangledName(irFunc, decl);
+
+ List<IRType*> paramTypes;
- IRParam* irParamPtr = subBuilder->emitParam(irPtrType);
- subBuilder->addHighLevelDeclDecoration(irParamPtr, paramDecl);
+ // We first need to walk the generic parameters (if any)
+ // because these will influence the declared type of
+ // the function.
+ UInt genericParamCounter = 0;
+ for( auto genericParamDecl : parameterLists.genericParams )
+ {
+ UInt genericParamIndex = genericParamCounter++;
+ if( auto genericTypeParamDecl = dynamic_cast<GenericTypeParamDecl*>(genericParamDecl) )
+ {
+ // In the logical type for the function, a generic
+ // type parameter will be represented as a parameter of type `Type`
- paramVal = LoweredValInfo::ptr(irParamPtr);
+ IRType* irTypeType = context->irBuilder->getTypeType();
+ paramTypes.Add(irTypeType);
- // TODO: We might want to copy the pointed-to value into
- // a temporary at the start of the function, and then copy
- // back out at the end, so that we don't have to worry
- // about things like aliasing in the function body.
- //
- // For now we will just use the storage that was passed
- // in by the caller, knowing that our current lowering
- // at call sites will guarantee a fresh/unique location.
+ // Anywhere else in the parameter type list where this type parameter
+ // is referenced, we'll need to substitute in a reference
+ // to the appropriate generic parameter position.
+
+ IRType* irParameterType = context->irBuilder->getGenericParameterType(genericParamIndex);
+ LoweredValInfo LoweredValInfo = LoweredValInfo::simple(irParameterType);
+ subContext->shared->declValues[makeDeclRef(genericTypeParamDecl)] = LoweredValInfo;
}
else
{
- // Simple case of a by-value input parameter.
- // But note that HLSL allows an input parameter
- // to be used as a local variable inside of a
- // function body, so we need to introduce a temporary
- // and then copy over to it...
- //
- // TODO: we could skip this step if we knew
- // the parameter was marked `const` or similar.
-
- paramTypes.Add(irParamType);
+ // TODO: handle the other cases here.
+ SLANG_UNEXPECTED("generic parameter kind");
+ }
+ }
- IRParam* irParam = subBuilder->emitParam(irParamType);
- subBuilder->addHighLevelDeclDecoration(irParam, paramDecl);
- paramVal = LoweredValInfo::simple(irParam);
+ for( auto paramInfo : parameterLists.params )
+ {
+ IRType* irParamType = lowerSimpleType(context, paramInfo.type);
- auto irLocal = subBuilder->emitVar(irParamType);
- auto localVal = LoweredValInfo::ptr(irLocal);
+ switch( paramInfo.direction )
+ {
+ case kParameterDirection_In:
+ // Simple case of a by-value input parameter.
+ paramTypes.Add(irParamType);
+ break;
- assign(subContext, localVal, paramVal);
+ default:
+ // The parameter is being used for input/output purposes,
+ // so it will lower to an actual parameter with a pointer type.
+ //
+ // TODO: Is this the best representation we can use?
- paramVal = localVal;
+ auto irPtrType = subBuilder->getPtrType(irParamType);
+ paramTypes.Add(irPtrType);
}
-
- DeclRef<ParamDecl> paramDeclRef = makeDeclRef(paramDecl.Ptr());
- subContext->shared->declValues.Add(paramDeclRef, paramVal);
}
auto irResultType = lowerSimpleType(context, declForReturnType->ReturnType);
@@ -2218,20 +2734,111 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
irResultType);
irFunc->type.init(irFunc, irFuncType);
- lowerStmt(subContext, decl->Body);
+ if (!decl->Body)
+ {
+ // This is a function declaration without a body.
+ // In Slang we currently try not to support forward declarations
+ // (although we might have to give in eventually), so the
+ // only case where this arises is for a function that
+ // needs to be imported from another module.
+
+ // TODO: we may need to attach something to the declaration,
+ // so that later passes don't get confused by it not having
+ // a body.
- // We need to carefully add a terminator instruction to the end
- // of the body, in case the user didn't do so.
- if (!isTerminatorInst(subContext->irBuilder->parentInst->lastChild))
+ }
+ else
{
- if (irResultType->op == kIROp_VoidType)
+ // This is a function definition, so we need to actually
+ // construct IR for the body...
+ IRBlock* entryBlock = subBuilder->emitBlock();
+ subBuilder->parentInst = entryBlock;
+
+ UInt paramTypeIndex = 0;
+ for( auto paramInfo : parameterLists.params )
{
- subContext->irBuilder->emitReturn();
+ auto irParamType = paramTypes[paramTypeIndex++];
+
+ LoweredValInfo paramVal;
+
+ switch( paramInfo.direction )
+ {
+ default:
+ {
+ // The parameter is being used for input/output purposes,
+ // so it will lower to an actual parameter with a pointer type.
+ //
+ // TODO: Is this the best representation we can use?
+
+ auto irPtrType = (IRPtrType*)irParamType;
+
+ IRParam* irParamPtr = subBuilder->emitParam(irPtrType);
+ if(auto paramDecl = paramInfo.decl)
+ subBuilder->addHighLevelDeclDecoration(irParamPtr, paramDecl);
+
+ paramVal = LoweredValInfo::ptr(irParamPtr);
+
+ // TODO: We might want to copy the pointed-to value into
+ // a temporary at the start of the function, and then copy
+ // back out at the end, so that we don't have to worry
+ // about things like aliasing in the function body.
+ //
+ // For now we will just use the storage that was passed
+ // in by the caller, knowing that our current lowering
+ // at call sites will guarantee a fresh/unique location.
+ }
+ break;
+
+ case kParameterDirection_In:
+ {
+ // Simple case of a by-value input parameter.
+ // But note that HLSL allows an input parameter
+ // to be used as a local variable inside of a
+ // function body, so we need to introduce a temporary
+ // and then copy over to it...
+ //
+ // TODO: we could skip this step if we knew
+ // the parameter was marked `const` or similar.
+
+ paramTypes.Add(irParamType);
+
+ IRParam* irParam = subBuilder->emitParam(irParamType);
+ if(auto paramDecl = paramInfo.decl)
+ subBuilder->addHighLevelDeclDecoration(irParam, paramDecl);
+ paramVal = LoweredValInfo::simple(irParam);
+
+ auto irLocal = subBuilder->emitVar(irParamType);
+ auto localVal = LoweredValInfo::ptr(irLocal);
+
+ assign(subContext, localVal, paramVal);
+
+ paramVal = localVal;
+ }
+ break;
+ }
+
+ if( auto paramDecl = paramInfo.decl )
+ {
+ DeclRef<VarDeclBase> paramDeclRef = makeDeclRef(paramDecl);
+ subContext->shared->declValues.Add(paramDeclRef, paramVal);
+ }
}
- else
+
+ lowerStmt(subContext, decl->Body);
+
+ // We need to carefully add a terminator instruction to the end
+ // of the body, in case the user didn't do so.
+ if (!isTerminatorInst(subContext->irBuilder->parentInst->lastChild))
{
- SLANG_UNEXPECTED("Needed a return here");
- subContext->irBuilder->emitReturn();
+ if (irResultType->op == kIROp_VoidType)
+ {
+ subContext->irBuilder->emitReturn();
+ }
+ else
+ {
+ SLANG_UNEXPECTED("Needed a return here");
+ subContext->irBuilder->emitReturn();
+ }
}
}
@@ -2341,6 +2948,9 @@ static void lowerEntryPointToIR(
auto entryPointDecoration = builder->addDecoration<IREntryPointDecoration>(irFunc);
entryPointDecoration->profile = profile;
+ // Attach layout information here.
+ builder->addLayoutDecoration(irFunc, entryPointLayout);
+
// Next, we need to start attaching the meta-data that is
// required based on the particular stage we are targetting:
switch (stage)
diff --git a/source/slang/mangle.cpp b/source/slang/mangle.cpp
new file mode 100644
index 000000000..ce36a97b6
--- /dev/null
+++ b/source/slang/mangle.cpp
@@ -0,0 +1,190 @@
+#include "mangle.h"
+
+#include "name.h"
+#include "syntax.h"
+
+namespace Slang
+{
+ struct ManglingContext
+ {
+ StringBuilder sb;
+ };
+
+ void emitRaw(
+ ManglingContext* context,
+ char const* text)
+ {
+ context->sb.append(text);
+ }
+
+ void emit(
+ ManglingContext* context,
+ UInt value)
+ {
+ context->sb.append(value);
+ }
+
+ void emitName(
+ ManglingContext* context,
+ Name* name)
+ {
+ String str = getText(name);
+
+ // If the name consists of only traditional "identifer characters"
+ // (`[a-zA-Z_]`), then we wnat to emit it more or less directly.
+ //
+ // If it contains code points outside that range, we'll need to
+ // do something to encode them. I don't want to deal with
+ // that right now, so I'm going to ignore it.
+
+ // We prefix the string with its byte length, so that
+ // decoding doesn't have to worry about finding a terminator.
+ UInt length = str.Length();
+ emit(context, length);
+ context->sb.append(str);
+ }
+
+ void emitType(
+ ManglingContext* context,
+ Type* type)
+ {
+ // TODO: actually implement this bit...
+ }
+
+ void emitQualifiedName(
+ ManglingContext* context,
+ Decl* decl)
+ {
+ auto parentDecl = decl->ParentDecl;
+ if( parentDecl )
+ {
+ emitQualifiedName(context, parentDecl);
+ }
+
+ // A generic declaration is kind of a pseudo-declaration
+ // as far as the user is concerned; so we don't want
+ // to emit its name.
+ if( auto genericDecl = dynamic_cast<GenericDecl*>(decl) )
+ {
+ return;
+ }
+
+ emitName(context, decl->nameAndLoc.name);
+
+ if( auto parentGenericDecl = dynamic_cast<GenericDecl*>(parentDecl))
+ {
+ emitRaw(context, "g");
+ UInt genericParameterCount = 0;
+ for( auto mm : parentGenericDecl->Members )
+ {
+ if(mm.As<GenericTypeParamDecl>())
+ {
+ genericParameterCount++;
+ }
+ else if(mm.As<GenericValueParamDecl>())
+ {
+ genericParameterCount++;
+ }
+ else if(mm.As<GenericTypeConstraintDecl>())
+ {
+ genericParameterCount++;
+ }
+ else
+ {
+ }
+ }
+
+ emit(context, genericParameterCount);
+ for( auto mm : parentGenericDecl->Members )
+ {
+ if(auto genericTypeParamDecl = mm.As<GenericTypeParamDecl>())
+ {
+ emitRaw(context, "T");
+ }
+ else if(auto genericValueParamDecl = mm.As<GenericValueParamDecl>())
+ {
+ emitRaw(context, "v");
+ emitType(context, genericValueParamDecl->getType());
+ }
+ else if(mm.As<GenericTypeConstraintDecl>())
+ {
+ emitRaw(context, "C");
+ // TODO: actually emit info about the constraint
+ }
+ else
+ {
+ }
+ }
+ }
+
+ // If the declaration has parameters, then we need to emit
+ // those parameters to distinguish it from other declarations
+ // of the same name that might have different parameters.
+ //
+ // We'll also go ahead and emit the result type as well,
+ // just for completeness.
+ //
+ if( auto callableDecl = dynamic_cast<CallableDecl*>(decl) )
+ {
+ emitRaw(context, "p");
+ UInt parameterCount = callableDecl->GetParameters().Count();
+ emit(context, parameterCount);
+ for(auto pp : callableDecl->GetParameters())
+ {
+ emitType(context, pp->getType());
+ }
+
+ emitType(context, callableDecl->ReturnType);
+ }
+ }
+
+ void mangleName(
+ ManglingContext* context,
+ Decl* decl)
+ {
+ // TODO: catch cases where the declaration should
+ // forward to something else? E.g., what if we
+ // are asked to mangle the name of a `typedef`?
+
+ // We will start with a unique prefix to avoid
+ // clashes with user-defined symbols:
+ emitRaw(context, "_S");
+
+ // Next we will add a bit of info to register
+ // the *kind* of declaration we are dealing with.
+ //
+ // Functions will get no prefix, since we assume
+ // they are a common case:
+ if(dynamic_cast<FuncDecl*>(decl))
+ {}
+ // Types will get a `T` prefix:
+ else if(dynamic_cast<AggTypeDecl*>(decl))
+ emitRaw(context, "T");
+ else if(dynamic_cast<TypeDefDecl*>(decl))
+ emitRaw(context, "T");
+ // Variables will get a `V` prefix:
+ //
+ // TODO: probably need to pull constant-buffer
+ // declarations out of this...
+ else if(dynamic_cast<VarDeclBase*>(decl))
+ emitRaw(context, "V");
+ else
+ {
+ // TODO: handle other cases
+ }
+
+ // Now we encode the qualified name of the decl.
+ emitQualifiedName(context, decl);
+ }
+
+
+
+ String getMangledName(Decl* decl)
+ {
+ ManglingContext context;
+
+ mangleName(&context, decl);
+
+ return context.sb.ProduceString();
+ }
+}
diff --git a/source/slang/mangle.h b/source/slang/mangle.h
new file mode 100644
index 000000000..6eea96a19
--- /dev/null
+++ b/source/slang/mangle.h
@@ -0,0 +1,15 @@
+#ifndef SLANG_MANGLE_H_INCLUDED
+#define SLANG_MANGLE_H_INCLUDED
+
+// This file implements the name mangling scheme for the Slang language.
+
+#include "../core/basic.h"
+
+namespace Slang
+{
+ struct Decl;
+
+ String getMangledName(Decl* decl);
+}
+
+#endif \ No newline at end of file
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index c83561b65..072556cb9 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -178,6 +178,7 @@
<ClInclude Include="lexer.h" />
<ClInclude Include="lookup.h" />
<ClInclude Include="lower-to-ir.h" />
+ <ClInclude Include="mangle.h" />
<ClInclude Include="modifier-defs.h" />
<ClInclude Include="name.h" />
<ClInclude Include="object-meta-begin.h" />
@@ -214,6 +215,7 @@
<ClCompile Include="lookup.cpp" />
<ClCompile Include="lower-to-ir.cpp" />
<ClCompile Include="lower.cpp" />
+ <ClCompile Include="mangle.cpp" />
<ClCompile Include="name.cpp" />
<ClCompile Include="options.cpp" />
<ClCompile Include="parameter-binding.cpp" />
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index ac4585082..ba16ac8d8 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -42,6 +42,7 @@
<ClInclude Include="ir-insts.h" />
<ClInclude Include="bytecode.h" />
<ClInclude Include="vm.h" />
+ <ClInclude Include="mangle.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="check.cpp" />
@@ -68,6 +69,7 @@
<ClCompile Include="lower-to-ir.cpp" />
<ClCompile Include="bytecode.cpp" />
<ClCompile Include="vm.cpp" />
+ <ClCompile Include="mangle.cpp" />
</ItemGroup>
<ItemGroup>
<CustomBuild Include="core.meta.slang" />