This is an automated email from the ASF dual-hosted git repository.
tlopex pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-tirx.git
The following commit(s) were added to refs/heads/main by this push:
new 30c842daa [Fix] Preserve effectful operand evaluation in C codegen
(#50)
30c842daa is described below
commit 30c842daa6385ca3c1a1f01daa83d454e40ec334
Author: Shushi Hong <[email protected]>
AuthorDate: Sat Oct 24 20:28:12 2026 -0400
[Fix] Preserve effectful operand evaluation in C codegen (#50)
This PR fixes operand evaluation in C-based codegen by preventing
distinct calls with side effects from being merged, evaluating each
operand once when expanding vector operations, and avoiding stale reads
after writes. It introduces `PrintExprToSSA` to make expression printing
and binding explicit, with minimal C host and CUDA regression tests
checking results and evaluation counts.
---
src/backend/cuda/codegen/codegen_cuda.cc | 70 +++++++++++++------------
src/backend/metal/codegen/codegen_metal.cc | 2 +-
src/backend/opencl/codegen/codegen_opencl.cc | 13 +++--
src/backend/webgpu/codegen/codegen_webgpu.cc | 14 ++---
src/codegen/c/codegen_c.cc | 34 +++++++-----
src/codegen/c/codegen_c.h | 10 ++++
src/codegen/c/codegen_c_host.cc | 10 ++--
src/codegen/c/codegen_source_base.cc | 10 ++--
src/codegen/c/codegen_source_base.h | 8 +--
tests/python/backend/cuda/codegen/test_basic.py | 28 ++++++++++
tests/python/codegen/c/test_basic.py | 23 ++++++++
11 files changed, 145 insertions(+), 77 deletions(-)
diff --git a/src/backend/cuda/codegen/codegen_cuda.cc
b/src/backend/cuda/codegen/codegen_cuda.cc
index 32a91899b..bed6b7d66 100644
--- a/src/backend/cuda/codegen/codegen_cuda.cc
+++ b/src/backend/cuda/codegen/codegen_cuda.cc
@@ -774,21 +774,11 @@ void CodeGenCUDA::PrintMinMaxNanPreservingImpl(const T*
op, const char* opstr,
this->PrintVecBinaryOpNanPreserving(std::string(opstr), op_ty, op->a,
op->b, cmp, os);
return;
}
- // Bind both operands once (SSAGetID, in the expression's own scope: the
- // same binding the host side and PrintVecBinaryOp use), then reference the
- // temporaries: the expressions must not be re-evaluated per clause. Both
- // are bound before the constant classification below, so a constant NaN
- // lhs still evaluates the rhs and only the select is dropped. A
- // float32/float64 constant comes back from SSAGetID as itself (PrintConst
- // registers its text with MarkConst); a float16/bfloat16 constant is
- // wrapped in a conversion call that is not registered and gets a temporary
- // like any other operand. The bindings live in their own scope so a later
- // statement that prints the same text (e.g.
- // `red_buf[0] = max(red_buf[0], shuffle_down(...))` repeated by warp
- // reduction) does not hit the cache and read the pre-write value.
+ // Evaluate both operands before simplifying the result. Even a constant NaN
+ // lhs must preserve effects in the rhs; repeated clauses use the bound
values.
int ssa_scope = BeginScope();
- std::string va = SSAGetID(PrintExpr(op->a), op->a.ty());
- std::string vb = SSAGetID(PrintExpr(op->b), op->b.ty());
+ std::string va = PrintExprToSSA(op->a);
+ std::string vb = PrintExprToSSA(op->b);
const FloatImmObj* ca = AsFloatImm(op->a);
const FloatImmObj* cb = AsFloatImm(op->b);
if (ca != nullptr && std::isnan(ca->value)) {
@@ -830,8 +820,8 @@ void CodeGenCUDA::PrintVecBinaryOpNanPreserving(const
std::string& op, const Pri
stream << ' ' << sret << ";\n";
int ssa_scope = BeginScope();
{
- std::string vlhs = SSAGetID(PrintExpr(lhs), lhs.ty());
- std::string vrhs = SSAGetID(PrintExpr(rhs), rhs.ty());
+ std::string vlhs = PrintExprToSSA(lhs);
+ std::string vrhs = PrintExprToSSA(rhs);
for (int i = 0, lanes = t.lanes(); i < lanes; ++i) {
std::ostringstream value_temp;
value_temp << "((";
@@ -863,8 +853,8 @@ void CodeGenCUDA::PrintVecBinaryOp(const std::string& op,
const PrimType& t, Pri
int ssa_scope = BeginScope();
{
// Unpack into individual ops.
- std::string vlhs = SSAGetID(PrintExpr(lhs), lhs.ty());
- std::string vrhs = SSAGetID(PrintExpr(rhs), rhs.ty());
+ std::string vlhs = PrintExprToSSA(lhs);
+ std::string vrhs = PrintExprToSSA(rhs);
for (int i = 0, lanes = t.lanes(); i < lanes; ++i) {
std::ostringstream value_temp;
@@ -1087,7 +1077,7 @@ void CodeGenCUDA::Dispatch_(const prim::CastObj* op,
std::ostream& os) {
this->PrintType(target_ty, stream);
stream << ' ' << sret << ";\n";
{
- std::string src = SSAGetID(PrintExpr(op->value), from_ty);
+ std::string src = PrintExprToSSA(op->value);
for (int i = 0, lanes = from_ty.lanes(); i < lanes; ++i) {
std::ostringstream val;
val << "(";
@@ -1136,7 +1126,7 @@ void CodeGenCUDA::PrintCallExtern(Type ret_type,
ffi::String global_symbol,
size_t arg_begin = static_cast<size_t>(skip_first_arg);
for (size_t i = arg_begin; i < args.size(); ++i) {
Expr arg = args[i];
- std::string val = SSAGetID(PrintExpr(arg), arg->ty);
+ std::string val = PrintExprToSSA(arg);
sargs.push_back(std::move(val));
}
@@ -1180,9 +1170,20 @@ void CodeGenCUDA::Dispatch_(const CallObj* op,
std::ostream& os) {
if (op->op.same_as(prim::isnan_op()) &&
op->ty.as_or_throw<PrimType>().lanes() > 1) {
// CUDA vector comparisons do not produce a lane-wise boolean vector.
- // Reuse the scalar comparison path for every lane, including packed half.
+ // Evaluate the input once, then compare each lane with itself.
PrimExpr value = op->args[0].as_or_throw<PrimExpr>();
- PrintVecBinaryOp("!=", op->ty.as_or_throw<PrimType>(), value, value, os);
+ std::string input = PrintExprToSSA(value);
+ PrimType result_ty = op->ty.as_or_throw<PrimType>();
+ std::string result = name_supply_->FreshName("_");
+ PrintIndent();
+ PrintType(result_ty, stream);
+ stream << ' ' << result << ";\n";
+ for (int i = 0; i < result_ty.lanes(); ++i) {
+ std::ostringstream lane;
+ PrintVecElemLoad(input, value.ty(), i, lane);
+ PrintVecElemStore(result, result_ty, i, "(" + lane.str() + " != " +
lane.str() + ")");
+ }
+ os << result;
return;
}
if (auto opt_call_opt = op->op.as<Op>()) {
@@ -1293,7 +1294,7 @@ void CodeGenCUDA::Dispatch_(const CallObj* op,
std::ostream& os) {
if (lanes == 1) {
// The case of lane=1 is same as the normal reinterpret,
// except that we allow the src and dst dtype to have different number
of bits.
- std::string rhs = SSAGetID(PrintExpr(value), src_ty);
+ std::string rhs = PrintExprToSSA(value);
os << "(*(";
this->PrintType(tgt_ty, os);
os << " *)(&(" << rhs << ")))";
@@ -1420,11 +1421,12 @@ void CodeGenCUDA::Dispatch_(const prim::RampObj* op,
std::ostream& os) {
PrimType op_ty = op->ty.as_or_throw<PrimType>();
int lanes = op_ty.lanes();
if (lanes <= 4) {
+ std::string base = PrintExprToSSA(op->base);
+ std::string stride = PrintExprToSSA(op->stride);
PrintVecConstructor(op_ty, os);
os << "(";
for (int i = 0; i < lanes; i++) {
- os << "(" << PrintExpr(op->base) << ")"
- << "+(" << PrintExpr(op->stride) << "*" << i << ")";
+ os << "(" << base << ")+(" << stride << "*" << i << ")";
if (i != lanes - 1) os << ", ";
}
os << ")";
@@ -1439,8 +1441,8 @@ void CodeGenCUDA::Dispatch_(const prim::RampObj* op,
std::ostream& os) {
stream << ' ' << sret << ";\n";
int ssa_scope = BeginScope();
{
- std::string vbase = SSAGetID(PrintExpr(op->base), op->base.ty());
- std::string vstride = SSAGetID(PrintExpr(op->stride), op->stride.ty());
+ std::string vbase = PrintExprToSSA(op->base);
+ std::string vstride = PrintExprToSSA(op->stride);
for (int i = 0; i < lanes; ++i) {
std::ostringstream value_temp;
value_temp << "(" << vbase << ")+(" << vstride << "*" << i << ")";
@@ -1471,7 +1473,7 @@ void CodeGenCUDA::Dispatch_(const prim::BroadcastObj* op,
std::ostream& os) { /
}
if (op_ty.MatchesElementType(DLDataTypeCode::kDLFloat, 16)) {
- std::string v = PrintExpr(op->value);
+ std::string v = PrintExprToSSA(op->value);
PrintVecConstructor(op_ty, os);
os << '(';
if (lanes <= 4) {
@@ -1490,7 +1492,7 @@ void CodeGenCUDA::Dispatch_(const prim::BroadcastObj* op,
std::ostream& os) { /
}
if (op_ty.MatchesElementType(DLDataTypeCode::kDLBfloat, 16)) {
- std::string v = PrintExpr(op->value);
+ std::string v = PrintExprToSSA(op->value);
PrintVecConstructor(op_ty, os);
os << '(';
if (lanes > 4) {
@@ -1510,7 +1512,7 @@ void CodeGenCUDA::Dispatch_(const prim::BroadcastObj* op,
std::ostream& os) { /
if (IsFloat8(op_ty) || IsFloat4(op_ty)) {
TVM_FFI_ICHECK(lanes == 1 || lanes == 2 || lanes == 4);
- std::string v = PrintExpr(op->value);
+ std::string v = PrintExprToSSA(op->value);
// Implicit conversion from float back to fp8
PrintType(op_ty, os);
os << "(make_float" << lanes << "(";
@@ -1566,7 +1568,7 @@ void CodeGenCUDA::Dispatch_(const prim::BroadcastObj* op,
std::ostream& os) { /
}
}
- std::string v = PrintExpr(op->value);
+ std::string v = PrintExprToSSA(op->value);
PrintVecConstructor(op_ty, os);
os << '(';
for (int i = 0; i < lanes; ++i) {
@@ -1593,9 +1595,9 @@ void CodeGenCUDA::Dispatch_(const prim::SelectObj* op,
std::ostream& os) {
this->PrintType(op_ty, stream);
stream << ' ' << r_var << ";\n";
{
- std::string c_var = SSAGetID(PrintExpr(op->condition), op_ty);
- std::string t_var = SSAGetID(PrintExpr(op->true_value), op_ty);
- std::string f_var = SSAGetID(PrintExpr(op->false_value), op_ty);
+ std::string c_var = PrintExprToSSA(op->condition);
+ std::string t_var = PrintExprToSSA(op->true_value);
+ std::string f_var = PrintExprToSSA(op->false_value);
// The condition is stored as an ushort vector.
int lanes = op_ty.lanes();
diff --git a/src/backend/metal/codegen/codegen_metal.cc
b/src/backend/metal/codegen/codegen_metal.cc
index 07ca9bd7a..43b8297a9 100644
--- a/src/backend/metal/codegen/codegen_metal.cc
+++ b/src/backend/metal/codegen/codegen_metal.cc
@@ -449,7 +449,7 @@ void CodeGenMetal::Dispatch_(const prim::SelectObj* op,
std::ostream& os) { //
}
void CodeGenMetal::Dispatch_(const prim::BroadcastObj* op, std::ostream& os) {
// NOLINT(*)
- std::string v = PrintExpr(op->value);
+ std::string v = PrintExprToSSA(op->value);
int lanes = op->ty.as_or_throw<PrimType>().lanes();
PrintType(op->ty.as_or_throw<PrimType>(), os);
os << "(";
diff --git a/src/backend/opencl/codegen/codegen_opencl.cc
b/src/backend/opencl/codegen/codegen_opencl.cc
index eb23638b2..9b3a577bd 100644
--- a/src/backend/opencl/codegen/codegen_opencl.cc
+++ b/src/backend/opencl/codegen/codegen_opencl.cc
@@ -516,7 +516,8 @@ void CodeGenOpenCL::Dispatch_(const CallObj* op,
std::ostream& os) {
std::stringstream ss;
this->PrintExpr(op->args[5].as_or_throw<PrimExpr>(), ss);
std::string value;
- value = this->SSAGetID(ss.str(), buffer_type.WithLanes(channel_size /
buffer_type.bits()));
+ value = this->SSAGetID(ss.str(), buffer_type.WithLanes(channel_size /
buffer_type.bits()),
+ SideEffect(op->args[5]) == CallEffectKind::kPure);
if (channel_size == 64) {
os << "write_imageh(";
} else if (channel_size == 128) {
@@ -571,7 +572,8 @@ void CodeGenOpenCL::Dispatch_(const CallObj* op,
std::ostream& os) {
this->PrintExpr(IntImm::Int32(0), ss);
ss << "))))";
- std::string rhs = SSAGetID(ss.str(), op_ty.WithLanes(data_lanes));
+ // An earlier image read may precede a write to the same texture.
+ std::string rhs = SSAGetID(ss.str(), op_ty.WithLanes(data_lanes),
/*can_reuse=*/false);
if (auto ramp = op->args.back().as<prim::RampObj>()) {
const auto* base = ramp->base.as<IntImmObj>();
const auto* lanes_imm = ramp->lanes.as<IntImmObj>();
@@ -622,7 +624,7 @@ void CodeGenOpenCL::Dispatch_(const CallObj* op,
std::ostream& os) {
}
void CodeGenOpenCL::Dispatch_(const prim::BroadcastObj* op, std::ostream& os)
{ // NOLINT(*)
- std::string v = PrintExpr(op->value);
+ std::string v = PrintExprToSSA(op->value);
int lanes = op->ty.as_or_throw<PrimType>().lanes();
os << "((";
PrintType(op->ty.as_or_throw<PrimType>(), os);
@@ -635,13 +637,14 @@ void CodeGenOpenCL::Dispatch_(const prim::BroadcastObj*
op, std::ostream& os) {
}
void CodeGenOpenCL::Dispatch_(const prim::RampObj* op, std::ostream& os) { //
NOLINT(*)
+ std::string base = PrintExprToSSA(op->base);
+ std::string stride = PrintExprToSSA(op->stride);
os << "((";
PrintType(op->ty.as_or_throw<PrimType>(), os);
os << ")(";
int lanes = op->ty.as_or_throw<PrimType>().lanes();
for (int i = 0; i < lanes; i++) {
- os << "(" << PrintExpr(op->base) << ")"
- << "+(" << PrintExpr(op->stride) << "*" << i << ")";
+ os << "(" << base << ")+(" << stride << "*" << i << ")";
if (i != lanes - 1) os << ", ";
}
os << "))";
diff --git a/src/backend/webgpu/codegen/codegen_webgpu.cc
b/src/backend/webgpu/codegen/codegen_webgpu.cc
index ffb60267b..e18fb9ecc 100644
--- a/src/backend/webgpu/codegen/codegen_webgpu.cc
+++ b/src/backend/webgpu/codegen/codegen_webgpu.cc
@@ -384,7 +384,7 @@ void CodeGenWebGPU::BindThreadIndex(const PrimVar& var,
const ffi::String& threa
// We allow runtime to spread the load out to blockIdx.z so it can be a
large number.
os << "(blockIdx.z * gridDim.x + blockIdx.x)";
std::string tidx = os.str();
- std::string aggregated_bidx = SSAGetID(os.str(), var.ty());
+ std::string aggregated_bidx = SSAGetID(os.str(), var.ty(),
/*can_reuse=*/true);
var_idmap_[var.get()] = aggregated_bidx;
} else {
os << "(" << thread_tag << ")";
@@ -472,7 +472,7 @@ void CodeGenWebGPU::PrintVecElemStore(const std::string&
vec, const PrimType& t,
}
void CodeGenWebGPU::Dispatch_(const prim::BroadcastObj* op, std::ostream& os)
{ // NOLINT(*)
- std::string v = PrintExpr(op->value);
+ std::string v = PrintExprToSSA(op->value);
int lanes = op->ty.as_or_throw<PrimType>().lanes();
PrintType(op->ty.as_or_throw<PrimType>(), os);
os << "(";
@@ -660,7 +660,7 @@ void CodeGenWebGPU::Dispatch_(const TensorLoadObj* op,
std::ostream& os) { // N
tvm::PVar<PrimExpr> base;
if (tvm::ramp(base, 1, value_ty.lanes()).Match(index)) {
// vec3<f32>(buf[base + 0], buf[base + 1], buf[base + 2]);
- std::string base_vid = SSAGetID(PrintExpr(base.Eval()),
base.Eval().ty());
+ std::string base_vid = PrintExprToSSA(base.Eval());
PrintType(element_ty.WithLanes(value_ty.lanes()), os);
os << "(";
for (int i = 0; i < lanes; ++i) {
@@ -670,7 +670,7 @@ void CodeGenWebGPU::Dispatch_(const TensorLoadObj* op,
std::ostream& os) { // N
os << ")";
} else {
// vec3<f32>(buf[index[0]], buf[index[1]], buf[index[2]]);
- std::string index_vid = SSAGetID(PrintExpr(index), index.ty());
+ std::string index_vid = PrintExprToSSA(index);
PrintType(element_ty.WithLanes(value_ty.lanes()), os);
os << "(";
for (int i = 0; i < lanes; ++i) {
@@ -742,12 +742,12 @@ void CodeGenWebGPU::Dispatch_(const TensorStoreObj* op) {
TVM_FFI_ICHECK_EQ(element_ty.lanes(), 1) << "Can only vector load scalar
array";
TVM_FFI_ICHECK(value_ty.WithLanes(1) == element_ty)
<< "WebGPU vector stire requires base type to match";
- std::string value_vid = PrintExpr(op->value);
+ std::string value_vid = PrintExprToSSA(op->value);
tvm::PVar<PrimExpr> base;
if (tvm::ramp(base, 1, value_ty.lanes()).Match(index)) {
// buf[base + 0] = value[0]
// buf[base + 1] = value[1]
- std::string base_vid = SSAGetID(PrintExpr(base.Eval()),
base.Eval().ty());
+ std::string base_vid = PrintExprToSSA(base.Eval());
for (int i = 0; i < value_ty.lanes(); ++i) {
this->PrintIndent();
stream << buffer_vid << "[" << base_vid << " + " << i << "] = " <<
value_vid << "[" << i
@@ -756,7 +756,7 @@ void CodeGenWebGPU::Dispatch_(const TensorStoreObj* op) {
} else {
// buf[index[0]] = value[0]
// buf[index[1]] = value[1]
- std::string index_vid = SSAGetID(PrintExpr(index), index.ty());
+ std::string index_vid = PrintExprToSSA(index);
for (int i = 0; i < value_ty.lanes(); ++i) {
this->PrintIndent();
stream << buffer_vid << "[" << index_vid << "[" << i << "]] = " <<
value_vid << "[" << i
diff --git a/src/codegen/c/codegen_c.cc b/src/codegen/c/codegen_c.cc
index 430dff054..bbe009df6 100644
--- a/src/codegen/c/codegen_c.cc
+++ b/src/codegen/c/codegen_c.cc
@@ -42,6 +42,7 @@
#include <cctype>
#include <iomanip>
#include <limits>
+#include <utility>
#include "codegen_params.h"
@@ -225,12 +226,19 @@ void CodeGenC::PrintExpr(const PrimExpr& n, std::ostream&
os) { // NOLINT(*)
if (print_ssa_form_) {
std::ostringstream temp;
Dispatch(n, temp);
- os << SSAGetID(temp.str(), n.ty());
+ os << SSAGetID(temp.str(), n.ty(), SideEffect(n) == CallEffectKind::kPure);
} else {
Dispatch(n, os);
}
}
+std::string CodeGenC::PrintExprToSSA(const Expr& expr) {
+ std::string source = PrintExpr(expr);
+ bool can_reuse = SideEffect(expr) == CallEffectKind::kPure;
+ // Emit a declaration if needed; the caller prints the returned variable or
literal.
+ return CodeGenSourceBase::SSAGetID(std::move(source), expr->ty, can_reuse);
+}
+
void CodeGenC::PrintExpr(const Expr& n, std::ostream& os) { // NOLINT(*)
if (auto prim = n.as<PrimExpr>()) {
PrintExpr(prim.value(), os);
@@ -896,17 +904,14 @@ void CodeGenC::Dispatch_(const CallObj* op, std::ostream&
os) { // NOLINT(*)
source_dtype.lanes() * source_dtype.bits())
<< "reinterpret expects source and target to have the same number of
bits";
int ssa_scope = BeginScope();
- std::string rhs = SSAGetID(PrintExpr(op->args[0]), source_dtype);
+ std::string rhs = PrintExprToSSA(op->args[0]);
os << "(*(";
this->PrintType(target_dtype, os);
os << " *)(&(" << rhs << ")))";
EndScope(ssa_scope);
} else if (op->op.same_as(prim::isnan_op())) {
- os << "(";
- this->PrintExpr(op->args[0], os);
- os << " != ";
- this->PrintExpr(op->args[0], os);
- os << ")";
+ std::string value = PrintExprToSSA(op->args[0]);
+ os << "(" << value << " != " << value << ")";
} else if (op->op.same_as(tirx::gpu_thread_invariant_op())) {
os << "(";
this->PrintExpr(op->args[0], os);
@@ -1040,7 +1045,7 @@ void CodeGenC::Dispatch_(const TensorLoadObj* op,
std::ostream& os) { // NOLINT
HandleVolatileLoads(ref, op, os);
} else {
std::ostringstream svalue_expr;
- std::string sindex = SSAGetID(PrintExpr(index), index.ty());
+ std::string sindex = PrintExprToSSA(index);
std::string vid = GetVarID(buffer_var.get());
PrimType elem_type = value_ty.WithLanes(1);
for (int i = 0; i < lanes; ++i) {
@@ -1095,8 +1100,8 @@ void CodeGenC::Dispatch_(const TensorStoreObj* op) {
int vec_scope = BeginScope();
// store elements separately
- std::string index = SSAGetID(PrintExpr(index_expr), index_expr.ty());
- std::string value = SSAGetID(PrintExpr(op->value), op->value.ty());
+ std::string index = PrintExprToSSA(index_expr);
+ std::string value = PrintExprToSSA(op->value);
std::string vid = GetVarID(buffer_var.get());
for (int i = 0; i < value_ty.lanes(); ++i) {
this->PrintIndent();
@@ -1160,14 +1165,15 @@ void CodeGenC::Dispatch_(const prim::LetObj* op,
std::ostream& os) { // NOLINT(
}
void CodeGenC::Dispatch_(const prim::RampObj* op, std::ostream& os) { //
NOLINT(*)
+ std::string base = PrintExprToSSA(op->base);
+ std::string stride = PrintExprToSSA(op->stride);
// NOTE: C have comma expression so cannot use (int2)(v0, v1)
// instead should use int2(v0, v1)
PrintType(op->ty.as_or_throw<PrimType>(), os);
int lanes = op->ty.as_or_throw<PrimType>().lanes();
os << "(";
for (int i = 0; i < lanes; i++) {
- os << "(" << PrintExpr(op->base) << ")"
- << "+(" << PrintExpr(op->stride) << "*" << i << ")";
+ os << "(" << base << ")+(" << stride << "*" << i << ")";
if (i != lanes - 1) os << ", ";
}
os << ")";
@@ -1188,7 +1194,7 @@ void CodeGenC::Dispatch_(const prim::ShuffleObj* op,
std::ostream& os) { // NOL
// print each elements
if (op->vectors.size() > 1) {
for (const PrimExpr& vec : op->vectors) {
- std::string vec_value = this->PrintExpr(vec);
+ std::string vec_value = PrintExprToSSA(vec);
if (vec.ty().lanes() == 1) {
concat_vec.push_back(vec_value);
} else {
@@ -1203,7 +1209,7 @@ void CodeGenC::Dispatch_(const prim::ShuffleObj* op,
std::ostream& os) { // NOL
}
} else {
// Extract elements from a single vector-type value.
- std::string vec_value = "(" + this->PrintExpr(op->vectors[0]) + ")";
+ std::string vec_value = "(" + PrintExprToSSA(op->vectors[0]) + ")";
if (op->vectors[0].ty().lanes() == 1) {
concat_vec.push_back(vec_value);
} else {
diff --git a/src/codegen/c/codegen_c.h b/src/codegen/c/codegen_c.h
index d3b8212b5..a44ed8755 100644
--- a/src/codegen/c/codegen_c.h
+++ b/src/codegen/c/codegen_c.h
@@ -266,6 +266,16 @@ class CodeGenC : public tvm::ExprFunctor<void(const Expr&,
std::ostream&)>,
}
protected:
+ /*!
+ * \brief Print an expression, bind its value when needed, and return the
variable or literal.
+ * Only pure expressions may reuse an earlier binding. Reads and calls with
+ * effects are evaluated separately on each invocation; repeat the returned
+ * variable when one operand is needed more than once.
+ * \param expr The expression to print.
+ * \return The variable or literal holding the expression's value.
+ */
+ std::string PrintExprToSSA(const Expr& expr);
+
/*! \brief Print a C string literal with proper escaping of special chars. */
void PrintEscapedCString(const std::string& str, std::ostream& os);
// Print reference to struct location
diff --git a/src/codegen/c/codegen_c_host.cc b/src/codegen/c/codegen_c_host.cc
index 92b367fd5..55a917f0a 100644
--- a/src/codegen/c/codegen_c_host.cc
+++ b/src/codegen/c/codegen_c_host.cc
@@ -197,7 +197,7 @@ void CodeGenCHost::PrintType(const PrimType& type,
std::ostream& os) { // NOLIN
}
void CodeGenCHost::Dispatch_(const prim::BroadcastObj* op, std::ostream& os) {
// NOLINT(*)
- std::string v = PrintExpr(op->value);
+ std::string v = PrintExprToSSA(op->value);
int lanes = op->ty.as_or_throw<PrimType>().lanes();
os << "((";
PrintType(op->ty.as_or_throw<PrimType>(), os);
@@ -382,12 +382,8 @@ void CodeGenCHost::Dispatch_(const prim::MaxObj* op,
std::ostream& os) { // NOL
void CodeGenCHost::PrintMinMax(const PrimExpr& lhs, const PrimExpr& rhs, bool
is_min,
std::ostream& os) { // NOLINT(*)
- std::ostringstream temp_lhs;
- Dispatch(lhs, temp_lhs);
- std::string lhs_value = "(" + SSAGetID(temp_lhs.str(), lhs.ty()) + ")";
- std::ostringstream temp_rhs;
- Dispatch(rhs, temp_rhs);
- std::string rhs_value = "(" + SSAGetID(temp_rhs.str(), rhs.ty()) + ")";
+ std::string lhs_value = "(" + PrintExprToSSA(lhs) + ")";
+ std::string rhs_value = "(" + PrintExprToSSA(rhs) + ")";
PrimType dtype = lhs.ty().as_or_throw<PrimType>();
FloatNaNStatus lhs_nan_status = GetFloatNaNStatus(lhs);
FloatNaNStatus rhs_nan_status = GetFloatNaNStatus(rhs);
diff --git a/src/codegen/c/codegen_source_base.cc
b/src/codegen/c/codegen_source_base.cc
index 47096b0a8..1714f51c9 100644
--- a/src/codegen/c/codegen_source_base.cc
+++ b/src/codegen/c/codegen_source_base.cc
@@ -34,13 +34,13 @@ void CodeGenSourceBase::ClearFuncState() {
scope_mark_.clear();
}
-std::string CodeGenSourceBase::SSAGetID(std::string src, const Type& t) {
+std::string CodeGenSourceBase::SSAGetID(std::string src, const Type& t, bool
can_reuse) {
TVM_FFI_ICHECK(t.as<PrimTypeObj>() || t.as<PtrTypeObj>())
<< "Cannot assign an SSA value of type " << t;
if (name_supply_->ContainsName(src)) return src;
- auto it = ssa_assign_map_.find(src);
- if (it != ssa_assign_map_.end()) {
- if (scope_mark_.at(it->second.scope_id)) {
+ if (can_reuse) {
+ auto it = ssa_assign_map_.find(src);
+ if (it != ssa_assign_map_.end() && scope_mark_.at(it->second.scope_id)) {
return it->second.vid;
}
}
@@ -48,7 +48,7 @@ std::string CodeGenSourceBase::SSAGetID(std::string src,
const Type& t) {
// use v_ prefix so it works for most systems
e.vid = name_supply_->FreshName("v_");
e.scope_id = static_cast<int>(scope_mark_.size() - 1);
- ssa_assign_map_[src] = e;
+ if (can_reuse) ssa_assign_map_[src] = e;
this->PrintIndent();
PrintSSAAssign(e.vid, src, t);
return e.vid;
diff --git a/src/codegen/c/codegen_source_base.h
b/src/codegen/c/codegen_source_base.h
index 3f09f8150..4ed1767ab 100644
--- a/src/codegen/c/codegen_source_base.h
+++ b/src/codegen/c/codegen_source_base.h
@@ -99,12 +99,12 @@ class CodeGenSourceBase {
*/
std::string GetVarID(const tvm::VarObj* v) const;
/*!
- * \brief Get the SSA ID corresponds to src
- * If necessary, generate new assignment
- * \param src The source expression
+ * \brief Bind source text to an SSA variable, emitting an assignment if
needed.
+ * \param src The source expression.
* \param t The type of the expression.
+ * \param can_reuse Whether an earlier binding of this text may be reused.
*/
- std::string SSAGetID(std::string src, const Type& t);
+ std::string SSAGetID(std::string src, const Type& t, bool can_reuse);
/*!
* \brief mark the beginning of a new scope
* \return The scope id.
diff --git a/tests/python/backend/cuda/codegen/test_basic.py
b/tests/python/backend/cuda/codegen/test_basic.py
index cc6517ec0..5fa70ba54 100644
--- a/tests/python/backend/cuda/codegen/test_basic.py
+++ b/tests/python/backend/cuda/codegen/test_basic.py
@@ -924,6 +924,34 @@ def test_min_max_nan_preserving_cuda(dtype, form, *,
backend_config):
tvm_tirx.testing.run_with_gpu_lock(run_and_check)
[email protected]
[email protected](not env.has_cuda(), reason="need cuda")
+def test_atomic_operand_evaluation():
+ @T.function
+ def main(P: T.Tensor((1,), "float32"), O: T.Tensor((1,), "float32x4")):
+ with T.launch_thread("threadIdx.x", 1):
+ # Two independent atomics run once each; broadcast copies their
max.
+ O[0] = T.Broadcast(
+ T.max(
+ T.cuda.atomic_add(T.address_of(P[0]), T.float32(1)),
+ T.cuda.atomic_add(T.address_of(P[0]), T.float32(1)),
+ ),
+ 4,
+ )
+
+ executable = tvm_tirx.tirx.compile(main, target="cuda")
+
+ def run_and_check():
+ dev = tvm_tirx.cuda(0)
+ counter = tvm_tirx.runtime.tensor(np.zeros(1, dtype="float32"), dev)
+ output = tvm_tirx.runtime.empty((1,), "float32x4", dev)
+ executable(counter, output)
+ np.testing.assert_array_equal(output.numpy(), [[1] * 4])
+ np.testing.assert_array_equal(counter.numpy(), [2])
+
+ tvm_tirx.testing.run_with_gpu_lock(run_and_check)
+
+
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
def test_round_ties_to_even(*, backend_config):
diff --git a/tests/python/codegen/c/test_basic.py
b/tests/python/codegen/c/test_basic.py
index 2ab26b80f..e0aec80d3 100644
--- a/tests/python/codegen/c/test_basic.py
+++ b/tests/python/codegen/c/test_basic.py
@@ -190,6 +190,29 @@ def test_round():
check_c()
+def test_min_max_reads_after_store():
+ @I.ir_module
+ class Module:
+ # Keep both reads in one SSA cache scope, without public ABI checks.
+ @T.function(private=True)
+ def check(p: T.handle("float32"), o: T.handle("float32")):
+ P = T.decl_tensor(p, ty_args=[T.Tensor((1,), "float32", data=p)])
+ Out = T.decl_tensor(o, ty_args=[T.Tensor((2,), "float32", data=o)])
+ Out[0] = T.max(P[0], T.float32(0))
+ P[0] = 8.0
+ Out[1] = T.max(P[0], T.float32(0))
+
+ @T.function
+ def main(P: T.Tensor((1,), "float32"), Out: T.Tensor((2,), "float32")):
+ Module.check(P.data, Out.data)
+
+ executable = tvm_tirx.tirx.compile(Module, target="c")
+ data = tvm_tirx.runtime.tensor(np.ones(1, dtype="float32"))
+ output = tvm_tirx.runtime.empty((2,), "float32")
+ executable(data, output)
+ np.testing.assert_array_equal(output.numpy(), [1, 8])
+
+
def test_subroutine_call():
@I.ir_module
class Module: