diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2017-09-14 15:37:05 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-09-14 15:37:05 -0700 |
| commit | 10b62eecd94be53eca4ac2555af860f864966d76 (patch) | |
| tree | 9a140acfda0e3f0755f2c120870c72d5a8f4b232 /source/slang/lower-to-ir.cpp | |
| parent | 8cdfce564546c03c2c1ce179561591276aeb23a8 (diff) | |
IR: handle control flow constructs (#186)
* IR: handle control flow constructs
This change includes a bunch of fixes and additions to the IR path:
- `slang-ir-assembly` is now a valid output target (so we can use it for testing)
- This uses what used to be the IR "dumping" logic, revamped to support much prettier output.
- A future change will need to add back support for less prettified output to use when actually debugging
- IR generation for `for` loops and `if` statements is supported
- HLSL output from the above control flow constructs is implemented
- Revamped the handling of l-values, and in particular work on compound ops like `+=`
- Add basic IR support for `groupshared` variables
- Add basic IR support for storing compute thread-group size
- Output semantics on entry point parameters
- This uses the AST structures to find semantics, so its still needs work
- Pass through loop unroll flags
- This is required to match `fxc` output, at least until we implement
unrolling ourselves.
* Fixup: 64-bit build issues.
* fixup for merge
Diffstat (limited to 'source/slang/lower-to-ir.cpp')
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 1211 |
1 files changed, 1017 insertions, 194 deletions
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 10b4aefca..5ee6d5460 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -2,44 +2,130 @@ #include "lower-to-ir.h" #include "ir.h" +#include "ir-insts.h" #include "type-layout.h" #include "visitor.h" namespace Slang { -struct BoundMemberInfo; +// This file implements lowering of the Slang AST to a simpler SSA +// intermediate representation. +// +// IR is generated in a context (`IRGenContext`), which tracks the current +// location in the IR where code should be emitted (e.g., what basic +// block to add instructions to). Lowering a statement will emit some +// number of instructions to the context, and possibly change the +// insertion point (because of control flow). +// +// When lowering an expression we have a more interesting challenge, for +// two main reasons: +// +// 1. There might be types that are representible in the AST, but which +// we don't want to support natively in the IR. An example is a `struct` +// type with both ordinary and resource-type members; we might want to +// split values with such a type into distinct values during lowering. +// +// 2. We need to handle the difference between l-value and r-value expressions, +// and in particular the fact that HLSL/Slang supports complicated sorts +// of l-values (e.g., `someVector.zxy` is an l-value, even though it can't +// be represented by a single pointer), and also allows l-values to appear +// in multiple contexts (not just the left-hand side of assignment, but +// also as an argument to match an `out` or `in out` parameter). +// +// Our solution to both of these problems is the same. Rather than having +// the lowering of an expression return a single IR-level value (`IRInst*`), +// we have it return a more complex type (`LoweredValInfo`) which can represent +// a wider range of conceptual "values" which might correspond to multiple IR-level +// values, and/or represent a pointer to an l-value rather than the r-value itself. + +// We want to keep the representation of a `LoweringValInfo` relatively light +// - right now it is just a single pointer plus a "tag" to distinguish the cases. +// +// This means that cases that can't fit in a single pointer need a heap allocation +// to store their payload. For simplicity we represent all of these with a class +// hierarchy: +// +struct ExtendedValueInfo : RefObject +{}; -struct SubscriptInfo : RefObject +// This case is used to indicate a value that is a reference +// to an AST-level subscript declaration. +// +struct SubscriptInfo : ExtendedValueInfo { DeclRef<SubscriptDecl> declRef; }; -struct BoundSubscriptInfo : RefObject +// This case is used to indicate a reference to an AST-level +// subscript operation bound to particular arguments. +// +// For example in a case like this: +// +// RWStructuredBuffer<Foo> gBuffer; +// ... gBuffer[someIndex] ... +// +// the expression `gBuffer[someIndex]` will be lowered to +// a value that references `RWStructureBuffer<Foo>::operator[]` +// with arguments `(gBuffer, someIndex)`. +// +// Such a value can be an l-value, and depending on the context +// where it is used, can lower into a call to either the getter +// or setter operations of the subscript. +// +struct BoundSubscriptInfo : ExtendedValueInfo { DeclRef<SubscriptDecl> declRef; IRType* type; List<IRValue*> args; }; +// Some cases of `ExtendedValueInfo` need to +// recursively contain `LoweredValInfo`s, and +// so we forward declare them here and fill +// them in later. +// +struct BoundMemberInfo; +struct SwizzledLValueInfo; + + +// This type is our core representation of lowered values. +// In the simple case, it just wraps an `IRInst*`. +// More complex cases, representing l-values or aggregate +// values are also supported. struct LoweredValInfo { + // Which of the cases of value are we looking at? enum class Flavor { + // No value (akin to a null pointer) None, + + // A simple IR value Simple, + + // An l-value reprsented as an IR + // pointer to the value Ptr, + + // A member declaration bound to a particular `this` value BoundMember, + + // A reference to an AST-level subscript operation Subscript, + + // An AST-level subscript operation bound to a particular + // object and arguments. BoundSubscript, + + // The result of applying swizzling to an l-value + SwizzledLValue, }; union { IRValue* val; - BoundMemberInfo* boundMemberInfo; - SubscriptInfo* subscriptInfo; - BoundSubscriptInfo* boundSubscriptInfo; + ExtendedValueInfo* ext; }; Flavor flavor; @@ -66,33 +152,87 @@ struct LoweredValInfo } static LoweredValInfo boundMember( - LoweredValInfo const& base, - LoweredValInfo const& member); + BoundMemberInfo* boundMemberInfo); + + BoundMemberInfo* getBoundMemberInfo() + { + assert(flavor == Flavor::BoundMember); + return (BoundMemberInfo*)ext; + } static LoweredValInfo subscript( SubscriptInfo* subscriptInfo); + SubscriptInfo* getSubscriptInfo() + { + assert(flavor == Flavor::Subscript); + return (SubscriptInfo*)ext; + } + static LoweredValInfo boundSubscript( BoundSubscriptInfo* boundSubscriptInfo); + + BoundSubscriptInfo* getBoundSubscriptInfo() + { + assert(flavor == Flavor::BoundSubscript); + return (BoundSubscriptInfo*)ext; + } + + static LoweredValInfo swizzledLValue( + SwizzledLValueInfo* extInfo); + + SwizzledLValueInfo* getSwizzledLValueInfo() + { + assert(flavor == Flavor::SwizzledLValue); + return (SwizzledLValueInfo*)ext; + } }; -struct BoundMemberInfo +// Represents some declaration bound to a particular +// object. For example, if we had `obj.f` where `f` +// is a member function, we'd use a `BoundMemberInfo` +// to represnet this. +// +// Note: This case is largely avoided by special-casing +// in the handling of calls (like `obj.f(arg)`), but +// it is being left here as an example of what we might +// need/want to do in the long term. +struct BoundMemberInfo : ExtendedValueInfo { + // The base object LoweredValInfo base; - LoweredValInfo member; + + // The (AST-level) declaration reference. + DeclRef<Decl> declRef; }; -LoweredValInfo LoweredValInfo::boundMember( - LoweredValInfo const& base, - LoweredValInfo const& member) +// Represents the result of a swizzle operation in +// an l-value context. A swizzle without duplicate +// elements is allowed as an l-value, even if the +// element are non-contiguous (`.xz`) or out of +// order (`.zxy`). +// +struct SwizzledLValueInfo : ExtendedValueInfo { - BoundMemberInfo* boundMember = new BoundMemberInfo(); - boundMember->base = base; - boundMember->member = member; + // IR-level The type of the expression. + IRType* type; + + // The base expression (this should be an l-value) + LoweredValInfo base; + + // The number of elements in the swizzle + UInt elementCount; + // THe indices for the elements being swizzled + UInt elementIndices[4]; +}; + +LoweredValInfo LoweredValInfo::boundMember( + BoundMemberInfo* boundMemberInfo) +{ LoweredValInfo info; info.flavor = Flavor::BoundMember; - info.boundMemberInfo = boundMember; + info.ext = boundMemberInfo; return info; } @@ -101,7 +241,7 @@ LoweredValInfo LoweredValInfo::subscript( { LoweredValInfo info; info.flavor = Flavor::Subscript; - info.subscriptInfo = subscriptInfo; + info.ext = subscriptInfo; return info; } @@ -110,10 +250,18 @@ LoweredValInfo LoweredValInfo::boundSubscript( { LoweredValInfo info; info.flavor = Flavor::BoundSubscript; - info.boundSubscriptInfo = boundSubscriptInfo; + info.ext = boundSubscriptInfo; return info; } +LoweredValInfo LoweredValInfo::swizzledLValue( + SwizzledLValueInfo* extInfo) +{ + LoweredValInfo info; + info.flavor = Flavor::SwizzledLValue; + info.ext = extInfo; + return info; +} struct SharedIRGenContext { @@ -123,8 +271,13 @@ struct SharedIRGenContext Dictionary<DeclRef<Decl>, LoweredValInfo> declValues; - // Arrays we keep around strictly for memory-management purposes - List<RefPtr<BoundSubscriptInfo>> boundSubscripts; + // Arrays we keep around strictly for memory-management purposes: + + // Any extended values created during lowering need + // to be cleaned up after the fact. We don't try + // to reference-count these along the way because + // they need to get stored into a `union` inside `LoweredValInfo` + List<RefPtr<ExtendedValueInfo>> extValues; }; @@ -178,6 +331,29 @@ LoweredValInfo emitCallToVal( } } +LoweredValInfo emitCompoundAssignOp( + IRGenContext* context, + IRType* type, + IROp op, + UInt argCount, + IRValue* const* args) +{ + auto builder = context->irBuilder; + + assert(argCount == 2); + auto leftPtr = args[0]; + auto rightVal = args[1]; + + auto leftVal = builder->emitLoad(leftPtr); + + IRInst* innerArgs[] = { leftVal, rightVal }; + auto innerOp = builder->emitIntrinsicInst(type, op, 2, innerArgs); + + builder->emitStore(leftPtr, innerOp); + + return LoweredValInfo::ptr(leftPtr); +} + // Given a `DeclRef` for something callable, along with a bunch of // arguments, emit an appropriate call to it. LoweredValInfo emitCallToDeclRef( @@ -229,7 +405,7 @@ LoweredValInfo emitCallToDeclRef( boundSubscript->type = type; boundSubscript->args.AddRange(args, argCount); - context->shared->boundSubscripts.Add(boundSubscript); + context->shared->extValues.Add(boundSubscript); return LoweredValInfo::boundSubscript(boundSubscript); } @@ -245,6 +421,35 @@ LoweredValInfo emitCallToDeclRef( { auto op = getIntrinsicOp(funcDecl, intrinsicOpModifier); + if (Int(op) < 0) + { + switch (op) + { + case kIRPseudoOp_Pos: + return LoweredValInfo::simple(args[0]); + +#define CASE(COMPOUND, OP) \ + case COMPOUND: return emitCompoundAssignOp(context, type, OP, argCount, args) + + CASE(kIRPseudoOp_AddAssign, kIROp_Add); + CASE(kIRPseudoOp_SubAssign, kIROp_Sub); + CASE(kIRPseudoOp_MulAssign, kIROp_Mul); + CASE(kIRPseudoOp_DivAssign, kIROp_Div); + CASE(kIRPseudoOp_ModAssign, kIROp_Mod); + CASE(kIRPseudoOp_AndAssign, kIROp_BitAnd); + CASE(kIRPseudoOp_OrAssign, kIROp_BitOr); + CASE(kIRPseudoOp_XorAssign, kIROp_BitXor); + CASE(kIRPseudoOp_LshAssign, kIROp_Lsh); + CASE(kIRPseudoOp_RshAssign, kIROp_Rsh); + +#undef CASE + + default: + SLANG_UNIMPLEMENTED_X("IR pseudo-op"); + break; + } + } + return LoweredValInfo::simple(builder->emitIntrinsicInst( type, op, @@ -277,6 +482,8 @@ LoweredValInfo emitCallToDeclRef( IRValue* getSimpleVal(IRGenContext* context, LoweredValInfo lowered) { + auto builder = context->irBuilder; + top: switch(lowered.flavor) { @@ -287,12 +494,11 @@ top: return lowered.val; case LoweredValInfo::Flavor::Ptr: - return context->irBuilder->emitLoad(lowered.val); + return builder->emitLoad(lowered.val); case LoweredValInfo::Flavor::BoundSubscript: { - auto boundSubscriptInfo = lowered.boundSubscriptInfo; - auto builder = context->irBuilder; + auto boundSubscriptInfo = lowered.getBoundSubscriptInfo(); for (auto getter : getMembersOfType<GetterDecl>(boundSubscriptInfo->declRef)) { @@ -309,6 +515,17 @@ top: } break; + case LoweredValInfo::Flavor::SwizzledLValue: + { + auto swizzleInfo = lowered.getSwizzledLValueInfo(); + + return builder->emitSwizzle( + swizzleInfo->type, + getSimpleVal(context, swizzleInfo->base), + swizzleInfo->elementCount, + swizzleInfo->elementIndices); + } + default: SLANG_UNEXPECTED("unhandled value flavor"); return nullptr; @@ -397,8 +614,11 @@ IRType* lowerSimpleType( return getSimpleType(lowered); } +LoweredValInfo lowerLValueExpr( + IRGenContext* context, + Expr* expr); -LoweredValInfo lowerExpr( +LoweredValInfo lowerRValueExpr( IRGenContext* context, Expr* expr); @@ -528,6 +748,38 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower return getBuilder()->getMatrixType(irElementType, irRowCount, irColumnCount); } + + LoweredTypeInfo getArrayType( + LoweredTypeInfo const& loweredElementType, + IRValue* irElementCount) + { + switch (loweredElementType.flavor) + { + case LoweredTypeInfo::Flavor::Simple: + return getBuilder()->getArrayType( + loweredElementType.type, + irElementCount); + break; + + default: + SLANG_UNEXPECTED("array element type"); + break; + } + } + + LoweredTypeInfo visitArrayExpressionType(ArrayExpressionType* type) + { + auto loweredElementType = lowerType(context, type->BaseType); + if (auto elementCount = type->ArrayLength) + { + auto irElementCount = lowerSimpleVal(context, elementCount); + return getArrayType(loweredElementType, irElementCount); + } + else + { + return getArrayType(loweredElementType, nullptr); + } + } }; LoweredValInfo lowerVal( @@ -556,15 +808,79 @@ struct LoweringVisitor , ValVisitor<LoweringVisitor, RefPtr<Val>, RefPtr<Type>> #endif +LoweredValInfo createVar( + IRGenContext* context, + LoweredTypeInfo type, + Decl* decl = nullptr, + Layout* layout = nullptr, + IRAddressSpace addressSpace = kIRAddressSpace_Default) +{ + auto builder = context->irBuilder; + switch( type.flavor ) + { + case LoweredTypeInfo::Flavor::Simple: + { + auto irAlloc = builder->emitVar(getSimpleType(type), addressSpace); + + if (decl) + { + builder->addHighLevelDeclDecoration(irAlloc, decl); + } + + if (layout) + { + builder->addLayoutDecoration(irAlloc, layout); + } + + + return LoweredValInfo::ptr(irAlloc); + } + break; + + default: + SLANG_UNIMPLEMENTED_X("var type"); + return LoweredValInfo(); + } + +} + +void addArgs( + IRGenContext* context, + List<IRValue*>* ioArgs, + LoweredValInfo argInfo) +{ + auto& args = *ioArgs; + switch( argInfo.flavor ) + { + case LoweredValInfo::Flavor::Simple: + case LoweredValInfo::Flavor::Ptr: + case LoweredValInfo::Flavor::SwizzledLValue: + args.Add(getSimpleVal(context, argInfo)); + break; + + default: + SLANG_UNIMPLEMENTED_X("addArgs case"); + break; + } +} // -struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> +template<typename Derived> +struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> { IRGenContext* context; IRBuilder* getBuilder() { return context->irBuilder; } + // Lower an expression that should have the same l-value-ness + // as the visitor itself. + LoweredValInfo lowerSubExpr(Expr* expr) + { + return dispatch(expr); + } + + LoweredValInfo visitVarExpr(VarExpr* expr) { LoweredValInfo info = ensureDecl(context, expr->declRef); @@ -576,6 +892,83 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> SLANG_UNEXPECTED("overloaded expressions should not occur in checked AST"); } + LoweredValInfo visitIndexExpr(IndexExpr* expr) + { + auto type = lowerType(context, expr->type); + auto baseVal = lowerSubExpr(expr->BaseExpression); + auto indexVal = getSimpleVal(context, lowerRValueExpr(context, expr->IndexExpression)); + + return subscriptValue(type, baseVal, indexVal); + } + + LoweredValInfo visitMemberExpr(MemberExpr* expr) + { + auto loweredType = lowerType(context, expr->type); + auto loweredBase = lowerRValueExpr(context, expr->BaseExpression); + + auto declRef = expr->declRef; + if (auto fieldDeclRef = declRef.As<StructField>()) + { + // Okay, easy enough: we have a reference to a field of a struct type... + + auto loweredField = ensureDecl(context, fieldDeclRef); + return extractField(loweredType, loweredBase, loweredField); + } + else if (auto callableDeclRef = declRef.As<CallableDecl>()) + { + RefPtr<BoundMemberInfo> boundMemberInfo = new BoundMemberInfo(); + boundMemberInfo->base = loweredBase; + boundMemberInfo->declRef = callableDeclRef; + return LoweredValInfo::boundMember(boundMemberInfo); + } + + SLANG_UNIMPLEMENTED_X("codegen for subscript expression"); + } + + // We will always lower a dereference expression (`*ptr`) + // as an l-value, since that is the easiest way to handle it. + LoweredValInfo visitDerefExpr(DerefExpr* expr) + { + auto loweredType = lowerType(context, expr->type); + auto loweredBase = lowerRValueExpr(context, expr->base); + + // TODO: handle tupel-type for `base` + + // The type of the lowered base must by some kind of pointer, + // in order for a dereference to make senese, so we just + // need to extract the value type from that pointer here. + // + auto loweredBaseVal = getSimpleVal(context, loweredBase); + auto loweredBaseType = loweredBaseVal->getType(); + switch( loweredBaseType->op ) + { + case kIROp_PtrType: + // TODO: should we enumerate these explicitly? + case kIROp_ConstantBufferType: + case kIROp_TextureBufferType: + // Note that we do *not* perform an actual `load` operation + // here, but rather just use the pointer value to construct + // an appropriate `LoweredValInfo` representing the underlying + // dereference. + // + // This is important so that an expression like `&((*foo).bar)` + // (which is desugared from `&foo->bar`) can be handled; such + // an expression does *not* perform a dereference at runtime, + // and is just a bit of pointer math. + // + return LoweredValInfo::ptr(loweredBaseVal); + + default: + SLANG_UNIMPLEMENTED_X("codegen for deref expression"); + return LoweredValInfo(); + } + } + + LoweredValInfo visitParenExpr(ParenExpr* expr) + { + return lowerSubExpr(expr->base); + } + LoweredValInfo visitInitializerListExpr(InitializerListExpr* expr) { SLANG_UNIMPLEMENTED_X("codegen for initializer list expression"); @@ -605,23 +998,6 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> SLANG_UNIMPLEMENTED_X("codegen for aggregate type constructor expression"); } - void addArgs(List<IRValue*>* ioArgs, LoweredValInfo argInfo) - { - auto& args = *ioArgs; - switch( argInfo.flavor ) - { - case LoweredValInfo::Flavor::Simple: - case LoweredValInfo::Flavor::Ptr: - args.Add(getSimpleVal(context, argInfo)); - break; - - default: - SLANG_UNIMPLEMENTED_X("addArgs case"); - break; - } - } - - // Add arguments that appeared directly in an argument list // to the list of argument values for a call. void addDirectCallArgs( @@ -632,45 +1008,129 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> for( auto arg : expr->Arguments ) { - auto loweredArg = lowerExpr(context, arg); - addArgs(&irArgs, loweredArg); + // TODO: Need to handle case of l-value arguments, + // when they are matched to `out` or `in out` parameters. + auto loweredArg = lowerRValueExpr(context, arg); + addArgs(context, ioArgs, loweredArg); } } - // Try to add "all" the arguments for a call to the argument list, - // including implicit arguments that come from (e.g.,) a member - // expression used to form the call. - void addCallArgs( - InvokeExpr* expr, - List<IRValue*>* ioArgs) - { - auto& irArgs = *ioArgs; + // 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. + // + // During lowering of the argument list, we build + // up a list of these "fixup" assignments that need + // to be performed. + struct OutArgumentFixup + { + LoweredValInfo dst; + LoweredValInfo src; + }; - // TODO: should unwrap any layers of identity expressions around this... - if( auto baseMemberExpr = expr->FunctionExpr.As<MemberExpr>() ) + void addDirectCallArgs( + InvokeExpr* expr, + DeclRef<CallableDecl> funcDeclRef, + List<IRValue*>* ioArgs, + List<OutArgumentFixup>* ioFixups) + { + auto funcDecl = funcDeclRef.getDecl(); + auto& args = expr->Arguments; + UInt argCount = expr->Arguments.Count(); + UInt argIndex = 0; + for (auto paramDeclRef : getMembersOfType<ParamDecl>(funcDeclRef)) { - // This call took the form of a member function call, so - // we need to correctly add the `this` argument as - // an explicit argument. - // - auto loweredBase = lowerExpr(context, baseMemberExpr->BaseExpression); - addArgs(&irArgs, loweredBase); - } + if (argIndex >= argCount) + { + // The remaining parameters must be defaulted... + break; + } - addDirectCallArgs(expr, ioArgs); - } + auto paramDecl = paramDeclRef.getDecl(); + auto paramType = lowerType(context, GetType(paramDeclRef)); + auto argExpr = expr->Arguments[argIndex++]; - LoweredValInfo lowerIntrinsicCall( - InvokeExpr* expr, - IROp intrinsicOp) - { - auto type = lowerSimpleType(context, expr->type); + if (paramDecl->HasModifier<OutModifier>() + || paramDecl->HasModifier<InOutModifier>()) + { + // This is a `out` or `inout` parameter, and so + // the argument must be lowered as an l-value. + + LoweredValInfo loweredArg = lowerLValueExpr(context, argExpr); + + // According to our "calling convention" we need to + // pass a pointer into the callee. + // + // A naive approach would be to just take the address + // of `loweredArg` above and pass it in, but that + // has two issues: + // + // 1. The l-value might not be something that has a single + // well-defined "address" (e.g., `foo.xzy`). + // + // 2. The l-value argument might actually alias some other + // storage that the callee will access (e.g., we are + // passing in a global variable, or two `out` parameters + // are being passed the same location in an array). + // + // In each of these cases, the safe option is to create + // a temporary variable to use for argument-passing, + // and then do copy-in/copy-out around the call. + + LoweredValInfo tempVar = createVar(context, paramType); + + // If the parameter is `in out` or `inout`, then we need + // to ensure that we pass in the original value stored + // in the argument, which we accomplish by assigning + // from the l-value to our temp. + if (paramDecl->HasModifier<InModifier>() + || paramDecl->HasModifier<InOutModifier>()) + { + assign(context, tempVar, loweredArg); + } - List<IRValue*> irArgs; - addCallArgs(expr, &irArgs); - UInt argCount = irArgs.Count(); + // Now we can pass the address of the temporary variable + // to the callee as the actual argument for the `in out` + assert(tempVar.flavor == LoweredValInfo::Flavor::Ptr); + (*ioArgs).Add(tempVar.val); - return LoweredValInfo::simple(getBuilder()->emitIntrinsicInst(type, intrinsicOp, argCount, &irArgs[0])); + // Finally, after the call we will need + // to copy in the other direction: from our + // temp back to the original l-value. + OutArgumentFixup fixup; + fixup.src = tempVar; + fixup.dst = loweredArg; + + (*ioFixups).Add(fixup); + + } + else + { + // This is a pure input parameter, and so we will + // pass it as an r-value. + LoweredValInfo loweredArg = lowerRValueExpr(context, argExpr); + addArgs(context, ioArgs, loweredArg); + } + } + } + + // Add arguments that appeared directly in an argument list + // to the list of argument values for a call. + void addDirectCallArgs( + InvokeExpr* expr, + DeclRef<Decl> funcDeclRef, + List<IRValue*>* ioArgs, + List<OutArgumentFixup>* ioFixups) + { + if (auto callableDeclRef = funcDeclRef.As<CallableDecl>()) + { + addDirectCallArgs(expr, callableDeclRef, ioArgs, ioFixups); + } + else + { + SLANG_UNEXPECTED("shouldn't relaly happen"); + addDirectCallArgs(expr, ioArgs); + } } void addFuncBaseArgs( @@ -684,9 +1144,12 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> } } - LoweredValInfo lowerSimpleCall(InvokeExpr* expr) + void applyOutArgumentFixups(List<OutArgumentFixup> const& fixups) { - + for (auto fixup : fixups) + { + assign(context, fixup.dst, fixup.src); + } } LoweredValInfo visitInvokeExpr(InvokeExpr* expr) @@ -711,40 +1174,60 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> // arguments that will be part of the call. List<IRValue*> irArgs; + // We will also collect "fixup" actions that need + // to be performed after teh call, in order to + // copy the final values for `out` parameters + // back to their arguments. + List<OutArgumentFixup> argFixups; auto funcExpr = expr->FunctionExpr; if (auto memberFuncExpr = funcExpr.As<MemberExpr>()) { - auto loweredBaseVal = lowerExpr(context, memberFuncExpr->BaseExpression); - addArgs(&irArgs, loweredBaseVal); + auto loweredBaseVal = lowerRValueExpr(context, memberFuncExpr->BaseExpression); + addArgs(context, &irArgs, loweredBaseVal); auto funcDeclRef = memberFuncExpr->declRef; - addDirectCallArgs(expr, &irArgs); - return emitCallToDeclRef(context, type, funcDeclRef, irArgs); + 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, &irArgs); - return emitCallToDeclRef(context, type, funcDeclRef, irArgs); + 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, &irArgs); - return emitCallToDeclRef(context, type, funcDeclRef, irArgs); + addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups); + auto result = emitCallToDeclRef(context, type, funcDeclRef, irArgs); + applyOutArgumentFixups(argFixups); + return result; } // The default case is to assume that we just have // an ordinary expression, and can lower it as such. - LoweredValInfo funcVal = lowerExpr(context, expr->FunctionExpr); + LoweredValInfo funcVal = lowerRValueExpr(context, expr->FunctionExpr); // Now we add any direct arguments from the call expression itself. addDirectCallArgs(expr, &irArgs); // Delegate to the logic for invoking a value. - return emitCallToVal(context, type, funcVal, irArgs.Count(), irArgs.Buffer()); + auto result = emitCallToVal(context, type, funcVal, irArgs.Count(), irArgs.Buffer()); + + // TODO: because of the nature of how the `emitCallToVal` case works + // right now, we don't have information on in/out parameters, and + // so we can't collect info to apply fixups. + // + // Once we have a better representation for function types, though, + // this should be fixable. + + return result; } LoweredValInfo subscriptValue( @@ -776,15 +1259,6 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> } - LoweredValInfo visitIndexExpr(IndexExpr* expr) - { - auto type = lowerType(context, expr->type); - auto baseVal = lowerExpr(context, expr->BaseExpression); - auto indexVal = getSimpleVal(context, lowerExpr(context, expr->IndexExpression)); - - return subscriptValue(type, baseVal, indexVal); - } - LoweredValInfo extractField( LoweredTypeInfo fieldType, LoweredValInfo base, @@ -824,70 +1298,6 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> return ensureDecl(context, expr->declRef); } - LoweredValInfo visitMemberExpr(MemberExpr* expr) - { - auto loweredType = lowerType(context, expr->type); - auto loweredBase = lowerExpr(context, expr->BaseExpression); - - auto declRef = expr->declRef; - if (auto fieldDeclRef = declRef.As<StructField>()) - { - // Okay, easy enough: we have a reference to a field of a struct type... - - auto loweredField = ensureDecl(context, fieldDeclRef); - return extractField(loweredType, loweredBase, loweredField); - } - else if (auto callableDeclRef = declRef.As<CallableDecl>()) - { - auto loweredFunc = ensureDecl(context, callableDeclRef); - return LoweredValInfo::boundMember(loweredBase, loweredFunc); - } - - SLANG_UNIMPLEMENTED_X("codegen for subscript expression"); - } - - LoweredValInfo visitSwizzleExpr(SwizzleExpr* expr) - { - SLANG_UNIMPLEMENTED_X("codegen for swizzle expression"); - } - - LoweredValInfo visitDerefExpr(DerefExpr* expr) - { - auto loweredType = lowerType(context, expr->type); - auto loweredBase = lowerExpr(context, expr->base); - - // TODO: handle tupel-type for `base` - - // The type of the lowered base must by some kind of pointer, - // in order for a dereference to make senese, so we just - // need to extract the value type from that pointer here. - // - auto loweredBaseVal = getSimpleVal(context, loweredBase); - auto loweredBaseType = loweredBaseVal->getType(); - switch( loweredBaseType->op ) - { - case kIROp_PtrType: - // TODO: should we enumerate these explicitly? - case kIROp_ConstantBufferType: - case kIROp_TextureBufferType: - // Note that we do *not* perform an actual `load` operation - // here, but rather just use the pointer value to construct - // an appropriate `LoweredValInfo` representing the underlying - // dereference. - // - // This is important so that an expression like `&((*foo).bar)` - // (which is desugared from `&foo->bar`) can be handled; such - // an expression does *not* perform a dereference at runtime, - // and is just a bit of pointer math. - // - return LoweredValInfo::ptr(loweredBaseVal); - - default: - SLANG_UNIMPLEMENTED_X("codegen for deref expression"); - return LoweredValInfo(); - } - } - LoweredValInfo visitTypeCastExpr(TypeCastExpr* expr) { SLANG_UNIMPLEMENTED_X("codegen for type cast expression"); @@ -916,8 +1326,8 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> // and right-hand sides, and then peform an assignment // based on the resulting values. // - auto leftVal = lowerExpr(context, expr->left); - auto rightVal = lowerExpr(context, expr->right); + auto leftVal = lowerLValueExpr(context, expr->left); + auto rightVal = lowerRValueExpr(context, expr->right); assign(context, leftVal, rightVal); // The result value of the assignment expression is @@ -925,18 +1335,80 @@ struct ExprLoweringVisitor : ExprVisitor<ExprLoweringVisitor, LoweredValInfo> // to be an l-value). return leftVal; } +}; - LoweredValInfo visitParenExpr(ParenExpr* expr) +struct LValueExprLoweringVisitor : ExprLoweringVisitorBase<LValueExprLoweringVisitor> +{ + // When visiting a swizzle expression in an l-value context, + // we need to construct a "sizzled l-value." + LoweredValInfo visitSwizzleExpr(SwizzleExpr* expr) { - return lowerExpr(context, expr->base); + auto irType = lowerSimpleType(context, expr->type); + auto loweredBase = lowerRValueExpr(context, expr->base); + + RefPtr<SwizzledLValueInfo> swizzledLValue = new SwizzledLValueInfo(); + swizzledLValue->type = irType; + swizzledLValue->base = loweredBase; + + UInt elementCount = (UInt)expr->elementCount; + swizzledLValue->elementCount = elementCount; + for (UInt ii = 0; ii < elementCount; ++ii) + { + swizzledLValue->elementIndices[ii] = (UInt) expr->elementIndices[ii]; + } + + context->shared->extValues.Add(swizzledLValue); + return LoweredValInfo::swizzledLValue(swizzledLValue); + } + +}; + +struct RValueExprLoweringVisitor : ExprLoweringVisitorBase<RValueExprLoweringVisitor> +{ + // A swizzle in an r-value context can save time by just + // emitting the swizzle instuctions directly. + LoweredValInfo visitSwizzleExpr(SwizzleExpr* expr) + { + auto irType = lowerSimpleType(context, expr->type); + auto irBase = getSimpleVal(context, lowerRValueExpr(context, expr->base)); + + auto builder = getBuilder(); + + auto irIntType = builder->getBaseType(BaseType::Int); + + UInt elementCount = (UInt)expr->elementCount; + IRValue* irElementIndices[4]; + for (UInt ii = 0; ii < elementCount; ++ii) + { + irElementIndices[ii] = builder->getIntValue( + irIntType, + (IRIntegerValue)expr->elementIndices[ii]); + } + + auto irSwizzle = builder->emitSwizzle( + irType, + irBase, + elementCount, + &irElementIndices[0]); + + return LoweredValInfo::simple(irSwizzle); } }; -LoweredValInfo lowerExpr( +LoweredValInfo lowerLValueExpr( + IRGenContext* context, + Expr* expr) +{ + LValueExprLoweringVisitor visitor; + visitor.context = context; + return visitor.dispatch(expr); +} + +LoweredValInfo lowerRValueExpr( IRGenContext* context, Expr* expr) { - ExprLoweringVisitor visitor; + RValueExprLoweringVisitor visitor; visitor.context = context; return visitor.dispatch(expr); } @@ -952,6 +1424,185 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> SLANG_UNIMPLEMENTED_X("stmt catch-all"); } + // Create a basic block in the current function, + // so that it can be used for a label. + IRBlock* createBlock() + { + return getBuilder()->createBlock(); + } + + // Insert a block at the current location (ending + // the previous block with an unconditional jump + // if needed). + void insertBlock(IRBlock* block) + { + auto builder = getBuilder(); + auto parent = builder->parentInst; + + IRBlock* prevBlock = nullptr; + IRFunc* parentFunc = nullptr; + + switch (parent->op) + { + case kIROp_Block: + prevBlock = (IRBlock*)parent; + parentFunc = prevBlock->getParent(); + break; + + default: + SLANG_UNEXPECTED("bad parent kind for block"); + return; + } + + // If the previous block doesn't already have + // a terminator instruction, then be sure to + // emit a branch to the new block. + if (!isTerminatorInst(prevBlock->lastChild)) + { + builder->emitBranch(block); + } + + builder->parentInst = parentFunc; + builder->addInst(block); + builder->parentInst = block; + } + + // Start a new block at the current location. + // This is just the composition of `createBlock` + // and `insertBlock`. + IRBlock* startBlock() + { + auto block = createBlock(); + insertBlock(block); + return block; + } + + void visitIfStmt(IfStmt* stmt) + { + auto builder = getBuilder(); + + auto condExpr = stmt->Predicate; + auto thenStmt = stmt->PositiveStatement; + auto elseStmt = stmt->NegativeStatement; + + auto irCond = getSimpleVal(context, + lowerRValueExpr(context, condExpr)); + + if (elseStmt) + { + auto thenBlock = createBlock(); + auto elseBlock = createBlock(); + auto afterBlock = createBlock(); + + builder->emitIfElse(irCond, thenBlock, elseBlock, afterBlock); + + insertBlock(thenBlock); + lowerStmt(context, thenStmt); + builder->emitBranch(afterBlock); + + insertBlock(elseBlock); + lowerStmt(context, elseStmt); + + insertBlock(afterBlock); + } + else + { + auto thenBlock = createBlock(); + auto afterBlock = createBlock(); + + builder->emitIf(irCond, thenBlock, afterBlock); + + insertBlock(thenBlock); + lowerStmt(context, thenStmt); + + insertBlock(afterBlock); + } + } + + void addLoopDecorations( + IRInst* inst, + Stmt* stmt) + { + for(auto attr : stmt->GetModifiersOfType<HLSLUncheckedAttribute>()) + { + // TODO: We should actually catch these attributes during + // semantic checking, so that they have a strongly-typed + // representation in the AST. + if(getText(attr->getName()) == "unroll") + { + auto decoration = getBuilder()->addDecoration<IRLoopControlDecoration>(inst); + decoration->mode = kIRLoopControl_Unroll; + } + } + } + + void visitForStmt(ForStmt* stmt) + { + auto builder = getBuilder(); + + // The initializer clause for the statement + // can always safetly be emitted to the current block. + if (auto initStmt = stmt->InitialStatement) + { + lowerStmt(context, initStmt); + } + + // We will create blocks for the various places + // we need to jump to inside the control flow, + // including the blocks that will be referenced + // by `continue` or `break` statements. + auto loopHead = createBlock(); + auto bodyLabel = createBlock(); + auto breakLabel = createBlock(); + auto continueLabel = createBlock(); + + // TODO: register `loopHead` as the target for a + // `continue` statement. + + // Emit the branch that will start out loop, + // and then insert the block for the head. + + auto loopInst = builder->emitLoop( + loopHead, + breakLabel, + continueLabel); + + addLoopDecorations(loopInst, stmt); + + insertBlock(loopHead); + + // Now that we are within the header block, we + // want to emit the expression for the loop condition: + if (auto condExpr = stmt->PredicateExpression) + { + auto irCondition = getSimpleVal(context, + lowerRValueExpr(context, stmt->PredicateExpression)); + + // Now we want to `break` if the loop condition is false. + builder->emitLoopTest( + irCondition, + bodyLabel, + breakLabel); + } + + // Emit the body of the loop + insertBlock(bodyLabel); + lowerStmt(context, stmt->Statement); + + // Insert the `continue` block + insertBlock(continueLabel); + if (auto incrExpr = stmt->SideEffectExpression) + { + lowerRValueExpr(context, incrExpr); + } + + // At the end of the body we need to jump back to the top. + builder->emitBranch(loopHead); + + // Finally we insert the label that a `break` will jump to + insertBlock(breakLabel); + } + void visitExpressionStmt(ExpressionStmt* stmt) { // The statement evaluates an expression @@ -960,7 +1611,11 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> // lower the expression, and don't use // the result. // - lowerExpr(context, stmt->Expression); + // Note that we lower using the l-value path, + // so that an expression statement that names + // a location (but doesn't load from it) + // will not actually emit a load. + lowerLValueExpr(context, stmt->Expression); } void visitDeclStmt(DeclStmt* stmt) @@ -1004,7 +1659,7 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> // a value first, and then emit the resulting value. if( auto expr = stmt->Expression ) { - auto loweredExpr = lowerExpr(context, expr); + auto loweredExpr = lowerRValueExpr(context, expr); getBuilder()->emitReturn(getSimpleVal(context, loweredExpr)); } @@ -1026,9 +1681,15 @@ void lowerStmt( void assign( IRGenContext* context, - LoweredValInfo const& left, - LoweredValInfo const& right) + LoweredValInfo const& inLeft, + LoweredValInfo const& inRight) { + LoweredValInfo left = inLeft; + LoweredValInfo right = inRight; + + auto builder = context->irBuilder; + +top: switch (left.flavor) { case LoweredValInfo::Flavor::Ptr: @@ -1036,8 +1697,8 @@ void assign( { case LoweredValInfo::Flavor::Simple: case LoweredValInfo::Flavor::Ptr: + case LoweredValInfo::Flavor::SwizzledLValue: { - auto builder = context->irBuilder; builder->emitStore( left.val, getSimpleVal(context, right)); @@ -1050,6 +1711,90 @@ void assign( } break; + case LoweredValInfo::Flavor::SwizzledLValue: + { + // The `left` value is of the form `<someLValue>.<swizzleElements>`. + // + // We could conceivably define a custom "swizzled store" instruction + // that would handle the common case where the base l-value is + // a simple lvalue (`LowerdValInfo::Flavor::Ptr`): + // + // float4 foo; + // foo.zxy = float3(...); + // + // However, this doesn't handle complex cases like the following: + // + // RWStructureBuffer<float4> foo; + // ... + // foo[index].xzy = float3(...); + // + // In a case like that, we really need to lower through a temp: + // + // float4 tmp = foo[index]; + // tmp.xzy = float3(...); + // foo[index] = tmp; + // + // We want to handle the general case, we we might as well + // try to handle everything uniformly. + // + auto swizzleInfo = left.getSwizzledLValueInfo(); + auto type = swizzleInfo->type; + auto loweredBase = swizzleInfo->base; + + // Load from the base value: + IRInst* irLeftVal = getSimpleVal(context, loweredBase); + auto irRightVal = getSimpleVal(context, right); + + // Now apply the swizzle + IRInst* irSwizzled = builder->emitSwizzleSet( + irLeftVal->getType(), + irLeftVal, + irRightVal, + swizzleInfo->elementCount, + swizzleInfo->elementIndices); + + // And finally, store the value back where we got it. + // + // Note: this is effectively a recursive call to + // `assign()`, so we do a simple tail-recursive call here. + left = loweredBase; + right = LoweredValInfo::simple(irSwizzled); + goto top; + } + break; + + case LoweredValInfo::Flavor::BoundSubscript: + { + // The `left` value refers to a subscript operation on + // a resource type, bound to particular arguments, e.g.: + // `someStructuredBuffer[index]`. + // + // When storing to such a value, we need to emit a call + // to the appropriate builtin "setter" accessor. + auto subscriptInfo = left.getBoundSubscriptInfo(); + auto type = subscriptInfo->type; + + // Search for an appropriate "setter" declaration + for (auto setterDeclRef : getMembersOfType<SetterDecl>(subscriptInfo->declRef)) + { + auto allArgs = subscriptInfo->args; + + addArgs(context, &allArgs, right); + + emitCallToDeclRef( + context, + builder->getVoidType(), + setterDeclRef, + allArgs); + return; + } + + // No setter found? Then we have an error! + SLANG_UNEXPECTED("no setter found"); + break; + } + break; + default: SLANG_UNIMPLEMENTED_X("assignment"); break; @@ -1120,33 +1865,44 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> auto varType = lowerType(context, decl->getType()); - LoweredValInfo varVal; + // TODO: If the variable is marked `static` then we need to + // deal with it specially: we should move its allocation out + // to the global scope, and then we have to deal with its + // initializer expression a bit carefully (it should only + // be initialized on-demand at its first use). + + // Some qualifiers on a variable will change how we allocate it, + // so we need to reflect that somehow. The first example + // we run into is the `groupshared` qualifier, which marks + // a variable in a compute shader as having per-group allocation + // rather than the traditional per-thread (or rather per-thread + // per-activation-record) allocation. + // + // Options include: + // + // - Use a distinct allocation opration, so that the type + // of the variable address/value is unchanged. + // + // - Add a notion of an "address space" to pointer types, + // so that we can allocate things in distinct spaces. + // + // - Add a notion of a "rate" so that we can declare a + // variable with a distinct rate. + // + // For now we might do the expedient thing and handle this + // via a notion of an "address space." - switch( varType.flavor ) + IRAddressSpace addressSpace = kIRAddressSpace_Default; + if (decl->HasModifier<HLSLGroupSharedModifier>()) { - case LoweredTypeInfo::Flavor::Simple: - { - auto irAlloc = getBuilder()->emitVar(getSimpleType(varType)); - - getBuilder()->addHighLevelDeclDecoration(irAlloc, decl); - - if (getLayout()) - { - getBuilder()->addLayoutDecoration(irAlloc, getLayout()); - } - - - varVal = LoweredValInfo::ptr(irAlloc); - } - break; - - default: - SLANG_UNIMPLEMENTED_X("struct field type"); + addressSpace = kIRAddressSpace_GroupShared; } + LoweredValInfo varVal = createVar(context, varType, decl, getLayout(), addressSpace); + if( auto initExpr = decl->initExpr ) { - auto initVal = lowerExpr(context, initExpr); + auto initVal = lowerRValueExpr(context, initExpr); assign(context, varVal, initVal); } @@ -1241,6 +1997,8 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> IRParam* irParam = subBuilder->emitParam(irParamType); + subBuilder->addHighLevelDeclDecoration(irParam, paramDecl); + DeclRef<ParamDecl> paramDeclRef = makeDeclRef(paramDecl.Ptr()); LoweredValInfo irParamVal = LoweredValInfo::simple(irParam); @@ -1278,6 +2036,21 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> 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)) + { + if (irResultType->op == kIROp_VoidType) + { + subContext->irBuilder->emitReturn(); + } + else + { + SLANG_UNEXPECTED("Needed a return here"); + subContext->irBuilder->emitReturn(); + } + } + getBuilder()->addHighLevelDeclDecoration(irFunc, decl); getBuilder()->addInst(irFunc); @@ -1365,11 +2138,48 @@ static void lowerEntryPointToIR( EntryPointRequest* entryPointRequest, EntryPointLayout* entryPointLayout) { - auto entryPointFunc = entryPointLayout->entryPoint; + // First, lower the entry point like an ordinary function + auto entryPointFuncDecl = entryPointLayout->entryPoint; + auto loweredEntryPointFunc = lowerDecl(context, entryPointFuncDecl, entryPointLayout); + auto irFunc = getSimpleVal(context, loweredEntryPointFunc); + + auto builder = context->irBuilder; + + // We are going to attach all the entry-point-specific information + // to the declaration as meta-data decorations for now. + // + // I'm not convinced this is the right way to go, but it is + // the easiest and most expedient thing. + // + auto profile = entryPointRequest->profile; + auto stage = profile.GetStage(); - // TODO: entry point lowering is probably *not* just like lowering a function... + auto entryPointDecoration = builder->addDecoration<IREntryPointDecoration>(irFunc); + entryPointDecoration->profile = profile; - lowerDecl(context, entryPointFunc, entryPointLayout); + // Next, we need to start attaching the meta-data that is + // required based on the particular stage we are targetting: + switch (stage) + { + case Stage::Compute: + { + // We need to attach information about the thread group size here. + auto threadGroupSizeDecoration = builder->addDecoration<IRComputeThreadGroupSizeDecoration>(irFunc); + static const UInt kAxisCount = 3; + + // TODO: this is kind of gross because we are using a public + // reflection API function, rather than some kind of internal + // utility it forwards to... + spReflectionEntryPoint_getComputeThreadGroupSize( + (SlangReflectionEntryPoint*)entryPointLayout, + kAxisCount, + &threadGroupSizeDecoration->sizeAlongAxis[0]); + } + break; + + default: + break; + } } IRModule* lowerEntryPointToIR( @@ -1412,4 +2222,17 @@ IRModule* lowerEntryPointToIR( } +String emitSlangIRAssemblyForEntryPoint( + EntryPointRequest* entryPoint) +{ + auto compileRequest = entryPoint->compileRequest; + auto irModule = lowerEntryPointToIR( + entryPoint, + compileRequest->layout.Ptr(), + // TODO: we need to pick the target more carefully here + CodeGenTarget::HLSL); + + return getSlangIRAssembly(irModule); +} + } |
