summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2023-06-30 15:25:59 -0400
committerGitHub <noreply@github.com>2023-06-30 12:25:59 -0700
commitc5b0708ead5de2d90ef14f20b5b8e3ed4f576373 (patch)
tree0e7691cda679b5f937d7ed73eba021ad9170245c
parenta3ad4dd77bba6c87abad4f76b72055c9fed94bad (diff)
Fix for operator assignment issue (#2951)
* WIP handling LValue coercion via LValueImplicitCast * Need to have the ptr type for the cast. * Casting conversion working on C++. * Make the LValue casts record if in or in/out as we can produce better code if we know the difference. * WIP LValueCast pass * Fix tests so we don't fail because downstream compilers detect use of uninitialized variable. * Do conversions through through tmp for l-value scenarios that can't work other ways. * Fix a typo. * Change diagnostic implicit-cast-lvalue for a type that still exhibits the issue. * Add matrix test. * Added a bit more clarity around LValue casting choices. * Small comment improvements. Improvements based on comments on PR. * Use findOuterGeneric.
-rw-r--r--build/visual-studio/slang/slang.vcxproj2
-rw-r--r--build/visual-studio/slang/slang.vcxproj.filters6
-rw-r--r--source/slang/slang-ast-expr.h26
-rw-r--r--source/slang/slang-check-expr.cpp107
-rwxr-xr-xsource/slang/slang-compiler.h6
-rw-r--r--source/slang/slang-emit-cpp.cpp14
-rw-r--r--source/slang/slang-emit.cpp4
-rw-r--r--source/slang/slang-ir-inst-defs.h2
-rw-r--r--source/slang/slang-ir-insts.h2
-rw-r--r--source/slang/slang-ir-lower-l-value-cast.cpp247
-rw-r--r--source/slang/slang-ir-lower-l-value-cast.h24
-rw-r--r--source/slang/slang-ir.cpp9
-rw-r--r--source/slang/slang-lower-to-ir.cpp28
-rw-r--r--source/slang/slang-type-layout.cpp56
-rw-r--r--tests/bugs/op-assignment-unify-mat.slang47
-rw-r--r--tests/bugs/op-assignment-unify-mat.slang.expected.txt4
-rw-r--r--tests/bugs/op-assignment-unify-vec.slang47
-rw-r--r--tests/bugs/op-assignment-unify-vec.slang.expected.txt6
-rw-r--r--tests/bugs/op-assignment-unify.slang44
-rw-r--r--tests/bugs/op-assignment-unify.slang.expected.txt6
-rw-r--r--tests/diagnostics/implicit-cast-lvalue.slang9
-rw-r--r--tests/diagnostics/implicit-cast-lvalue.slang.expected7
22 files changed, 687 insertions, 16 deletions
diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj
index 3f1f580ed..3bff1e419 100644
--- a/build/visual-studio/slang/slang.vcxproj
+++ b/build/visual-studio/slang/slang.vcxproj
@@ -411,6 +411,7 @@ IF EXIST ..\..\..\external\slang-glslang\bin\windows-aarch64\release\slang-glsla
<ClInclude Include="..\..\..\source\slang\slang-ir-lower-generic-function.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-lower-generic-type.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-lower-generics.h" />
+ <ClInclude Include="..\..\..\source\slang\slang-ir-lower-l-value-cast.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-lower-optional-type.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-lower-reinterpret.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-lower-result-type.h" />
@@ -609,6 +610,7 @@ IF EXIST ..\..\..\external\slang-glslang\bin\windows-aarch64\release\slang-glsla
<ClCompile Include="..\..\..\source\slang\slang-ir-lower-generic-function.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-lower-generic-type.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-lower-generics.cpp" />
+ <ClCompile Include="..\..\..\source\slang\slang-ir-lower-l-value-cast.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-lower-optional-type.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-lower-reinterpret.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-lower-result-type.cpp" />
diff --git a/build/visual-studio/slang/slang.vcxproj.filters b/build/visual-studio/slang/slang.vcxproj.filters
index 3484c406a..f0b6b5324 100644
--- a/build/visual-studio/slang/slang.vcxproj.filters
+++ b/build/visual-studio/slang/slang.vcxproj.filters
@@ -321,6 +321,9 @@
<ClInclude Include="..\..\..\source\slang\slang-ir-lower-generics.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\slang\slang-ir-lower-l-value-cast.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-ir-lower-optional-type.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -911,6 +914,9 @@
<ClCompile Include="..\..\..\source\slang\slang-ir-lower-generics.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\slang\slang-ir-lower-l-value-cast.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\slang\slang-ir-lower-optional-type.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h
index da213e8d4..36d6546de 100644
--- a/source/slang/slang-ast-expr.h
+++ b/source/slang/slang-ast-expr.h
@@ -302,6 +302,32 @@ class ImplicitCastExpr : public TypeCastExpr
SLANG_AST_CLASS(ImplicitCastExpr)
};
+class LValueImplicitCastExpr : public TypeCastExpr
+{
+ SLANG_AST_CLASS(LValueImplicitCastExpr)
+
+ explicit LValueImplicitCastExpr(const TypeCastExpr& rhs) :Super(rhs) {}
+};
+
+// To work around situations like int += uint
+// where we want to allow an LValue to work with an implicit cast.
+// The argument being cast *must* be an LValue.
+class OutImplicitCastExpr : public LValueImplicitCastExpr
+{
+ SLANG_AST_CLASS(OutImplicitCastExpr)
+
+ /// Allow explict construction from any TypeCastExpr
+ explicit OutImplicitCastExpr(const TypeCastExpr& rhs) :Super(rhs) {}
+};
+
+class InOutImplicitCastExpr : public LValueImplicitCastExpr
+{
+ SLANG_AST_CLASS(InOutImplicitCastExpr)
+
+ /// Allow explict construction from any TypeCastExpr
+ explicit InOutImplicitCastExpr(const TypeCastExpr& rhs) :Super(rhs) {}
+};
+
/// A cast of a value to a super-type of its type.
///
/// The type being cast to is stored as this expression's `type`.
diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp
index e0084e08e..00ece3628 100644
--- a/source/slang/slang-check-expr.cpp
+++ b/source/slang/slang-check-expr.cpp
@@ -1952,6 +1952,51 @@ namespace Slang
return checkedExpr;
}
+ static bool _canLValueCoerceScalarType(Type* a, Type* b)
+ {
+ auto basicTypeA = as<BasicExpressionType>(a);
+ auto basicTypeB = as<BasicExpressionType>(b);
+
+ if (basicTypeA && basicTypeB)
+ {
+ const auto& infoA = BaseTypeInfo::getInfo(basicTypeA->baseType);
+ const auto& infoB = BaseTypeInfo::getInfo(basicTypeB->baseType);
+
+ // TODO(JS): Initially this tries to limit where LValueImplict casts happen.
+ // We could in principal allow different sizes, as long as we converted to a temprorary
+ // and back again.
+ //
+ // For now we just stick with the simple case.
+ // // We only allow on integer types for now. In effect just allowing any size uint/int conversions
+ if (infoA.sizeInBytes == infoB.sizeInBytes &&
+ (infoA.flags & infoB.flags & BaseTypeInfo::Flag::Integer))
+ {
+ return true;
+ }
+
+ }
+ return false;
+ }
+
+ static bool _canLValueCoerce(Type* a, Type* b)
+ {
+ // We can *assume* here that if they are coercable, that dimensions of vectors
+ // and matrices match. We might want to assert to be sure...
+ SLANG_ASSERT(a != b);
+ if (a->astNodeType == b->astNodeType)
+ {
+ if (auto matA = as<MatrixExpressionType>(a))
+ {
+ return _canLValueCoerceScalarType(matA->getElementType(), static_cast<MatrixExpressionType*>(b)->getElementType());
+ }
+ else if (auto vecA = as<VectorExpressionType>(a))
+ {
+ return _canLValueCoerceScalarType(vecA->getScalarType(), static_cast<VectorExpressionType*>(b)->getScalarType());
+ }
+ }
+ return _canLValueCoerceScalarType(a, b);
+ }
+
Expr* SemanticsVisitor::CheckInvokeExprWithCheckedOperands(InvokeExpr *expr)
{
auto rs = ResolveInvoke(expr);
@@ -1985,23 +2030,63 @@ namespace Slang
if( pp < expr->arguments.getCount() )
{
auto argExpr = expr->arguments[pp];
- if( !argExpr->type.isLeftValue )
+ if( !argExpr->type.isLeftValue)
{
- getSink()->diagnose(
- argExpr,
- Diagnostics::argumentExpectedLValue,
- pp);
+ auto implicitCastExpr = as<ImplicitCastExpr>(argExpr);
- if( auto implicitCastExpr = as<ImplicitCastExpr>(argExpr) )
+ if (implicitCastExpr && _canLValueCoerce(implicitCastExpr->arguments[0]->type, implicitCastExpr->type))
+ {
+ // This is to work around issues like
+ //
+ // ```
+ // int a = 0;
+ // uint b = 1;
+ // a += b;
+ // ```
+ // That strictly speaking it's not allowed, but we are going to allow it for now
+ // for situations were the types are uint/int and vector/matrix varieties of those types
+ //
+ // Then in lowering we are going to insert code to do something like
+ // ```
+ // var OutType: tmp = arg;
+ // f(... tmp);
+ // arg = tmp;
+ // ```
+
+ TypeCastExpr* lValueImplicitCast;
+
+ // We want to record if the cast is being used for `out` or `inout`/`ref` as
+ // if it's just `out` we won't need to convert before passing in.
+ if (as<OutType>(paramType))
+ {
+ lValueImplicitCast = getASTBuilder()->create<OutImplicitCastExpr>(*implicitCastExpr);
+ }
+ else
+ {
+ lValueImplicitCast = getASTBuilder()->create<InOutImplicitCastExpr>(*implicitCastExpr);
+ }
+
+ // Replace the expression. This should make this situation easier to detect.
+ expr->arguments[pp] = lValueImplicitCast;
+ }
+ else
{
getSink()->diagnose(
argExpr,
- Diagnostics::implicitCastUsedAsLValue,
- implicitCastExpr->arguments[0]->type,
- implicitCastExpr->type);
+ Diagnostics::argumentExpectedLValue,
+ pp);
+
+ if(implicitCastExpr)
+ {
+ getSink()->diagnose(
+ argExpr,
+ Diagnostics::implicitCastUsedAsLValue,
+ implicitCastExpr->arguments[pp]->type,
+ implicitCastExpr->type);
+ }
+
+ maybeDiagnoseThisNotLValue(argExpr);
}
-
- maybeDiagnoseThisNotLValue(argExpr);
}
}
else
diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h
index 4e396e1f5..7082eccd7 100755
--- a/source/slang/slang-compiler.h
+++ b/source/slang/slang-compiler.h
@@ -1633,6 +1633,12 @@ namespace Slang
/// Are we generating code for a CUDA API (CUDA / OptiX)?
bool isCUDATarget(TargetRequest* targetReq);
+ /// Given a target request returns which (if any) intermediate source language is required
+ /// to produce it.
+ ///
+ /// If no intermediate source language is required, will return SourceLanguage::Unknown
+ SourceLanguage getIntermediateSourceLanguageForTarget(TargetRequest* req);
+
/// Are resource types "bindless" (implemented as ordinary data) on the given `target`?
bool areResourceTypesBindlessOnTarget(TargetRequest* target);
diff --git a/source/slang/slang-emit-cpp.cpp b/source/slang/slang-emit-cpp.cpp
index 49edec92b..ef9b7f54e 100644
--- a/source/slang/slang-emit-cpp.cpp
+++ b/source/slang/slang-emit-cpp.cpp
@@ -1200,6 +1200,20 @@ bool CPPSourceEmitter::tryEmitInstExprImpl(IRInst* inst, const EmitOpInfo& inOut
{
return false;
}
+
+ case kIROp_InOutImplicitCast:
+ case kIROp_OutImplicitCast:
+ {
+ // We'll just the LValue to be the desired type
+ m_writer->emit("reinterpret_cast<");
+ emitType(inst->getDataType());
+ m_writer->emit(">(");
+
+ emitOperand(inst->getOperand(0), getInfo(EmitOp::General));
+
+ m_writer->emit(")");
+ return true;
+ }
case kIROp_MakeVector:
{
IRType* retType = inst->getFullType();
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index bd6542e2a..827c69e50 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -35,6 +35,7 @@
#include "slang-ir-lower-result-type.h"
#include "slang-ir-lower-optional-type.h"
#include "slang-ir-lower-bit-cast.h"
+#include "slang-ir-lower-l-value-cast.h"
#include "slang-ir-lower-reinterpret.h"
#include "slang-ir-loop-unroll.h"
#include "slang-ir-metadata.h"
@@ -866,6 +867,9 @@ Result linkAndOptimizeIR(
legalizeUniformBufferLoad(irModule);
}
+ // Lower all the LValue implict casts (used for out/inout/ref scenarios)
+ lowerLValueCast(targetRequest, irModule);
+
// Lower all bit_cast operations on complex types into leaf-level
// bit_cast on basic types.
lowerBitCast(targetRequest, irModule);
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index b33565700..de3735a55 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -923,6 +923,8 @@ INST(ExtractTaggedUnionPayload, extractTaggedUnionPayload, 1, 0)
INST(BitCast, bitCast, 1, 0)
INST(Reinterpret, reinterpret, 1, 0)
+INST(OutImplicitCast, outImplicitCast, 1, 0)
+INST(InOutImplicitCast, inOutImplicitCast, 1, 0)
INST(IntCast, intCast, 1, 0)
INST(FloatCast, floatCast, 1, 0)
INST(CastIntToFloat, castIntToFloat, 1, 0)
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index 4c540bfdd..f2c00f406 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -3302,6 +3302,8 @@ public:
IRUndefined* emitUndefined(IRType* type);
IRInst* emitReinterpret(IRInst* type, IRInst* value);
+ IRInst* emitOutImplicitCast(IRInst* type, IRInst* value);
+ IRInst* emitInOutImplicitCast(IRInst* type, IRInst* value);
IRFunc* createFunc();
IRGlobalVar* createGlobalVar(
diff --git a/source/slang/slang-ir-lower-l-value-cast.cpp b/source/slang/slang-ir-lower-l-value-cast.cpp
new file mode 100644
index 000000000..cd03d2bd5
--- /dev/null
+++ b/source/slang/slang-ir-lower-l-value-cast.cpp
@@ -0,0 +1,247 @@
+#include "slang-ir-lower-l-value-cast.h"
+
+#include "slang-ir.h"
+#include "slang-ir-insts.h"
+
+#include "slang-ir-clone.h"
+
+#include "slang-ir-util.h"
+
+namespace Slang
+{
+
+struct LValueCastLoweringContext
+{
+ void _addToWorkList(IRInst* inst)
+ {
+ if (!findOuterGeneric(inst) && !m_workList.contains(inst))
+ {
+ m_workList.add(inst);
+ }
+ }
+
+ void _processInst(IRInst* inst)
+ {
+ switch (inst->getOp())
+ {
+ case kIROp_InOutImplicitCast:
+ case kIROp_OutImplicitCast:
+ _processLValueCast(inst);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void processModule()
+ {
+ _addToWorkList(m_module->getModuleInst());
+
+ while (m_workList.getCount() != 0)
+ {
+ IRInst* inst = m_workList.getLast();
+ m_workList.removeLast();
+
+ _processInst(inst);
+
+ for (auto child = inst->getLastChild(); child; child = child->getPrevInst())
+ {
+ _addToWorkList(child);
+ }
+ }
+ }
+
+ /// True if the conversion from a to b, can be achieved
+ /// via a reinterpret cast/bitcast
+ /// Only some targets can allow such conversions
+ bool _canReinterpretCast(IRType* a, IRType* b)
+ {
+ auto ptrA = as<IRPtrType>(a);
+ auto ptrB = as<IRPtrType>(b);
+
+ // They must both be pointers...
+ SLANG_ASSERT(ptrA && ptrB);
+
+ a = ptrA->getValueType();
+ b = ptrB->getValueType();
+
+ if (a->m_op == b->m_op)
+ {
+ if (auto matA = as<IRMatrixType>(a))
+ {
+ auto matB = static_cast<IRMatrixType*>(b);
+
+ if (getIntVal(matA->getColumnCount()) != getIntVal(matB->getColumnCount()))
+ {
+ return false;
+ }
+
+ a = matA->getElementType();
+ b = matB->getElementType();
+ }
+ else if (auto vecA = as<IRVectorType>(a))
+ {
+ auto vecB = static_cast<IRVectorType*>(b);
+
+ if (getIntVal(vecA->getElementCount()) != getIntVal(vecB->getElementCount()))
+ {
+ return false;
+ }
+
+ a = vecA->getElementType();
+ b = vecB->getElementType();
+ }
+ }
+
+ auto basicA = as<IRBasicType>(a);
+ auto basicB = as<IRBasicType>(b);
+
+ if (basicA && basicB)
+ {
+ auto baseA = basicA->getBaseType();
+ auto baseB = basicB->getBaseType();
+
+ const auto& infoA = BaseTypeInfo::getInfo(baseA);
+ const auto& infoB = BaseTypeInfo::getInfo(baseB);
+
+ // We allow reinterpret case for int type conversions of the same bit size for now
+ if (infoA.sizeInBytes == infoB.sizeInBytes &&
+ (infoA.flags & infoB.flags & BaseTypeInfo::Flag::Integer))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// True if for HLSL the cast can be removed entirely
+ bool _canRemoveCastForHLSL(IRType* a, IRType* b)
+ {
+ // Currently _canReinterpret is exactly the same class of types that we can just ignore the cast totally
+ // for HLSL
+ // If _canReinterpretCast changes, this will need to be updated
+ return _canReinterpretCast(a, b);
+ }
+
+ void _processLValueCast(IRInst* castInst)
+ {
+ auto castOperand = castInst->getOperand(0);
+ auto fromType = castOperand->getDataType();
+ auto toType = castInst->getDataType();
+
+ switch (m_intermediateSourceLanguage)
+ {
+ case SourceLanguage::HLSL:
+ {
+ // If the conversion can just be ignored for HLSL, just remove it
+ if (_canRemoveCastForHLSL(fromType, toType))
+ {
+ castInst->replaceUsesWith(castOperand);
+ castInst->removeAndDeallocate();
+ return;
+ }
+ break;
+ }
+ case SourceLanguage::C:
+ case SourceLanguage::CPP:
+ case SourceLanguage::CUDA:
+ {
+ // For languages with pointers, out parameter differences can *sometimes* just be sidestepped with
+ // a reinterpret cast.
+ if (_canReinterpretCast(fromType, toType))
+ {
+ return;
+ }
+ break;
+ }
+ default: break;
+ }
+
+ // If we can't use the other mechanisms we are going to do a conversion
+ // via a cast into a temporary of the approprite time before the useSite,
+ // then immediately after converting back into the original location.
+ //
+ // With a special case for uses which are just out - where we don't need to
+ // convert in.
+
+ // Okay we are going to replace the implicit casts with temporaries around call sites/uses.
+ List<IRInst*> useSites;
+ for (auto use = castInst->firstUse; use; use = use->nextUse)
+ {
+ auto useSite = use->getUser();
+
+ if (useSites.indexOf(useSite) < 0)
+ {
+ useSites.add(useSite);
+ }
+ }
+
+ // If there is a name hint on the source, we'll copy it over to the temporaries
+ auto nameHintDecoration = castOperand->findDecoration<IRNameHintDecoration>();
+
+ IRBuilder builder(m_module);
+
+ IRType* toValueType = as<IRPtrType>(toType)->getValueType();
+ IRType* fromValueType = as<IRPtrType>(fromType)->getValueType();
+
+ for (auto useSite : useSites)
+ {
+ builder.setInsertBefore(useSite);
+ auto tmpVar = builder.emitVar(toValueType);
+
+ if (nameHintDecoration)
+ {
+ cloneDecoration(nameHintDecoration, tmpVar);
+ }
+
+ // If it's inout we convert via cast whats in the castOperand
+ if (castInst->getOp() == kIROp_InOutImplicitCast)
+ {
+ builder.emitStore(tmpVar, builder.emitCast(toValueType, builder.emitLoad(castOperand)));
+ }
+
+ // Convert the temporary back to the original location
+ builder.setInsertAfter(useSite);
+ builder.emitStore(castOperand, builder.emitCast(fromValueType, builder.emitLoad(tmpVar)));
+
+ // Go through all of the operands of the use inst relacing, with the temporary
+ const auto operandCount = Count(useSite->getOperandCount());
+ auto operands = useSite->getOperands();
+
+ for (Index i = 0; i < operandCount; ++i)
+ {
+ auto& callSiteOperand = operands[i];
+ if(callSiteOperand.get() == castInst)
+ {
+ callSiteOperand.set(tmpVar);
+ }
+ }
+ }
+
+ // When we are done we can destroy the inst
+ castInst->removeAndDeallocate();
+ }
+
+ LValueCastLoweringContext(TargetRequest* targetRequest, IRModule* module):
+ m_targetReq(targetRequest),
+ m_module(module)
+ {
+ m_intermediateSourceLanguage = getIntermediateSourceLanguageForTarget(targetRequest);
+ }
+
+ // The intermediate source language used to produce code for the target.
+ // If no intermediate source language is used will be SourceLanguage::Unknown.
+ SourceLanguage m_intermediateSourceLanguage = SourceLanguage::Unknown;
+ TargetRequest* m_targetReq;
+ IRModule* m_module;
+ OrderedHashSet<IRInst*> m_workList;
+};
+
+void lowerLValueCast(TargetRequest* targetReq, IRModule* module)
+{
+ LValueCastLoweringContext context(targetReq, module);
+ context.processModule();
+}
+
+} // namespace Slang
diff --git a/source/slang/slang-ir-lower-l-value-cast.h b/source/slang/slang-ir-lower-l-value-cast.h
new file mode 100644
index 000000000..3d93d1200
--- /dev/null
+++ b/source/slang/slang-ir-lower-l-value-cast.h
@@ -0,0 +1,24 @@
+#ifndef SLANG_IR_LOWER_L_VALUE_CAST_H
+#define SLANG_IR_LOWER_L_VALUE_CAST_H
+
+// This defines an IR pass that lowers LValue implicit casts. These are typically formed
+// when an in/inout paramter is passed a type that doesn't match.
+//
+// Depending on the target this could produce
+//
+// * Nothing - some kinds of casts are implicit for some targets such as HLSL on out parameters for same sized integer types
+// * A reinterpret cast. On targets with pointers, such as C++/CUDA we can fix the problem by just casting to the appropriate pointer (for some kinds of conversions)
+// * Creating a temporary of the right type and calling the function, and *converting* to the target (say an out parameter)
+// * Creating a temporary, converting the value into the temporary, calling the function, and converting back to the source
+
+namespace Slang
+{
+
+struct IRModule;
+class TargetRequest;
+
+void lowerLValueCast(TargetRequest* targetReq, IRModule* module);
+
+} // namespace Slang
+
+#endif
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index fb121d245..789349b4c 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -3130,7 +3130,14 @@ namespace Slang
{
return emitIntrinsicInst((IRType*)type, kIROp_Reinterpret, 1, &value);
}
-
+ IRInst* IRBuilder::emitInOutImplicitCast(IRInst* type, IRInst* value)
+ {
+ return emitIntrinsicInst((IRType*)type, kIROp_InOutImplicitCast, 1, &value);
+ }
+ IRInst* IRBuilder::emitOutImplicitCast(IRInst* type, IRInst* value)
+ {
+ return emitIntrinsicInst((IRType*)type, kIROp_OutImplicitCast, 1, &value);
+ }
IRLiveRangeStart* IRBuilder::emitLiveRangeStart(IRInst* referenced)
{
// This instruction doesn't produce any result,
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index 58768f2ad..85b17dafb 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -4677,6 +4677,34 @@ struct LValueExprLoweringVisitor : ExprLoweringVisitorBase<LValueExprLoweringVis
{
static bool _isLValueContext() { return true; }
+ LoweredValInfo visitLValueImplicitCastExpr(LValueImplicitCastExpr* expr)
+ {
+ auto builder = getBuilder();
+
+ auto irType = lowerType(context, expr->type);
+ auto irPtrType = builder->getPtrType(irType);
+
+ auto loweredArg = lowerLValueExpr(context, expr->arguments[0]);
+
+ // It should be a ptr, because it is a LValue
+ SLANG_ASSERT(loweredArg.flavor == LoweredValInfo::Flavor::Ptr);
+
+ // We have the irValue (which should be a Ptr because it's an LValue)
+ auto irLValue = loweredArg.val;
+
+ IRInst* irCast = nullptr;
+ if (as<OutImplicitCastExpr>(expr))
+ {
+ irCast = builder->emitOutImplicitCast(irPtrType, irLValue);
+ }
+ else
+ {
+ irCast = builder->emitInOutImplicitCast(irPtrType, irLValue);
+ }
+
+ return LoweredValInfo::ptr(irCast);
+ }
+
// When visiting a swizzle expression in an l-value context,
// we need to construct a "swizzled l-value."
LoweredValInfo visitMatrixSwizzleExpr(MatrixSwizzleExpr* expr)
diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp
index 29cf86f5e..4d52b76eb 100644
--- a/source/slang/slang-type-layout.cpp
+++ b/source/slang/slang-type-layout.cpp
@@ -1689,6 +1689,62 @@ bool isCUDATarget(TargetRequest* targetReq)
}
}
+SourceLanguage getIntermediateSourceLanguageForTarget(TargetRequest* req)
+{
+ // If we are emitting directly, there is no intermediate source language
+ if (req->shouldEmitSPIRVDirectly())
+ {
+ return SourceLanguage::Unknown;
+ }
+
+ switch (req->getTarget())
+ {
+ case CodeGenTarget::GLSL:
+ case CodeGenTarget::GLSL_Vulkan:
+ case CodeGenTarget::GLSL_Vulkan_OneDesc:
+ // If we aren't emitting directly we are going to output GLSL to feed to GLSLANG
+ case CodeGenTarget::SPIRV:
+ case CodeGenTarget::SPIRVAssembly:
+ {
+ return SourceLanguage::GLSL;
+ }
+ case CodeGenTarget::HLSL:
+ case CodeGenTarget::DXBytecode:
+ case CodeGenTarget::DXBytecodeAssembly:
+ case CodeGenTarget::DXIL:
+ case CodeGenTarget::DXILAssembly:
+ {
+ // Currently DXBytecode and DXIL are generated via HLSL
+ return SourceLanguage::HLSL;
+ }
+ case CodeGenTarget::CSource:
+ {
+ return SourceLanguage::C;
+ }
+ case CodeGenTarget::ShaderSharedLibrary:
+ case CodeGenTarget::ObjectCode:
+ case CodeGenTarget::HostExecutable:
+ case CodeGenTarget::HostHostCallable:
+ case CodeGenTarget::ShaderHostCallable:
+ case CodeGenTarget::CPPSource:
+ case CodeGenTarget::HostCPPSource:
+ case CodeGenTarget::PyTorchCppBinding:
+ {
+ // For CPU based scenarios are generated via C++
+ return SourceLanguage::CPP;
+ }
+ case CodeGenTarget::CUDAObjectCode:
+ case CodeGenTarget::CUDASource:
+ case CodeGenTarget::PTX:
+ {
+ return SourceLanguage::CUDA;
+ }
+ default: break;
+ }
+
+ return SourceLanguage::Unknown;
+}
+
bool areResourceTypesBindlessOnTarget(TargetRequest* targetReq)
{
return isCPUTarget(targetReq) || isCUDATarget(targetReq);
diff --git a/tests/bugs/op-assignment-unify-mat.slang b/tests/bugs/op-assignment-unify-mat.slang
new file mode 100644
index 000000000..13b6ecf48
--- /dev/null
+++ b/tests/bugs/op-assignment-unify-mat.slang
@@ -0,0 +1,47 @@
+// We can't test on VK, as currently we don't support integer matrix types
+//DISABLE_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj -vk
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj -cpu
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name outputBuffer
+RWStructuredBuffer<uint> outputBuffer;
+
+void silly<T : __BuiltinIntegerType, let ROWS : int, let COLS : int>(matrix<T, ROWS, COLS> a, inout matrix<T, ROWS, COLS> b)
+{
+ b *= a;
+}
+
+void silly2<T : __BuiltinIntegerType, let ROWS : int, let COLS : int>(matrix<T, ROWS, COLS> a, out matrix<T, ROWS, COLS> b)
+{
+ b = a;
+}
+
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ matrix<int, 2, 2> v = { 1, 2, 3, 4};
+ matrix<uint, 2, 2> u = { 5, 6, 7, 8};
+
+ int idx = int(dispatchThreadID.x);
+
+ matrix<uint, 2, 2> uidx = { idx, idx * idx, idx + idx, idx + 2 };
+
+ v += uidx;
+ u += idx;
+
+ silly(u, v);
+ silly(v, u);
+
+ // Check that detects issues with undefined variables.
+
+ matrix<int, 2, 2> undef1, undef2; // undefined
+ // Downstream compilers detect this
+ //silly(u, undef1);
+ silly2(u, undef2);
+
+ matrix<uint, 2, 2> added = (u + v + undef2);
+ uint2 added2 = added[0] + added[1];
+
+ outputBuffer[idx] = added2.x + added2.y;
+}
diff --git a/tests/bugs/op-assignment-unify-mat.slang.expected.txt b/tests/bugs/op-assignment-unify-mat.slang.expected.txt
new file mode 100644
index 000000000..f5973ac46
--- /dev/null
+++ b/tests/bugs/op-assignment-unify-mat.slang.expected.txt
@@ -0,0 +1,4 @@
+53E
+92C
+FA8
+19C0
diff --git a/tests/bugs/op-assignment-unify-vec.slang b/tests/bugs/op-assignment-unify-vec.slang
new file mode 100644
index 000000000..4cc3154a0
--- /dev/null
+++ b/tests/bugs/op-assignment-unify-vec.slang
@@ -0,0 +1,47 @@
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj -vk
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj -cpu
+
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0], stride=4):out,name outputBuffer
+RWStructuredBuffer<uint> outputBuffer;
+
+
+void silly<T : __BuiltinIntegerType, let N : int>(vector<T, N> a, inout vector<T, N> b)
+{
+ b *= a;
+}
+
+void silly2<T : __BuiltinIntegerType, let N : int>(vector<T, N> a, out vector<T, N> b)
+{
+ b = a;
+}
+
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ int2 v = int2(0, 0);
+ uint2 u = uint2(0, 0);
+
+ int idx = int(dispatchThreadID.x);
+
+ uint2 uidx = uint2(idx, idx * idx);
+
+ v += uidx;
+ u += idx;
+
+ silly(u, v);
+ silly(v, u);
+
+ // Check that detects issues with undefined variables.
+
+ int2 undef1, undef2; // undefined
+ // Downstream compilers detect this
+ //silly(u, undef1);
+ silly2(u, undef2);
+
+ uint2 added = (u + v + undef2);
+
+ outputBuffer[idx] = added.x + added.y;
+}
diff --git a/tests/bugs/op-assignment-unify-vec.slang.expected.txt b/tests/bugs/op-assignment-unify-vec.slang.expected.txt
new file mode 100644
index 000000000..d92319a24
--- /dev/null
+++ b/tests/bugs/op-assignment-unify-vec.slang.expected.txt
@@ -0,0 +1,6 @@
+0
+6
+3C
+FC
+0
+0
diff --git a/tests/bugs/op-assignment-unify.slang b/tests/bugs/op-assignment-unify.slang
new file mode 100644
index 000000000..738b49677
--- /dev/null
+++ b/tests/bugs/op-assignment-unify.slang
@@ -0,0 +1,44 @@
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj -vk
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -shaderobj -cpu
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0], stride=4):out,name outputBuffer
+RWStructuredBuffer<uint> outputBuffer;
+
+
+void silly<T : __BuiltinIntegerType>(T a, inout T b)
+{
+ b *= a;
+}
+
+void silly2<T : __BuiltinIntegerType>(T a, out T b)
+{
+ b = a;
+}
+
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ int v = 0;
+ uint u = 0;
+
+ int idx = int(dispatchThreadID.x);
+ uint uidx = uint(idx);
+
+ v += uidx;
+ u += idx;
+
+ silly(u, v);
+ silly(v, u);
+
+ // Check that detects issues with undefined variables.
+
+ int undef1, undef2; // undefined
+
+ // Downstream compilers detect this...
+ //silly(u, undef1);
+ silly2(u, undef2);
+
+ outputBuffer[idx] = u + v + undef2;
+}
diff --git a/tests/bugs/op-assignment-unify.slang.expected.txt b/tests/bugs/op-assignment-unify.slang.expected.txt
new file mode 100644
index 000000000..539ab696c
--- /dev/null
+++ b/tests/bugs/op-assignment-unify.slang.expected.txt
@@ -0,0 +1,6 @@
+0
+3
+14
+3F
+0
+0
diff --git a/tests/diagnostics/implicit-cast-lvalue.slang b/tests/diagnostics/implicit-cast-lvalue.slang
index 79b619443..daf196378 100644
--- a/tests/diagnostics/implicit-cast-lvalue.slang
+++ b/tests/diagnostics/implicit-cast-lvalue.slang
@@ -1,15 +1,20 @@
//DIAGNOSTIC_TEST:SIMPLE:
+
+// Generally speaking:
// Passing an argument for an `out` parameter such
// that implicit conversion would be required in
// both directions.
+//
+// But we do have special case code to handle uint/int of the same type and scalar/vector/matrix forms.
+// So for this to fail we need to use floats or something else
-void a(out uint x)
+void a(out float x)
{
x = 0;
}
-void b(int y)
+void b(double y)
{
a(y);
}
diff --git a/tests/diagnostics/implicit-cast-lvalue.slang.expected b/tests/diagnostics/implicit-cast-lvalue.slang.expected
index 3fdd846a5..920ad17c2 100644
--- a/tests/diagnostics/implicit-cast-lvalue.slang.expected
+++ b/tests/diagnostics/implicit-cast-lvalue.slang.expected
@@ -1,9 +1,12 @@
result code = -1
standard error = {
-tests/diagnostics/implicit-cast-lvalue.slang(14): error 30047: argument passed to parameter '0' must be l-value.
+tests/diagnostics/implicit-cast-lvalue.slang(19): warning 30081: implicit conversion from 'double' to 'float' is not recommended
a(y);
^
-tests/diagnostics/implicit-cast-lvalue.slang(14): note 30048: argument was implicitly cast from 'int' to 'uint', and Slang does not support using an implicit cast as an l-value
+tests/diagnostics/implicit-cast-lvalue.slang(19): error 30047: argument passed to parameter '0' must be l-value.
+ a(y);
+ ^
+tests/diagnostics/implicit-cast-lvalue.slang(19): note 30048: argument was implicitly cast from 'double' to 'float', and Slang does not support using an implicit cast as an l-value
}
standard output = {
}