diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2020-03-16 09:03:19 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-03-16 09:03:19 -0700 |
| commit | 256a20a163ef6ee93a817472adcb24c076b0c0dc (patch) | |
| tree | 1304dabdbc5e01c68251b251e3654481ead939df /source | |
| parent | c1743a52c814377198ec8ee6a22f4487278c57be (diff) | |
Define compound intrinsic ops in the standard library (#1273)
* Define compound intrinsic ops in the standard library
The current stdlib code has a notion of "compound" intrinsic ops, which use the `__intrinsic_op` modifier but don't actually map to a single IR instruction.
Instead, most* of these map to multiple IR instructions using hard-coded logic in `slang-ir-lower.cpp`.
(* One special case is `kCompoundOp_Pos` that is used for unary `operator+` and that maps to *zero* IR instructions)
All of the opcodes that used to use the `kCompoundOp_` enumeration values now have definitions directly in the stdlib and use the new `[__unsafeForceInlineEarly]` attribute to ensure that they get inlined into their use sites so that the output code is as close as possible to the original.
For the most part, generating the stdlib definitions for the compound ops is straightforward, but here's some notes:
* The unary `operator+` I just defined directly in Slang code, since it doesn't share much structure with other cases
* The unary increment/decrement ops are generated as a cross product of increment/decrement and prefix/postfix. The logic is a bit messy but given that we have scalar, vector, and matrix versions to deal with it still saves code overall
* Because all the compound/assignment cases got moved out, the existing code for generating unary/binary ops can be simplified a bit
* All the no-op bit-cast operations like `asfloat(float)` are now inline identity functions
* A few other small cleanups are made by not having to worry about the compound ops (which used to be called "pseudo ops") sometimes being encoded in to the same type of value as a real IR opcode.
The one big detail here is a fix for how IR lowering works for `let` declarations: they were previously being `materialize()`d which only guarantees that they've been placed in a contiguous and addressable location, but doesn't actually convert them to an r-value. As a result a `let` declaration could accidentally capture a mutable location by reference, which is definitely *not* what we wanted it to do. Fixing this was needed to make the new postfix `++` definition work (several existing tests end up covering this).
One important forward-looking note:
One subtle (but significant) choice in this change is that we actually reduce the number of declarations in the stdlib, because instead of having the compound operators include both per-type and generic overloads (just listing scalar cases for now):
float operator+=(in out float left, float right) { ... }
int operator+=(in out int left, int right) { ... }
...
T operator+= <T:__BuiltinBlahBlah>(in out T left, T right) { ... }
We now have *only* the single generic version:
T operator+= <T:__BuiltinBlahBlah>(in out T left, T right) { ... }
In running our current tests, this change didn't lead to any regressions (perhaps surprisingly).
Given that we were able to reduce the number of overloads for `operator+=` by a factor of N (where N is the number of built-in types), it seems worth considering whether we could also reduce the number of overloads of `operator+` by the same factor by only having generic rather than per-type versions.
One concern that this forward-looking question raises is whether the quality of diagnostic messages around bad calls to the operators might suffer when there are only generic overloads instead of per-type overloads. In order to feel out this problem I added a test case that includes some bad operator calls both to `+=` (which is now only generic with this change) and `+` (which still has per-type overloads). Overall, I found the quality of the error messages (in terms of the candidates that get listed) isn't perfect for either, but personally I prefer the output in the generic case.
As part of adding that test, I also added some fixups to how overload resolution messages get printed, to make sure the function name is printed in more cases, and also that the candidates print more consistently. These changes affected the expected output for one other diagnostic test.
* fixup: disable bad operator test on non-Windows targets
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/core.meta.slang | 201 | ||||
| -rw-r--r-- | source/slang/hlsl.meta.slang | 45 | ||||
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 10 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 1 | ||||
| -rw-r--r-- | source/slang/slang-check-overload.cpp | 33 | ||||
| -rw-r--r-- | source/slang/slang-compound-intrinsics.h | 80 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-expr-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 197 | ||||
| -rw-r--r-- | source/slang/slang-mangle.cpp | 8 | ||||
| -rw-r--r-- | source/slang/slang-modifier-defs.h | 12 | ||||
| -rw-r--r-- | source/slang/slang-stdlib.cpp | 25 | ||||
| -rw-r--r-- | source/slang/slang-syntax.h | 1 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 1 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 3 |
15 files changed, 263 insertions, 358 deletions
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index f73178322..821905375 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -1455,11 +1455,8 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) } -for (auto op : unaryOps) +for (auto op : intrinsicUnaryOps) { - char const* fixity = (op.flags & POSTFIX) != 0 ? "__postfix " : "__prefix "; - char const* qual = (op.flags & ASSIGNMENT) != 0 ? "in out " : ""; - for (auto type : kBaseTypes) { if ((type.flags & op.flags) == 0) @@ -1469,18 +1466,15 @@ for (auto op : unaryOps) if (op.flags & BOOL_RESULT) resultType = "bool"; // scalar version - sb << fixity; - sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << qual << type.name << " value);\n"; + sb << "__prefix __intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << type.name << " value);\n"; // vector version sb << "__generic<let N : int> "; - sb << fixity; - sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << qual << "vector<" << type.name << ",N> value);\n"; + sb << "__prefix __intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << "vector<" << type.name << ",N> value);\n"; // matrix version sb << "__generic<let N : int, let M : int> "; - sb << fixity; - sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << qual << "matrix<" << type.name << ",N,M> value);\n"; + sb << "__prefix __intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << "matrix<" << type.name << ",N,M> value);\n"; } // Synthesize generic versions @@ -1490,27 +1484,85 @@ for (auto op : unaryOps) if (op.flags & BOOL_RESULT) resultType = "bool"; // scalar version - sb << fixity; sb << "__generic<T : " << op.interface << ">\n"; - sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << qual << "T value);\n"; + sb << "__prefix __intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << "T value);\n"; // vector version sb << "__generic<T : " << op.interface << ", let N : int> "; - sb << fixity; - sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << qual << "vector<T,N> value);\n"; + sb << "__prefix __intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(vector<T,N> value);\n"; // matrix version sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; - sb << fixity; - sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << qual << "matrix<T,N,M> value);\n"; + sb << "__prefix __intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(matrix<T,N,M> value);\n"; } } -for (auto op : binaryOps) +}}}} + +__generic<T : __BuiltinArithmeticType> +[__unsafeForceInlineEarly] +__prefix T operator+(T value) +{ return value; } + +__generic<T : __BuiltinArithmeticType, let N : int> +[__unsafeForceInlineEarly] +__prefix vector<T,N> operator+(vector<T,N> value) +{ return value; } + +__generic<T : __BuiltinArithmeticType, let R : int, let C : int> +[__unsafeForceInlineEarly] +__prefix matrix<T,R,C> operator+(matrix<T,R,C> value) +{ return value; } + +${{{{ + +static const struct IncDecOpInfo +{ + char const* name; + char const* binOp; +} kIncDecOps[] = +{ + { "++", "+" }, + { "--", "-" }, +}; +static const struct IncDecOpFixity +{ + char const* qual; + char const* bodyPrefix; + char const* returnVal; +} kIncDecFixities[] = { - char const* leftQual = ""; - if(op.flags & ASSIGNMENT) leftQual = "in out "; + { "__prefix", "", "value" }, + { "__postfix", " let result = value;", "result" }, +}; +for(auto op : kIncDecOps) +for(auto fixity : kIncDecFixities) +{ +}}}} + +$(fixity.qual) +__generic<T : __BuiltinArithmeticType> +[__unsafeForceInlineEarly] +T operator$(op.name)(in out T value) +{$(fixity.bodyPrefix) value = value $(op.binOp) T(1); return $(fixity.returnVal); } + +$(fixity.qual) +__generic<T : __BuiltinArithmeticType, let N : int> +[__unsafeForceInlineEarly] +vector<T,N> operator$(op.name)(in out vector<T,N> value) +{$(fixity.bodyPrefix) value = value $(op.binOp) T(1); return $(fixity.returnVal); } +$(fixity.qual) +__generic<T : __BuiltinArithmeticType, let R : int, let C : int> +[__unsafeForceInlineEarly] +matrix<T,R,C> operator$(op.name)(in out matrix<T,R,C> value) +{$(fixity.bodyPrefix) value = value $(op.binOp) T(1); return $(fixity.returnVal); } + +${{{{ +} + +for (auto op : intrinsicBinaryOps) +{ for (auto type : kBaseTypes) { if ((type.flags & op.flags) == 0) @@ -1533,15 +1585,15 @@ for (auto op : binaryOps) // the compiler) // scalar version - sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << leftQual << leftType << " left, " << rightType << " right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << leftType << " left, " << rightType << " right);\n"; // vector version sb << "__generic<let N : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << "vector<" << leftType << ",N> left, vector<" << rightType << ",N> right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(vector<" << leftType << ",N> left, vector<" << rightType << ",N> right);\n"; // matrix version sb << "__generic<let N : int, let M : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, matrix<" << rightType << ",N,M> right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(matrix<" << leftType << ",N,M> left, matrix<" << rightType << ",N,M> right);\n"; // We currently synthesize addiitonal overloads // for the case where one or the other operand @@ -1577,21 +1629,18 @@ for (auto op : binaryOps) // for builtin in non-operator functions anyway. // scalar-vector and scalar-matrix - if (!(op.flags & ASSIGNMENT)) - { - sb << "__generic<let N : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << leftType << " left, vector<" << rightType << ",N> right);\n"; + sb << "__generic<let N : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftType << " left, vector<" << rightType << ",N> right);\n"; - sb << "__generic<let N : int, let M : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << leftType << " left, matrix<" << rightType << ",N,M> right);\n"; - } + sb << "__generic<let N : int, let M : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftType << " left, matrix<" << rightType << ",N,M> right);\n"; // vector-scalar and matrix-scalar sb << "__generic<let N : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << "vector<" << leftType << ",N> left, " << rightType << " right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(vector<" << leftType << ",N> left, " << rightType << " right);\n"; sb << "__generic<let N : int, let M : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, " << rightType << " right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(matrix<" << leftType << ",N,M> left, " << rightType << " right);\n"; } // Synthesize generic versions @@ -1606,36 +1655,102 @@ for (auto op : binaryOps) // scalar version sb << "__generic<T : " << op.interface << ">\n"; - sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << leftQual << leftType << " left, " << rightType << " right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << leftType << " left, " << rightType << " right);\n"; // vector version sb << "__generic<T : " << op.interface << ", let N : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << "vector<" << leftType << ",N> left, vector<" << rightType << ",N> right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(vector<" << leftType << ",N> left, vector<" << rightType << ",N> right);\n"; // matrix version sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, matrix<" << rightType << ",N,M> right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(matrix<" << leftType << ",N,M> left, matrix<" << rightType << ",N,M> right);\n"; // scalar-vector and scalar-matrix - if (!(op.flags & ASSIGNMENT)) - { - sb << "__generic<T : " << op.interface << ", let N : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << leftType << " left, vector<" << rightType << ",N> right);\n"; + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftType << " left, vector<" << rightType << ",N> right);\n"; - sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << leftType << " left, matrix<" << rightType << ",N,M> right);\n"; - } + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftType << " left, matrix<" << rightType << ",N,M> right);\n"; // vector-scalar and matrix-scalar sb << "__generic<T : " << op.interface << ", let N : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << "vector<" << leftType << ",N> left, " << rightType << " right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(vector<" << leftType << ",N> left, " << rightType << " right);\n"; sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; - sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, " << rightType << " right);\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(matrix<" << leftType << ",N,M> left, " << rightType << " right);\n"; } } + + + static const struct CompoundBinaryOpInfo + { + char const* name; + char const* interface; + } kCompoundBinaryOps[] = + { + { "+", "__BuiltinArithmeticType" }, + { "-", "__BuiltinArithmeticType" }, + { "*", "__BuiltinArithmeticType" }, + { "/", "__BuiltinArithmeticType" }, + { "%", "__BuiltinIntegerType" }, + { "%", "__BuiltinFloatingPointType" }, + { "&", "__BuiltinLogicalType" }, + { "|", "__BuiltinLogicalType" }, + { "^", "__BuiltinLogicalType" }, + { "<<", "__BuiltinIntegerType" }, + { ">>", "__BuiltinIntegerType" }, + }; + for( auto op : kCompoundBinaryOps ) + { + }}}} + + __generic<T : $(op.interface)> + [__unsafeForceInlineEarly] + T operator$(op.name)=(in out T left, T right) + { + left = left $(op.name) right; + return left; + } + + __generic<T : $(op.interface), let N : int> + [__unsafeForceInlineEarly] + vector<T,N> operator$(op.name)=(in out vector<T,N> left, vector<T,N> right) + { + left = left $(op.name) right; + return left; + } + + __generic<T : $(op.interface), let N : int> + [__unsafeForceInlineEarly] + vector<T,N> operator$(op.name)=(in out vector<T,N> left, T right) + { + left = left $(op.name) right; + return left; + } + + __generic<T : $(op.interface), let R : int, let C : int> + [__unsafeForceInlineEarly] + matrix<T,R,C> operator$(op.name)=(in out matrix<T,R,C> left, matrix<T,R,C> right) + { + left = left $(op.name) right; + return left; + } + + __generic<T : $(op.interface), let R : int, let C : int> + [__unsafeForceInlineEarly] + matrix<T,R,C> operator$(op.name)=(in out matrix<T,R,C> left, T right) + { + left = left $(op.name) right; + return left; + } + + ${{{{ + } + }}}} + + // Specialized function __intrinsic_op diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index 2b556c10b..30c86b3eb 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -524,16 +524,19 @@ matrix<float,N,M> asfloat(matrix<uint,N,M> x) } // No op -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -float asfloat(float x); +[__unsafeForceInlineEarly] +float asfloat(float x) +{ return x; } __generic<let N : int> -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -vector<float,N> asfloat(vector<float,N> x); +[__unsafeForceInlineEarly] +vector<float,N> asfloat(vector<float,N> x) +{ return x; } __generic<let N : int, let M : int> -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -matrix<float,N,M> asfloat(matrix<float,N,M> x); +[__unsafeForceInlineEarly] +matrix<float,N,M> asfloat(matrix<float,N,M> x) +{ return x; } // Inverse sine (HLSL SM 1.0) __generic<T : __BuiltinFloatingPointType> @@ -599,16 +602,19 @@ matrix<int, N, M> asint(matrix<uint, N, M> x) } // No op -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -int asint(int x); +[__unsafeForceInlineEarly] +int asint(int x) +{ return x; } __generic<let N : int> -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -vector<int,N> asint(vector<int,N> x); +[__unsafeForceInlineEarly] +vector<int,N> asint(vector<int,N> x) +{ return x; } __generic<let N : int, let M : int> -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -matrix<int,N,M> asint(matrix<int,N,M> x); +[__unsafeForceInlineEarly] +matrix<int,N,M> asint(matrix<int,N,M> x) +{ return x; } // Reinterpret bits of double as a uint (HLSL SM 5.0) @@ -657,16 +663,19 @@ matrix<uint, N, M> asuint(matrix<int, N, M> x) MATRIX_MAP_UNARY(uint, N, M, asuint, x); } -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -uint asuint(uint x); +[__unsafeForceInlineEarly] +uint asuint(uint x) +{ return x; } __generic<let N : int> -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -vector<uint,N> asuint(vector<uint,N> x); +[__unsafeForceInlineEarly] +vector<uint,N> asuint(vector<uint,N> x) +{ return x; } __generic<let N : int, let M : int> -__intrinsic_op($(kCompoundIntrinsicOp_Pos)) -matrix<uint,N,M> asuint(matrix<uint,N,M> x); +[__unsafeForceInlineEarly] +matrix<uint,N,M> asuint(matrix<uint,N,M> x) +{ return x; } // Inverse tangent (HLSL SM 1.0) __generic<T : __BuiltinFloatingPointType> diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index adfc36641..fbe38bac7 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -359,6 +359,7 @@ namespace Slang } RefPtr<Expr> SemanticsVisitor::createLookupResultExpr( + Name* name, LookupResult const& lookupResult, RefPtr<Expr> baseExpr, SourceLoc loc) @@ -366,6 +367,7 @@ namespace Slang if (lookupResult.isOverloaded()) { auto overloadedExpr = new OverloadedExpr(); + overloadedExpr->name = name; overloadedExpr->loc = loc; overloadedExpr->type = QualType( getSession()->getOverloadedType()); @@ -960,10 +962,11 @@ namespace Slang // declarations on the type and try to call one of them. { + Name* name = getName("operator[]"); LookupResult lookupResult = lookUpMember( getSession(), this, - getName("operator[]"), + name, baseType); if (!lookupResult.isValid()) { @@ -977,7 +980,7 @@ namespace Slang // case the attempt to call it will trigger overload // resolution. RefPtr<Expr> subscriptFuncExpr = createLookupResultExpr( - lookupResult, subscriptExpr->BaseExpression, subscriptExpr->loc); + name, lookupResult, subscriptExpr->BaseExpression, subscriptExpr->loc); RefPtr<InvokeExpr> subscriptCallExpr = new InvokeExpr(); subscriptCallExpr->loc = subscriptExpr->loc; @@ -1181,6 +1184,7 @@ namespace Slang if (lookupResult.isValid()) { return createLookupResultExpr( + expr->name, lookupResult, nullptr, expr->loc); @@ -1531,6 +1535,7 @@ namespace Slang } return createLookupResultExpr( + expr->name, lookupResult, baseExpression, expr->loc); @@ -1638,6 +1643,7 @@ namespace Slang // to in this context... return createLookupResultExpr( + expr->name, lookupResult, expr->BaseExpression, expr->loc); diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 8b32f8612..df0080ec1 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -356,6 +356,7 @@ namespace Slang SourceLoc loc); RefPtr<Expr> createLookupResultExpr( + Name* name, LookupResult const& lookupResult, RefPtr<Expr> baseExpr, SourceLoc loc); diff --git a/source/slang/slang-check-overload.cpp b/source/slang/slang-check-overload.cpp index 41aa94a35..92d390bf0 100644 --- a/source/slang/slang-check-overload.cpp +++ b/source/slang/slang-check-overload.cpp @@ -1261,9 +1261,9 @@ namespace Slang if (!first) sb << ", "; first = false; - formatType(sb, GetType(genericValParam)); - sb << " "; sb << getText(genericValParam.GetName()); + sb << ":"; + formatType(sb, GetType(genericValParam)); } else {} @@ -1279,14 +1279,24 @@ namespace Slang static void formatDeclKindPrefix(StringBuilder& sb, Decl* decl) { + if(auto genericDecl = as<GenericDecl>(decl)) + { + decl = genericDecl->inner; + } if(as<FuncDecl>(decl)) { sb << "func "; } } - void SemanticsVisitor::formatDeclResultType(StringBuilder& sb, DeclRef<Decl> const& declRef) + void SemanticsVisitor::formatDeclResultType(StringBuilder& sb, DeclRef<Decl> const& inDeclRef) { + DeclRef<Decl> declRef = inDeclRef; + if(auto genericDeclRef = declRef.as<GenericDecl>()) + { + declRef = DeclRef<Decl>(GetInner(genericDeclRef), genericDeclRef.substitutions); + } + if(as<ConstructorDecl>(declRef)) {} else if(auto callableDeclRef = declRef.as<CallableDecl>()) @@ -1434,10 +1444,19 @@ namespace Slang } Name* funcName = nullptr; - if (auto baseVar = as<VarExpr>(funcExpr)) - funcName = baseVar->name; - else if(auto baseMemberRef = as<MemberExpr>(funcExpr)) - funcName = baseMemberRef->name; + { + Expr* baseExpr = funcExpr; + + if(auto baseGenericApp = as<GenericAppExpr>(baseExpr)) + baseExpr = baseGenericApp->FunctionExpr; + + if (auto baseVar = as<VarExpr>(baseExpr)) + funcName = baseVar->name; + else if(auto baseMemberRef = as<MemberExpr>(baseExpr)) + funcName = baseMemberRef->name; + else if(auto baseOverloaded = as<OverloadedExpr>(baseExpr)) + funcName = baseOverloaded->name; + } String argsList = getCallSignatureString(context); diff --git a/source/slang/slang-compound-intrinsics.h b/source/slang/slang-compound-intrinsics.h deleted file mode 100644 index 682e7bc4a..000000000 --- a/source/slang/slang-compound-intrinsics.h +++ /dev/null @@ -1,80 +0,0 @@ -// slang-compound-intrinsics.h -#pragma once - -// Intrinsic functions in the Slang standard library are marked -// with the `__intrinsic_op(...)` modifier. Many of these map -// one-to-one to instruction opcodes in the Slang IR, and the -// argument to `__intrinsic_op(...)` is the IR instruction -// opcode in that case. -// -// In other cases, we have intrinsic operations like the `+=` or -// `&&` operator that either need to map to multiple IR instructions -// (or more generally, a number of instructions not equal to one), -// or otherwise have complications thake one-to-one lowering -// not possible. -// -// We refer to these as "compound" intrinsic ops, since the common -// case is that they represent a composition of multiple instructions. -// -// In order to not conflict with the opcodes of any IR instructions, -// these compound intrinsic ops will all be identified by *negative* -// integer opcodes. - -// We start by defining an "X-macro" that lists all the compound -// intrinsic ops we support. - -#define FOREACH_COMPOUND_INTRINSIC_OP(M) \ - M(Pos) \ - M(PreInc) \ - M(PreDec) \ - M(PostInc) \ - M(PostDec) \ - M(AddAssign) \ - M(SubAssign) \ - M(MulAssign) \ - M(DivAssign) \ - M(IRemAssign) \ - M(FRemAssign) \ - M(AndAssign) \ - M(OrAssign) \ - M(XorAssign ) \ - M(LshAssign) \ - M(RshAssign) \ - M(Assign) \ - M(And) \ - M(Or) \ - /* end */ - -// We will use a simple type alias to capture the fact that -// a 32-bit integer is sufficient to represent compound -// intrinsic ops (as negative values) plus IR opcode values -// for single-instruction intrinsics (as non-negative values) -// -typedef int32_t IntrinsicOp; - -// Next we use an enumeration declaration as an implementation -// detail, to associate each of the above cases with a (positive) -// integer. -// -enum class _CompoundIntrinsicOpVal : IntrinsicOp -{ -#define DECLARE_COMPOUND_INTRINSIC_OP_VAL(NAME) NAME, - FOREACH_COMPOUND_INTRINSIC_OP(DECLARE_COMPOUND_INTRINSIC_OP_VAL) -#undef DECLARE_COMPOUND_INTRINSIC_OP_VAL -}; - -// Finally, we define a second enumeration that takes the values -// from the first and performs a bitwise negation on them, which -// guarantees we get strictly negative values. - - /// Compound/complex intrinsic operations, which do not map to a single IR instruction. - /// - /// All of the values of this enumeration are guaranteed to be negative, and thus - /// cannot conflict with any valid value of type `IROp` - /// -enum CompoundIntrinsicOp : IntrinsicOp -{ -#define DECLARE_COMPOUND_INTRINSIC_OP(NAME) kCompoundIntrinsicOp_##NAME = ~IntrinsicOp(_CompoundIntrinsicOpVal::NAME), - FOREACH_COMPOUND_INTRINSIC_OP(DECLARE_COMPOUND_INTRINSIC_OP) -#undef DECLARE_COMPOUND_INTRINSIC_OP -}; diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index ff64b023c..2fbd373cc 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -329,7 +329,7 @@ DIAGNOSTIC(39999, Error, expectedIntegerConstantNotLiteral, "could not extract v DIAGNOSTIC(39999, Error, noApplicableOverloadForNameWithArgs, "no overload for '$0' applicable to arguments of type $1") DIAGNOSTIC(39999, Error, noApplicableWithArgs, "no overload applicable to arguments of type $0") -DIAGNOSTIC(39999, Error, ambiguousOverloadForNameWithArgs, "ambiguous call to '$0' operation with arguments of type $1") +DIAGNOSTIC(39999, Error, ambiguousOverloadForNameWithArgs, "ambiguous call to '$0' with arguments of type $1") DIAGNOSTIC(39999, Error, ambiguousOverloadWithArgs, "ambiguous call to overloaded operation with arguments of type $0") DIAGNOSTIC(39999, Note, overloadCandidate, "candidate: $0") diff --git a/source/slang/slang-expr-defs.h b/source/slang/slang-expr-defs.h index a7105b07b..725bbb62d 100644 --- a/source/slang/slang-expr-defs.h +++ b/source/slang/slang-expr-defs.h @@ -21,6 +21,8 @@ SIMPLE_SYNTAX_CLASS(VarExpr, DeclRefExpr) // An expression that references an overloaded set of declarations // having the same name. SYNTAX_CLASS(OverloadedExpr, Expr) + // The name that was looked up and found to be overloaded + FIELD(Name*, name) // Optional: the base expression is this overloaded result // arose from a member-reference expression. diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index f57e15bc4..029fe23f6 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -5,7 +5,6 @@ #include "slang-check.h" #include "slang-ir.h" -#include "slang-compound-intrinsics.h" #include "slang-ir-constexpr.h" #include "slang-ir-dce.h" #include "slang-ir-inline.h" @@ -475,7 +474,8 @@ bool isImportedDecl(IRGenContext* context, Decl* decl) return false; } -static bool isInline(Decl* decl) + /// Is `decl` a function that should be force-inlined early in compilation (before linking)? +static bool isForceInlineEarly(Decl* decl) { if(decl->HasModifier<UnsafeForceInlineEarlyAttribute>()) return true; @@ -542,113 +542,6 @@ LoweredValInfo emitCallToVal( } } -LoweredValInfo emitCompoundAssignOp( - IRGenContext* context, - IRType* type, - IROp op, - UInt argCount, - IRInst* const* args) -{ - auto builder = context->irBuilder; - SLANG_UNREFERENCED_PARAMETER(argCount); - SLANG_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); -} - -IRInst* getOneValOfType( - IRGenContext* context, - IRType* type) -{ - switch(type->op) - { - case kIROp_IntType: - case kIROp_UIntType: - case kIROp_UInt64Type: - return context->irBuilder->getIntValue(type, 1); - - case kIROp_HalfType: - case kIROp_FloatType: - case kIROp_DoubleType: - return context->irBuilder->getFloatValue(type, 1.0); - - default: - break; - } - - // TODO: should make sure to handle vector and matrix types here - - SLANG_UNEXPECTED("inc/dec type"); - UNREACHABLE_RETURN(nullptr); -} - -LoweredValInfo emitPrefixIncDecOp( - IRGenContext* context, - IRType* type, - IROp op, - UInt argCount, - IRInst* const* args) -{ - auto builder = context->irBuilder; - SLANG_UNREFERENCED_PARAMETER(argCount); - SLANG_ASSERT(argCount == 1); - auto argPtr = args[0]; - - auto preVal = builder->emitLoad(argPtr); - - IRInst* oneVal = getOneValOfType(context, type); - - IRInst* innerArgs[] = { preVal, oneVal }; - auto innerOp = builder->emitIntrinsicInst(type, op, 2, innerArgs); - - builder->emitStore(argPtr, innerOp); - - // For a prefix operator like `++i` we return - // the value after the increment/decrement has - // been applied. In casual terms we "increment - // the varaible, then return its value." - // - return LoweredValInfo::simple(innerOp); -} - -LoweredValInfo emitPostfixIncDecOp( - IRGenContext* context, - IRType* type, - IROp op, - UInt argCount, - IRInst* const* args) -{ - auto builder = context->irBuilder; - SLANG_UNREFERENCED_PARAMETER(argCount); - SLANG_ASSERT(argCount == 1); - auto argPtr = args[0]; - - auto preVal = builder->emitLoad(argPtr); - - IRInst* oneVal = getOneValOfType(context, type); - - IRInst* innerArgs[] = { preVal, oneVal }; - auto innerOp = builder->emitIntrinsicInst(type, op, 2, innerArgs); - - builder->emitStore(argPtr, innerOp); - - // For a postfix operator like `i++` we return - // the value that we read before the increment/decrement - // gets applied. In casual terms we "read - // the variable, then increment it." - // - return LoweredValInfo::simple(preVal); -} - LoweredValInfo lowerRValueExpr( IRGenContext* context, Expr* expr); @@ -739,67 +632,16 @@ LoweredValInfo emitCallToDeclRef( auto funcDecl = funcDeclRef.getDecl(); if(auto intrinsicOpModifier = funcDecl->FindModifier<IntrinsicOpModifier>()) { - // An intrinsic op either maps to a single IR instruction - // (in the case where the opcode value is >= 0), or to - // a `CompountIntrinsicOp` (in the case where it is < 0). + // The intrinsic op maps to a single IR instruction, + // so we will emit an instruction with the chosen + // opcode, and the arguments to the call as its operands. // auto intrinsicOp = getIntrinsicOp(funcDecl, intrinsicOpModifier); - if(intrinsicOp < 0) - { - // We have a compound op, which requires special-case - // handling to generate zero or more IR instructions. - // - auto compoundOp = CompoundIntrinsicOp(intrinsicOp); - switch (compoundOp) - { - case kCompoundIntrinsicOp_Pos: - return LoweredValInfo::simple(args[0]); - -#define CASE(COMPOUND, OP) \ - case COMPOUND: return emitCompoundAssignOp(context, type, OP, argCount, args) - - CASE(kCompoundIntrinsicOp_AddAssign, kIROp_Add); - CASE(kCompoundIntrinsicOp_SubAssign, kIROp_Sub); - CASE(kCompoundIntrinsicOp_MulAssign, kIROp_Mul); - CASE(kCompoundIntrinsicOp_DivAssign, kIROp_Div); - CASE(kCompoundIntrinsicOp_IRemAssign,kIROp_IRem); - CASE(kCompoundIntrinsicOp_FRemAssign,kIROp_FRem); - CASE(kCompoundIntrinsicOp_AndAssign, kIROp_BitAnd); - CASE(kCompoundIntrinsicOp_OrAssign, kIROp_BitOr); - CASE(kCompoundIntrinsicOp_XorAssign, kIROp_BitXor); - CASE(kCompoundIntrinsicOp_LshAssign, kIROp_Lsh); - CASE(kCompoundIntrinsicOp_RshAssign, kIROp_Rsh); - -#undef CASE - -#define CASE(COMPOUND, OP) \ - case COMPOUND: return emitPrefixIncDecOp(context, type, OP, argCount, args) - CASE(kCompoundIntrinsicOp_PreInc, kIROp_Add); - CASE(kCompoundIntrinsicOp_PreDec, kIROp_Sub); -#undef CASE - -#define CASE(COMPOUND, OP) \ - case COMPOUND: return emitPostfixIncDecOp(context, type, OP, argCount, args) - CASE(kCompoundIntrinsicOp_PostInc, kIROp_Add); - CASE(kCompoundIntrinsicOp_PostDec, kIROp_Sub); -#undef CASE - default: - SLANG_UNIMPLEMENTED_X("IR pseudo-op"); - UNREACHABLE_RETURN(LoweredValInfo()); - } - } - else - { - // The intrinsic op maps to a single IR instruction, - // so we will emit an instruction with the chosen - // opcode, and the arguments to the call as its operands. - // - return LoweredValInfo::simple(builder->emitIntrinsicInst( - type, - IROp(intrinsicOp), - argCount, - args)); - } + return LoweredValInfo::simple(builder->emitIntrinsicInst( + type, + IROp(intrinsicOp), + argCount, + args)); } if( auto ctorDeclRef = funcDeclRef.as<ConstructorDecl>() ) @@ -5184,7 +5026,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> if(auto initExpr = decl->initExpr) { auto initVal = lowerRValueExpr(context, initExpr); - initVal = materialize(context, initVal); + initVal = LoweredValInfo::simple(getSimpleVal(context, initVal)); setGlobalValue(context, decl, initVal); return initVal; } @@ -6042,7 +5884,22 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> subBuilder->setInsertInto(irFunc); - if (isImportedDecl(decl) && !isInline(decl)) + // If a function is imported from another module then + // we usually don't want to emit it as a definition, and + // will instead only emit a declaration for it with an + // appropriate `[import(...)]` linkage decoration. + // + // However, if the function is marked with `[__unsafeForceInlineEarly]` + // then we need to make sure the IR for its definition is available + // to the mandatory optimization passes. + // + // TODO: The design here means that we will re-emit the inline + // function from its AST in every module that uses it. We should + // instead have logic to clone the target function in from the + // pre-generated IR for the module that defines it (or do some kind + // of minimal linking to bring in the inline functions). + // + if (isImportedDecl(decl) && !isForceInlineEarly(decl)) { // Always emit imported declarations as declarations, // and not definitions. diff --git a/source/slang/slang-mangle.cpp b/source/slang/slang-mangle.cpp index dfb55404e..2ad84d3d3 100644 --- a/source/slang/slang-mangle.cpp +++ b/source/slang/slang-mangle.cpp @@ -273,6 +273,14 @@ namespace Slang if (declRef.is<RefAccessorDecl>()) emitRaw(context, "Ar"); } + // Special case: need a way to tell prefix and postfix unary + // operators apart. + { + if(declRef.getDecl()->HasModifier<PostfixModifier>()) emitRaw(context, "P"); + if(declRef.getDecl()->HasModifier<PrefixModifier>()) emitRaw(context, "p"); + } + + // Are we the "inner" declaration beneath a generic decl? if(parentGenericDeclRef && (parentGenericDeclRef.getDecl()->inner.Ptr() == declRef.getDecl())) { diff --git a/source/slang/slang-modifier-defs.h b/source/slang/slang-modifier-defs.h index 58f2c5af9..7ea1d0101 100644 --- a/source/slang/slang-modifier-defs.h +++ b/source/slang/slang-modifier-defs.h @@ -36,17 +36,9 @@ SYNTAX_CLASS(IntrinsicOpModifier, Modifier) // Token that names the intrinsic op. FIELD(Token, opToken) - // The opcode for the intrinsic operation. + // The IR opcode for the intrinsic operation. // - // If greather than or equal to zero, then `op` - // is an `IROp` and directly identifies an IR - // instruction that the intrinsic should translate to. - // - // If less than zero, then `op` is a `CompoundIntrinsicOp` - // which maps to zero or more IR instructions using - // special-case logic in the IR lowering phase. - // - FIELD_INIT(IntrinsicOp, op, 0) + FIELD_INIT(uint32_t, op, 0) END_SYNTAX_CLASS() // A modifier that marks something as an intrinsic function, diff --git a/source/slang/slang-stdlib.cpp b/source/slang/slang-stdlib.cpp index b23d1970b..1f59b1686 100644 --- a/source/slang/slang-stdlib.cpp +++ b/source/slang/slang-stdlib.cpp @@ -1,7 +1,6 @@ // slang-stdlib.cpp #include "slang-compiler.h" -#include "slang-compound-intrinsics.h" #include "slang-ir.h" #include "slang-syntax.h" @@ -50,8 +49,6 @@ namespace Slang BOOL_RESULT = 1 << 2, BOOL_MASK = 1 << 3, UINT_MASK = 1 << 4, - ASSIGNMENT = 1 << 5, - POSTFIX = 1 << 6, INT_MASK = SINT_MASK | UINT_MASK, ARITHMETIC_MASK = INT_MASK | FLOAT_MASK, @@ -199,20 +196,15 @@ namespace Slang } } - struct OpInfo { IntrinsicOp opCode; char const* opName; char const* interface; unsigned flags; }; + struct IntrinsicOpInfo { IROp opCode; char const* opName; char const* interface; unsigned flags; }; - static const OpInfo unaryOps[] = { - { kCompoundIntrinsicOp_Pos, "+", "__BuiltinArithmeticType", ARITHMETIC_MASK }, + static const IntrinsicOpInfo intrinsicUnaryOps[] = { { kIROp_Neg, "-", "__BuiltinArithmeticType", ARITHMETIC_MASK }, { kIROp_Not, "!", nullptr, BOOL_MASK | BOOL_RESULT }, { kIROp_BitNot, "~", "__BuiltinIntegerType", INT_MASK }, - { kCompoundIntrinsicOp_PreInc, "++", "__BuiltinArithmeticType", ARITHMETIC_MASK | ASSIGNMENT }, - { kCompoundIntrinsicOp_PreDec, "--", "__BuiltinArithmeticType", ARITHMETIC_MASK | ASSIGNMENT }, - { kCompoundIntrinsicOp_PostInc, "++", "__BuiltinArithmeticType", ARITHMETIC_MASK | ASSIGNMENT | POSTFIX }, - { kCompoundIntrinsicOp_PostDec, "--", "__BuiltinArithmeticType", ARITHMETIC_MASK | ASSIGNMENT | POSTFIX }, }; - static const OpInfo binaryOps[] = { + static const IntrinsicOpInfo intrinsicBinaryOps[] = { { kIROp_Add, "+", "__BuiltinArithmeticType", ARITHMETIC_MASK }, { kIROp_Sub, "-", "__BuiltinArithmeticType", ARITHMETIC_MASK }, { kIROp_Mul, "*", "__BuiltinArithmeticType", ARITHMETIC_MASK }, @@ -232,17 +224,6 @@ namespace Slang { kIROp_Less, "<", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT }, { kIROp_Geq, ">=", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT }, { kIROp_Leq, "<=", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT }, - { kCompoundIntrinsicOp_AddAssign, "+=", "__BuiltinArithmeticType", ASSIGNMENT | ARITHMETIC_MASK }, - { kCompoundIntrinsicOp_SubAssign, "-=", "__BuiltinArithmeticType", ASSIGNMENT | ARITHMETIC_MASK }, - { kCompoundIntrinsicOp_MulAssign, "*=", "__BuiltinArithmeticType", ASSIGNMENT | ARITHMETIC_MASK }, - { kCompoundIntrinsicOp_DivAssign, "/=", "__BuiltinArithmeticType", ASSIGNMENT | ARITHMETIC_MASK }, - { kCompoundIntrinsicOp_IRemAssign, "%=", "__BuiltinIntegerType", ASSIGNMENT | INT_MASK }, - { kCompoundIntrinsicOp_FRemAssign, "%=", "__BuiltinFloatingPointType", ASSIGNMENT | FLOAT_MASK }, - { kCompoundIntrinsicOp_AndAssign, "&=", "__BuiltinLogicalType", ASSIGNMENT | LOGICAL_MASK }, - { kCompoundIntrinsicOp_OrAssign, "|=", "__BuiltinLogicalType", ASSIGNMENT | LOGICAL_MASK }, - { kCompoundIntrinsicOp_XorAssign, "^=", "__BuiltinLogicalType", ASSIGNMENT | LOGICAL_MASK }, - { kCompoundIntrinsicOp_LshAssign, "<<=", "__BuiltinIntegerType", ASSIGNMENT | INT_MASK }, - { kCompoundIntrinsicOp_RshAssign, ">>=", "__BuiltinIntegerType", ASSIGNMENT | INT_MASK }, }; String Session::getCoreLibraryCode() diff --git a/source/slang/slang-syntax.h b/source/slang/slang-syntax.h index 9c3945b4d..61219ecd2 100644 --- a/source/slang/slang-syntax.h +++ b/source/slang/slang-syntax.h @@ -2,7 +2,6 @@ #define SLANG_SYNTAX_H #include "../core/slang-basic.h" -#include "slang-compound-intrinsics.h" #include "slang-lexer.h" #include "slang-profile.h" #include "slang-type-system-shared.h" diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 0d702d9f5..027c43a95 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -193,7 +193,6 @@ <ClInclude Include="slang-check-impl.h" /> <ClInclude Include="slang-check.h" /> <ClInclude Include="slang-compiler.h" /> - <ClInclude Include="slang-compound-intrinsics.h" /> <ClInclude Include="slang-decl-defs.h" /> <ClInclude Include="slang-diagnostic-defs.h" /> <ClInclude Include="slang-diagnostics.h" /> diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index a73a74756..9be567654 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -30,9 +30,6 @@ <ClInclude Include="slang-compiler.h"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="slang-compound-intrinsics.h"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="slang-decl-defs.h"> <Filter>Header Files</Filter> </ClInclude> |
