diff options
| author | Tim Foley <tfoley@nvidia.com> | 2017-09-22 08:24:14 -0700 |
|---|---|---|
| committer | Tim Foley <tfoley@nvidia.com> | 2017-09-22 14:18:44 -0700 |
| commit | 1972fa3616af55c223096e9b11465f580530d88f (patch) | |
| tree | 990e9f62c9ece4ec9996866480d6dd469c4f982f | |
| parent | b206af702cbc8cc42c73052ad690d69984ecd7b7 (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.
| -rw-r--r-- | slang.h | 1 | ||||
| -rw-r--r-- | source/slang/core.meta.slang | 8 | ||||
| -rw-r--r-- | source/slang/core.meta.slang.cpp | 8 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 355 | ||||
| -rw-r--r-- | source/slang/ir-inst-defs.h | 74 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 12 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 76 | ||||
| -rw-r--r-- | source/slang/ir.h | 15 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 800 | ||||
| -rw-r--r-- | source/slang/mangle.cpp | 190 | ||||
| -rw-r--r-- | source/slang/mangle.h | 15 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 2 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 2 | ||||
| -rw-r--r-- | tests/ir/loop.slang.expected | 122 |
14 files changed, 1476 insertions, 204 deletions
@@ -1016,6 +1016,7 @@ namespace slang #include "source/slang/emit.cpp" #include "source/slang/ir.cpp" #include "source/slang/lexer.cpp" +#include "source/slang/mangle.cpp" #include "source/slang/name.cpp" #include "source/slang/options.cpp" #include "source/slang/parameter-binding.cpp" 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, ¶meterLists, 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" /> diff --git a/tests/ir/loop.slang.expected b/tests/ir/loop.slang.expected index 2b8bff732..a0f8de432 100644 --- a/tests/ir/loop.slang.expected +++ b/tests/ir/loop.slang.expected @@ -3,76 +3,76 @@ standard error = { } standard output = { let %63 : Ptr<Array<Vec<Float32,4>,64>,1> = var() -let %90 : Ptr<StructuredBuffer<Vec<Float32,4>>,0> = var() -let %268 : Ptr<RWStructuredBuffer<Vec<Float32,4>>,0> = var() +let %96 : Ptr<StructuredBuffer<Vec<Float32,4>>,0> = var() +let %282 : Ptr<RWStructuredBuffer<Vec<Float32,4>>,0> = var() -func %1( - param %7 : UInt32, - param %18 : UInt32, - param %28 : UInt32) +func @_S04mainp3 : (Int32, Int32, Int32) -> Void { -block %4: - let %13 : Ptr<UInt32,0> = var() - store(%13, %7) - let %23 : Ptr<UInt32,0> = var() - store(%23, %18) - let %33 : Ptr<UInt32,0> = var() - store(%33, %28) - let %64 : UInt32 = load(%23) +block %14( param %15 : Int32, + param %24 : Int32, + param %32 : Int32) +: + let %21 : Ptr<Int32,0> = var() + store(%21, %15) + let %29 : Ptr<Int32,0> = var() + store(%29, %24) + let %37 : Ptr<Int32,0> = var() + store(%37, %32) + let %64 : Int32 = load(%29) let %69 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, %64) - let %91 : StructuredBuffer<Vec<Float32,4>> = load(%90) - let %94 : UInt32 = load(%13) - let %95 : Vec<Float32,4> = bufferLoad(%91, %94) - store(%69, %95) - let %104 : Ptr<UInt32,0> = var() - let %112 : UInt32 = construct(1) - store(%104, %112) - loop(%117, %123, %126) + let %97 : StructuredBuffer<Vec<Float32,4>> = load(%96) + let %100 : Int32 = load(%21) + let %101 : Vec<Float32,4> = bufferLoad(%97, %100) + store(%69, %101) + let %110 : Ptr<Int32,0> = var() + let %118 : Int32 = construct(1) + store(%110, %118) + loop(%123, %129, %132) -block %117: - let %133 : UInt32 = load(%104) - let %142 : UInt32 = construct(64) - let %143 : Bool = cmpLT(%133, %142) - loopTest(%143, %120, %123) +block %123: + let %139 : Int32 = load(%110) + let %148 : Int32 = construct(64) + let %149 : Bool = cmpLT(%139, %148) + loopTest(%149, %126, %129) -block %120: +block %126: GroupMemoryBarrierWithGroupSync() - let %166 : UInt32 = load(%23) - let %171 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, %166) - let %176 : Ptr<Vec<Float32,4>,0> = var() - let %177 : Vec<Float32,4> = load(%171) - store(%176, %177) - let %196 : UInt32 = load(%23) - let %199 : UInt32 = load(%104) - let %200 : UInt32 = sub(%196, %199) - let %205 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, %200) - let %206 : Vec<Float32,4> = load(%205) - let %207 : Vec<Float32,4> = load(%176) - let %208 : Vec<Float32,4> = add(%207, %206) - store(%176, %208) - let %211 : Vec<Float32,4> = load(%176) - store(%171, %211) - unconditionalBranch(%126) + let %174 : Int32 = load(%29) + let %179 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, %174) + let %184 : Ptr<Vec<Float32,4>,0> = var() + let %185 : Vec<Float32,4> = load(%179) + store(%184, %185) + let %204 : Int32 = load(%29) + let %207 : Int32 = load(%110) + let %208 : Int32 = sub(%204, %207) + let %213 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, %208) + let %214 : Vec<Float32,4> = load(%213) + let %215 : Vec<Float32,4> = load(%184) + let %216 : Vec<Float32,4> = add(%215, %214) + store(%184, %216) + let %219 : Vec<Float32,4> = load(%184) + store(%179, %219) + unconditionalBranch(%132) -block %126: - let %224 : Ptr<UInt32,0> = var() - let %225 : UInt32 = load(%104) - store(%224, %225) - let %236 : UInt32 = construct(1) - let %237 : UInt32 = load(%224) - let %238 : UInt32 = shl(%237, %236) - store(%224, %238) - let %241 : UInt32 = load(%224) - store(%104, %241) - unconditionalBranch(%117) +block %132: + let %232 : Ptr<Int32,0> = var() + let %233 : Int32 = load(%110) + store(%232, %233) + let %244 : Int32 = construct(1) + let %245 : Int32 = load(%232) + let %246 : Int32 = shl(%245, %244) + store(%232, %246) + let %249 : Int32 = load(%232) + store(%110, %249) + unconditionalBranch(%123) -block %123: +block %129: GroupMemoryBarrierWithGroupSync() - let %269 : RWStructuredBuffer<Vec<Float32,4>> = load(%268) - let %272 : UInt32 = load(%13) - let %286 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, 0) - let %287 : Vec<Float32,4> = load(%286) - bufferStore(%269, %272, %287) + let %283 : RWStructuredBuffer<Vec<Float32,4>> = load(%282) + let %286 : Int32 = load(%21) + let %300 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, 0) + let %301 : Vec<Float32,4> = load(%300) + bufferStore(%283, %286, %301) return_void() } } |
