Skip to content

[ssbuffer](feat) scalar v2c dep fix v2 - #1521

Open
1801ZDL wants to merge 9 commits into
triton-lang:main-devfrom
1801ZDL:feat/scalar-v2c-dep-opclassifier-fix
Open

[ssbuffer](feat) scalar v2c dep fix v2#1521
1801ZDL wants to merge 9 commits into
triton-lang:main-devfrom
1801ZDL:feat/scalar-v2c-dep-opclassifier-fix

Conversation

@1801ZDL

@1801ZDL 1801ZDL commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

New contributor declaration

  • I am not making a trivial change, such as fixing a typo in a comment.

  • I have written a PR description following these
    rules.

  • I have run pre-commit run --from-ref origin/main --to-ref HEAD.

  • Select one of the following.

    • I have added tests.
      • /test for lit tests
      • /unittest for C++ tests
      • /python/test for end-to-end tests
    • This PR does not need a test because FILL THIS IN.
  • Select one of the following.

    • I have not added any lit tests.
    • The lit tests I have added follow these best practices,
      including the "tests should be minimal" section. (Usually running Python code
      and using the instructions it generates is not minimal.)

Transfer tensor.extract scalar results from VECTOR blocks to CUBE blocks
via SSBuffer with PIPE_S sync, so CUBE blocks no longer recompute
VECTOR-only math chains (math.floor/math.ceil) or depend on them across
the CV boundary.

- DataDependencyAnalysis: detect scalar V->C deps from CUBE-side
  tensor.extract of VECTOR-only math ops (using the op's own core_type,
  since a block may mix VECTOR and CUBE ops), tracing the downstream pure
  scalar chain (fptosi/muli/subi/addi) to find CUBE consumers, including
  uses inside for/if bodies.
- InterCoreTransferAndSync: insert SSBuffer store/load pairs with
  cross-core deps and PIPE_S sync for scalar transfers.  The store is
  placed right after the extract, the load after the store when they share
  a block, and the store is skipped in the consumer-use rewrite so it keeps
  referencing the original extract (no store->load self loop).
- SeparateCVScope: keep VECTOR-only boundary chains out of the CUBE scope
  by rewriting leftover VECTOR references to the CUBE-side load chains and
  retaining only ops each scope actually needs.
- OpClassifier: classify math.floor/ceil as VECTOR; isVectorOnlyOp covers
  math::CeilOp.
- SSBufferManager: the SSBuffer memref element type must match the stored
  value (memref.store verifies value/element types); the LLVM pointer era
  allowed a hardcoded i32 memref.

Rebased onto latest main-dev: adapted LLVM::LoadOp/StoreOp to
memref::LoadOp/StoreOp (the main-dev LLVM->memref migration) and dropped
the now-removed DependencyInfo::isScaler flag.

Co-Authored-By: DeLong code
Review feedback:
1. OpClassifier: an extract whose source tensor traces to a VECTOR-only
   producer (e.g. math.floor/ceil) is itself a VECTOR computation -- the CUBE
   side consumes its scalar via the SSBuffer dependency channel and must not
   recompute it.  Skip marking such extracts CUBE in both propagateCubeUpstream
   and propagateCubeUpstreamForOp (new hasVectorOnlyProducer helper).
2. DataDependencyAnalysis: split the tensor.extract walk out of
   analyzeScalarVToCDependencies into analyzeScalarExtractDependencies, since
   the 3-walk function was ~300 lines.  Pure refactor, no behavior change.

Co-Authored-By: DeLong code
Follow-up to classifying scalar-dep extracts as VECTOR:
- With the extract now VECTOR, requiring it to live in a CUBE block would
  reject the very extracts the scalar V->C feature targets.  Remove that
  block-level isCube gate and rely on the downstream CUBE-consumer walk.
- analyzeExternalInputs (which runs first) may already have recorded a V->C
  dep for the extract crossing into a CUBE block.  Skip the duplicate in
  analyzeScalarExtractDependencies, otherwise the same scalar gets a second
  SSBuffer store/sync (deadlock risk).

Verified end-to-end from the pre-plan input: op-classifier now classifies
the floor/ceil extracts as VECTOR, the scalar transfer is inserted with one
store per scalar, and the CUBE scope is free of math ops.

Co-Authored-By: DeLong code
With OpClassifier now marking scalar-dependency extracts (and their math
chains) as VECTOR, the CUBE scope no longer contains these chains, so the
replaceVectorRefsInCubeScope/retainNeededOpsInScope rewrite+cleanup is
expected to be dead code.  Comment the calls out for an end-to-end test;
the definitions are removed once E2E passes.

Co-Authored-By: DeLong code
E2E verified that with OpClassifier marking scalar-dependency extracts (and
their math chains) as VECTOR, the CUBE scope no longer contains the cloned
VECTOR chains, so the rewrite+cleanup in SeparateCVScope is dead code.
Remove collectChainPaths, replaceVectorRefsInCubeScope and
retainNeededOpsInScope, their call sites, and the now-unused includes
(LLVM/Tensor) plus duplicate includes.

Co-Authored-By: DeLong code
@github-actions github-actions Bot added compiler Changes to C/C++ compiler backend (lib/, include/) python Changes to Python runtime or bindings ascend-backend Changes to the Ascend NPU backend labels Aug 12, 2026
@1801ZDL 1801ZDL closed this Aug 12, 2026
@1801ZDL 1801ZDL changed the title [ssbuffer](feat) scalar v2c dep opclassifier fix [ssbuffer](feat) scalar v2c dep fix v2 Aug 12, 2026
Shorten the added comments (no examples/walkthroughs), and fix a stale
llvm.store/load reference in the VECTOR redundant-load cleanup comment.

Co-Authored-By: DeLong code
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

🔍 OpenCodeReview found 6 issue(s) in this PR.

  • ✅ Successfully posted inline: 6 comment(s)

Comment on lines 113 to 118
inline hivm::PointerCastOp createPointerCastOp(OpBuilder &builder, Location loc,
uint64_t addr) {
return getSsbufConstAndPointerCast(builder, loc, addr).second;
// Default to i32 for callers that only store i32 values into SSBuffer.
auto i32Type = builder.getIntegerType(CONST_INT_TYPE);
return getSsbufConstAndPointerCast(builder, loc, addr, i32Type).second;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[maintainability · low]
The createPointerCastOp convenience function silently defaults to i32 element type. While the comment documents this assumption and all current callers in UpdateConditionInfo.cpp indeed store i32 values, there is no compile-time or runtime guard against misuse. A future developer adding a new caller could unintentionally create a type-mismatched memref::StoreOp (which verifies value type matches memref element type).

Suggestion: Add an overload that accepts an explicit element type parameter, making the API symmetric with getSsbufConstAndPointerCast. This would also make the function more discoverable for developers who need non-i32 scalar types.

Suggestion:

Suggested change
inline hivm::PointerCastOp createPointerCastOp(OpBuilder &builder, Location loc,
uint64_t addr) {
return getSsbufConstAndPointerCast(builder, loc, addr).second;
// Default to i32 for callers that only store i32 values into SSBuffer.
auto i32Type = builder.getIntegerType(CONST_INT_TYPE);
return getSsbufConstAndPointerCast(builder, loc, addr, i32Type).second;
}
inline hivm::PointerCastOp createPointerCastOp(OpBuilder &builder, Location loc,
uint64_t addr, Type elemType) {
return getSsbufConstAndPointerCast(builder, loc, addr, elemType).second;
}
// Convenience overload for the common i32 case.
inline hivm::PointerCastOp createPointerCastOp(OpBuilder &builder, Location loc,
uint64_t addr) {
return createPointerCastOp(builder, loc, addr,
builder.getIntegerType(CONST_INT_TYPE));
}

@1801ZDL 1801ZDL reopened this Aug 13, 2026
Remove the store->load cleanup in VECTOR scopes to test whether downstream
dead-code elimination handles the redundant VECTOR-side load.  Re-add if the
E2E test shows it is still required.

Co-Authored-By: DeLong code
Comment on lines +148 to +151
void analyzeScalarExtractDependencies(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues,
llvm::DenseSet<scf::ForOp> &handledForOps);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[maintainability · medium]
Missing direct include for llvm::DenseSet. The header now uses llvm::DenseSet<mlir::Value> and llvm::DenseSet<scf::ForOp> in the newly added function declarations, but llvm/ADT/DenseSet.h is not explicitly included. The existing includes (DenseMap.h, SetVector.h, SmallVector.h) do not guarantee that DenseSet is transitively available. This could cause compilation failures on different LLVM configurations. Please add #include "llvm/ADT/DenseSet.h".

Suggestion:

Suggested change
void analyzeScalarExtractDependencies(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues,
llvm::DenseSet<scf::ForOp> &handledForOps);
#include "llvm/ADT/DenseSet.h"
// ... (keep existing includes above)
class DataDependencyAnalysisPass ...


#include <optional>

#include <map>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[maintainability · low]
Unused include: <map> is added but std::map (or any related type) is never used anywhere in this file. Please remove it to avoid unnecessary dependencies.


#include "bishengir/Dialect/HIVM/IR/HIVM.h"
#include "bishengir/Dialect/Scope/IR/Scope.h"
#include "mlir/Transforms/RegionUtils.h"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[maintainability · low]
Unused include: mlir/Transforms/RegionUtils.h is added but none of the utilities it provides (e.g., replaceAllUsesInRegionWith, cloneRegionInto, isIsolatedFromAbove) are used in this file. Please remove it to keep dependencies clean.

E2E confirmed the VECTOR-side store->load is a redundant operation (re-reads
the value the VECTOR core just stored for its own loop bound), not dead code:
downstream DCE does not remove it because it feeds the loop bound.  Restore
the separate-cv-scope cleanup that replaces the load with the stored value.

Co-Authored-By: DeLong code
Comment on lines +148 to +151
void analyzeScalarExtractDependencies(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues,
llvm::DenseSet<scf::ForOp> &handledForOps);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[maintainability · low]
The name analyzeScalarExtractDependencies is somewhat ambiguous — it specifically handles tensor::ExtractOp, but MLIR also has vector::ExtractOp. A more precise name like analyzeTensorExtractScalarDependencies would better convey its scope and avoid potential confusion when other extract-like ops are added later. Additionally, there is a minor naming inconsistency with existing methods: the class uses Dep abbreviations (e.g., collectDepInfo, collectMemDepInfo) while the new methods use the full word Dependencies. Consistent abbreviation style improves readability.

Suggestion:

Suggested change
void analyzeScalarExtractDependencies(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues,
llvm::DenseSet<scf::ForOp> &handledForOps);
void analyzeTensorExtractScalarDeps(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues,
llvm::DenseSet<scf::ForOp> &handledForOps);

Remove the handledForOps cross-suppression between for-loop bounds and
nested if conditions: whether a loop bound has a scalar dep does not
determine whether an if condition inside it does (and vice versa).  The
outer-loop skip was an over-approximation that could miss a real if
condition derived from an independent vector-only chain.

Merge both into analyzeScalarControlFlowDependencies: each bound/condition
is checked independently against its own defining chain, suppressed only by
the shared handledScalarValues stop-set (already-transferred scalars) or by
not reaching a vector-only op.  handledForOps is removed.

Co-Authored-By: DeLong code
Comment on lines +148 to +153
void analyzeScalarExtractDependencies(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues);
void analyzeScalarControlFlowDependencies(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[maintainability · low]
The header uses llvm::DenseSet<mlir::Value> in the new method declarations but does not explicitly include "llvm/ADT/DenseSet.h". It currently relies on transitive inclusion (likely through SetVector.h or MLIR headers), which is fragile. Other headers in this project (e.g., UpdateConditionInfo.h, Utils.h, AddMultiBufferToGMLoadInternal.h) explicitly include DenseSet.h — this file should follow the same convention.

Suggestion:

Suggested change
void analyzeScalarExtractDependencies(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues);
void analyzeScalarControlFlowDependencies(
DataDependencyInfo &info,
llvm::DenseSet<mlir::Value> &handledScalarValues);
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"


#include <optional>

#include <map>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[style · low]
This include is unused — the new code uses llvm::DenseMap, not std::map. No other part of the file references std::map either. Dead includes add unnecessary build dependencies.

Suggestion:

Suggested change
#include <map>
// Remove this line


#include "bishengir/Dialect/HIVM/IR/HIVM.h"
#include "bishengir/Dialect/Scope/IR/Scope.h"
#include "mlir/Transforms/RegionUtils.h"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[style · low]
The added include mlir/Transforms/RegionUtils.h provides utilities such as getUsedValuesDefinedAbove, replaceAllUsesInRegionWith, etc., but none of these are used in the existing code or the new optimization block. Consider removing unless needed for a planned follow-up.

Comment on lines +1026 to +1035
llvm::DenseMap<int64_t, mlir::Value> storedValues;
scopeOp.walk([&](memref::StoreOp storeOp) {
auto transferIdAttr =
storeOp->getAttrOfType<mlir::IntegerAttr>(CVPipeline::kTransferId);
if (!transferIdAttr) {
return;
}
int64_t tid = transferIdAttr.getInt();
storedValues[tid] = storeOp.getValue();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug · high]
The stored value map keys solely on kTransferId without verifying that the StoreOp and LoadOp operate on the same memref buffer. If two different SSBuffers share the same kTransferId (e.g., sender vs. receiver buffers in a double-buffer group), the load may be replaced with a value from a different buffer, causing silent semantic corruption.

Suggestion:

Suggested change
llvm::DenseMap<int64_t, mlir::Value> storedValues;
scopeOp.walk([&](memref::StoreOp storeOp) {
auto transferIdAttr =
storeOp->getAttrOfType<mlir::IntegerAttr>(CVPipeline::kTransferId);
if (!transferIdAttr) {
return;
}
int64_t tid = transferIdAttr.getInt();
storedValues[tid] = storeOp.getValue();
});
// Key by (transferId, memref) to ensure buffer identity
struct MemRefKey {
int64_t transferId;
mlir::Value memref;
bool operator==(const MemRefKey &o) const {
return transferId == o.transferId && memref == o.memref;
}
};
struct MemRefKeyInfo : llvm::DenseMapInfo<MemRefKey> {
static inline MemRefKey getEmptyKey() { return {-1, {}}; }
static inline MemRefKey getTombstoneKey() { return {-2, {}}; }
static unsigned getHashValue(const MemRefKey &k) {
return llvm::hash_combine(k.transferId, k.memref.getAsOpaquePointer());
}
static bool isEqual(const MemRefKey &lhs, const MemRefKey &rhs) {
return lhs == rhs;
}
};
llvm::DenseMap<MemRefKey, mlir::Value, MemRefKeyInfo> storedValues;

Comment on lines +1033 to +1034
int64_t tid = transferIdAttr.getInt();
storedValues[tid] = storeOp.getValue();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug · medium]
If multiple StoreOps share the same kTransferId, the DenseMap keeps only the last-visited store's value (last-write-wins). A subsequent LoadOp may be replaced with a value from a store that does not correspond to it semantically, breaking the intended 1:1 store-to-load assumption.

Suggestion:

Suggested change
int64_t tid = transferIdAttr.getInt();
storedValues[tid] = storeOp.getValue();
int64_t tid = transferIdAttr.getInt();
// Warn if duplicate TIDs encountered — violates 1:1 expectation
if (storedValues.count(tid)) {
LLVM_DEBUG(llvm::dbgs()
<< "[SeparateCVScope] Warning: duplicate StoreOp tid=" << tid);
}
storedValues[tid] = storeOp.getValue();

Comment on lines +1053 to +1058
mlir::Value storeVal = it->second;
if (storeVal == loadOp.getResult()) {
return;
}
loadOp.replaceAllUsesWith(storeVal);
deadLoads.push_back(loadOp);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug · high]
loadOp.replaceAllUsesWith(storeVal) is called without verifying that storeVal dominates the load. If the store resides in a nested region (e.g., inside an scf.if or loop) that does not dominate the load's position, this creates invalid MLIR IR with use-before-definition errors, potentially causing verification failures or runtime crashes.

Suggestion:

Suggested change
mlir::Value storeVal = it->second;
if (storeVal == loadOp.getResult()) {
return;
}
loadOp.replaceAllUsesWith(storeVal);
deadLoads.push_back(loadOp);
mlir::Value storeVal = it->second;
if (storeVal == loadOp.getResult()) {
return;
}
// Verify dominance: the stored value must dominate the load
if (auto *defOp = storeVal.getDefiningOp()) {
if (!defOp->isBeforeInBlock(loadOp) &&
defOp->getBlock() != loadOp->getBlock()) {
return;
}
} else if (storeVal.isa<mlir::BlockArgument>()) {
// Block arguments dominate their block, skip trivial check
} else {
return;
}
loadOp.replaceAllUsesWith(storeVal);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ascend-backend Changes to the Ascend NPU backend compiler Changes to C/C++ compiler backend (lib/, include/) python Changes to Python runtime or bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant