summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2025-10-07 08:53:36 -0700
committerGitHub <noreply@github.com>2025-10-07 15:53:36 +0000
commit54e1d02715747ee81585bfd23e96a1f4956dbf66 (patch)
tree83dfc746883b6b7aee0018bb55d4aea3095400e8
parent0dac20642b971191256e058282811cf9307292e7 (diff)
Fix a bug that causes a struct field to be initialized twice. (#8619)
We insert field initialization logic at the beginning of every ctor in `synthesizeCtorBody`, but then immediately inserts another round of initialization again for explicit ctors in `maybeInsertDefaultInitExpr`, both called from `SemanticsDeclBodyVisitor::visitAggTypeDecl` right next to each other. The fix is to remove `maybeInsertDefaultInitExpr`. This change also enhances the address aliasing analysis, so that for the following case: ``` this->member1 = 0; this->member2 = 0; this->member1 = param; ``` We can still remove the first assignment to `this->member1` despite seeing `this->member2=0`, since it is easy to know that `this->member2` cannot alias with `this->member1`. Closes #8600.
-rw-r--r--source/slang/slang-check-decl.cpp47
-rw-r--r--source/slang/slang-ir-util.cpp65
-rw-r--r--tests/optimization/func-resource-result/init-copy-eliminate.slang40
3 files changed, 94 insertions, 58 deletions
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index fa31c54bd..4bfbde584 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -614,7 +614,6 @@ private:
Decl* member,
Expr* rightHandSideExpr);
Expr* createCtorParamExpr(ConstructorDecl* ctor, Index paramIndex);
- void maybeInsertDefaultInitExpr(StructDecl* structDecl);
};
template<typename VisitorType>
@@ -9918,51 +9917,6 @@ void SemanticsDeclBodyVisitor::synthesizeCtorBodyForMemberVar(
seqStmtChild->stmts.add(stmt);
}
-// This function inserts the default initialization expression for every member who
-// has init expression at beginning of the user-defined ctor. We don't need to do this
-// for synthesized ctor, because synthesized ctor already handle this at the function
-// parameters
-void SemanticsDeclBodyVisitor::maybeInsertDefaultInitExpr(StructDecl* structDecl)
-{
- // If there is no explicit constructor, we don't need to do anything.
- if (!_hasExplicitConstructor(structDecl, false))
- return;
-
- // traverse all the constructors and insert the default initialization expressions.
- for (auto ctor : structDecl->getMembersOfType<ConstructorDecl>())
- {
- ThisExpr* thisExpr = m_astBuilder->create<ThisExpr>();
- thisExpr->scope = ctor->ownedScope;
- thisExpr->type = ctor->returnType.type;
- auto seqStmtChild = m_astBuilder->create<SeqStmt>();
-
- for (auto varDeclBase : structDecl->getDirectMemberDeclsOfType<VarDeclBase>())
- {
- if (varDeclBase->hasModifier<HLSLStaticModifier>() ||
- varDeclBase->getName() == nullptr || varDeclBase->initExpr == nullptr)
- continue;
-
- auto assign = createMemberAssignmentExpr(
- thisExpr,
- ctor->ownedScope,
- varDeclBase,
- varDeclBase->initExpr);
- if (!assign)
- continue;
-
- auto stmt = m_astBuilder->create<ExpressionStmt>();
- stmt->expression = assign;
- stmt->loc = varDeclBase->loc;
- seqStmtChild->stmts.add(stmt);
- }
- if (seqStmtChild->stmts.getCount() != 0)
- {
- auto seqStmt = _ensureCtorBodyIsSeqStmt(m_astBuilder, ctor);
- seqStmt->stmts.insert(0, seqStmtChild);
- }
- }
-}
-
void SemanticsDeclBodyVisitor::synthesizeCtorBody(
DeclAndCtorInfo& structDeclInfo,
List<DeclAndCtorInfo>& inheritanceDefaultCtorList,
@@ -10063,7 +10017,6 @@ void SemanticsDeclBodyVisitor::visitAggTypeDecl(AggTypeDecl* aggTypeDecl)
}
synthesizeCtorBody(structDeclInfo, inheritanceDefaultCtorList, structDecl);
- maybeInsertDefaultInitExpr(structDecl);
if (structDeclInfo.defaultCtor)
{
diff --git a/source/slang/slang-ir-util.cpp b/source/slang/slang-ir-util.cpp
index 9bec464dc..5745ef85f 100644
--- a/source/slang/slang-ir-util.cpp
+++ b/source/slang/slang-ir-util.cpp
@@ -800,6 +800,7 @@ IRInst* getRootAddr(IRInst* addr, List<IRInst*>& outAccessChain, List<IRInst*>*
return addr;
}
+
IRInst* getRootBufferOrAddr(IRInst* addr)
{
auto rootAddr = getRootAddr(addr);
@@ -953,11 +954,11 @@ bool canAddressesPotentiallyAlias(
if (addr1 == addr2)
return true;
- addr1 = getRootBufferOrAddr(addr1);
- addr2 = getRootBufferOrAddr(addr2);
+ auto root1 = getRootBufferOrAddr(addr1);
+ auto root2 = getRootBufferOrAddr(addr2);
- auto addr1Class = getAliasingClass(addr1);
- auto addr2Class = getAliasingClass(addr2);
+ auto addr1Class = getAliasingClass(root1);
+ auto addr2Class = getAliasingClass(root2);
if (!canAddrClassesAlias(addr1Class, addr2Class))
return false;
@@ -974,17 +975,17 @@ bool canAddressesPotentiallyAlias(
case AddressAliasingClass::BoundBuffer:
case AddressAliasingClass::BoundTexture:
case AddressAliasingClass::ConstantBuffer:
- if (addr1 != addr2)
+ if (root1 != root2)
return false;
break;
}
}
// A param and a var can never alias.
- if (addr1->getOp() == kIROp_Param && addr1->getParent() == func->getFirstBlock() &&
- addr2->getOp() == kIROp_Var ||
- addr1->getOp() == kIROp_Var && addr2->getOp() == kIROp_Param &&
- addr2->getParent() == func->getFirstBlock())
+ if (root1->getOp() == kIROp_Param && root1->getParent() == func->getFirstBlock() &&
+ root2->getOp() == kIROp_Var ||
+ root1->getOp() == kIROp_Var && root2->getOp() == kIROp_Param &&
+ root2->getParent() == func->getFirstBlock())
return false;
// If one addr is user pointer and one addr is a var,
@@ -992,11 +993,53 @@ bool canAddressesPotentiallyAlias(
// the var.
if (addr1Class == AddressAliasingClass::Var && addr2Class == AddressAliasingClass::UserPointer)
{
- return canVarAliasWithUserPointer(target, addr1);
+ return canVarAliasWithUserPointer(target, root1);
}
if (addr2Class == AddressAliasingClass::Var && addr1Class == AddressAliasingClass::UserPointer)
{
- return canVarAliasWithUserPointer(target, addr2);
+ return canVarAliasWithUserPointer(target, root2);
+ }
+
+ // If two addrs are rooted from the same object but found to statically differ in access chain,
+ // then they cannot alias.
+ if (root1 == root2)
+ {
+ List<IRInst*> accessChain1;
+ List<IRInst*> accessChain2;
+
+ // Since getRootBufferOrAddr has a different behavior around
+ // RWStructuredBufferGetElementPtr compared to getRootAddr,
+ // we need to call getRootAddr here again to get a simpler access chain
+ // that we can handle here, so that we don't need to handle the nuance
+ // of whether or not to trace past any RWStructuredBufferGetElementPtr.
+ //
+ root1 = getRootAddr(addr1, accessChain1, nullptr);
+ root2 = getRootAddr(addr2, accessChain2, nullptr);
+ if (root1 != root2)
+ return true;
+ for (Index i = 0; i < Math::Min(accessChain1.getCount(), accessChain2.getCount()); i++)
+ {
+ auto node1 = accessChain1[i];
+ auto node2 = accessChain2[i];
+ if (as<IRStructKey>(node1) && as<IRStructKey>(node2))
+ {
+ // Two different field keys means the two addresses cannot alias.
+ // TODO: If we are going to support union types, we need to exclude that
+ // here.
+ if (node1 != node2)
+ return false;
+ // If the keys are the same, continue looking further down the access chain.
+ continue;
+ }
+ // Two different constant indices means the two addresses cannot alias.
+ auto index1 = as<IRIntLit>(node1);
+ auto index2 = as<IRIntLit>(node2);
+ if (index1 && index2 && index1->getValue() != index2->getValue())
+ return false;
+ // In all other cases, such as when either one of the indices is
+ // a untime value, we treat the two indices as potentially being the same.
+ return true;
+ }
}
return true;
}
diff --git a/tests/optimization/func-resource-result/init-copy-eliminate.slang b/tests/optimization/func-resource-result/init-copy-eliminate.slang
new file mode 100644
index 000000000..c7c8eadcf
--- /dev/null
+++ b/tests/optimization/func-resource-result/init-copy-eliminate.slang
@@ -0,0 +1,40 @@
+//TEST:SIMPLE(filecheck=CHECK):-target cuda -entry computeMain -stage compute
+
+// Test on a regression that caused a field to be initialized twice in the ctor.
+
+// CHECK-COUNT-1: callOnce{{.*}}(int(1000))
+// CHECK-COUNT-1: {{.*}}->origin_0 =
+int callOnce(int x)
+{
+ *result += 1.0f; // cause some side effect so the call is not eliminated
+ return 999;
+}
+
+public struct Ray
+{
+ public int calledOnce = callOnce(1000);
+ public float3 origin = {};
+ public float t_min = 0;
+ public float3 dir = {};
+ public float t_max = 0;
+
+ public __init(float3 origin, float3 dir, float t_min = 0.f, float t_max = 1000.0)
+ {
+ this.origin = origin;
+ this.dir = dir;
+ this.t_min = t_min;
+ this.t_max = t_max;
+ }
+
+ public RayDesc to_ray_desc() { return { origin, t_min, dir, t_max }; }
+};
+
+uniform float* result;
+
+[numthreads(1,1,1)]
+void computeMain()
+{
+ Ray r = Ray(float3(1,2,3), float3(4,5,6));
+ RayDesc rd = r.to_ray_desc();
+ *result = rd.Direction.x;
+}