Skip to content

Add stream_loads and stream_stores scheduling directives#9207

Merged
alexreinking merged 5 commits into
mainfrom
alexreinking/non-temporal
Jul 18, 2026
Merged

Add stream_loads and stream_stores scheduling directives#9207
alexreinking merged 5 commits into
mainfrom
alexreinking/non-temporal

Conversation

@alexreinking

@alexreinking alexreinking commented Jul 14, 2026

Copy link
Copy Markdown
Member

Implements the stream_loads and stream_stores directives as proposed in #9206.

  • stream_loads - ensure that loads this stage makes are streamed. It has two modes
    • Bare .stream_loads() -- stream all direct loads after inlining, excluding self-loads.
    • g.stream_loads({f1, ..., fN}) -- stream loads from the named funcs after inlining. Naming g here is a user_error.
  • stream_stores - ensure that stores to this func are streamed on a per-stage basis. A stage can have this specialization only if all of its RVars are pure (meaning that no two stores can alias). If any stage-specialization requests streaming, a fence is placed after the specialization block. This is vacuously true for pure funcs, RDom-less updates, and stages without specializations.

This fully recovers performance of memcpy on my Apple M3.

Fixes #9206

Checklist

  • Tests added or updated (not required for docs, CI config, or typo fixes)
  • Documentation updated (if public API changed)
  • Python bindings updated (if public API changed)
  • Benchmarks are included here if the change is intended to affect performance.
  • Commits include AI attribution where applicable (see Code of Conduct)

@alexreinking

Copy link
Copy Markdown
Member Author

This implementation was super straightforward, but also super messy. It would be nice if we had an annotations system so that places that just forward those annotations don't need to be updated for every new feature.

Comment thread src/CodeGen_LLVM.cpp Outdated
Comment thread src/CodeGen_LLVM.cpp Outdated
Comment thread test/performance/memcpy.cpp Outdated
Comment thread src/CodeGen_LLVM.cpp
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.36919% with 158 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@5476bf3). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/StmtToHTML.cpp 0.00% 38 Missing ⚠️
src/IRPrinter.cpp 0.00% 20 Missing and 2 partials ⚠️
src/CodeGen_LLVM.cpp 60.00% 10 Missing and 6 partials ⚠️
src/Func.cpp 73.17% 8 Missing and 3 partials ⚠️
src/IREquality.cpp 20.00% 6 Missing and 2 partials ⚠️
src/Simplify_Stmts.cpp 61.90% 5 Missing and 3 partials ⚠️
src/StorageFlattening.cpp 84.37% 4 Missing and 1 partial ⚠️
src/CodeGen_D3D12Compute_Dev.cpp 0.00% 4 Missing ⚠️
src/CodeGen_PTX_Dev.cpp 0.00% 4 Missing ⚠️
src/Derivative.cpp 0.00% 4 Missing ⚠️
... and 17 more
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #9207   +/-   ##
=======================================
  Coverage        ?   69.64%           
=======================================
  Files           ?      255           
  Lines           ?    78880           
  Branches        ?    18859           
=======================================
  Hits            ?    54936           
  Misses          ?    18303           
  Partials        ?     5641           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@alexreinking
alexreinking force-pushed the alexreinking/non-temporal branch from e0d2142 to 4fe8903 Compare July 15, 2026 19:25
Comment thread src/CodeGen_C.cpp
@alexreinking
alexreinking force-pushed the alexreinking/non-temporal branch 2 times, most recently from 538dbc8 to 057fec5 Compare July 16, 2026 02:52
Comment thread src/IR.h
Comment thread src/ScheduleFunctions.cpp
// after the whole (specialized) production is equivalent to,
// and simpler than, fencing inside each branch individually.
if (any_specialization_requests_streaming(def)) {
Expr fence = Call::make(Int(32), Call::stream_store_fence, {}, Call::Intrinsic);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So I guess the StreamingStores Stmt doesn't actually imply code then, and is purely communication to future lowering passes?

@alexreinking alexreinking Jul 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It implies a fence. However, rather than place a fence right then-and-there, this implementation places it after the specialization branch (if any) for the stage. This is sound because stream_stores only applies to stages with only pure Vars and RDoms. So that point assuredly post-dominates all such stores and pre-dominates any potential loads. There might be a later point in some programs with the same soundness property (especially when streaming out from the last stage), but that analysis is deferred to future work. This is safe and not too inefficient.

Comment thread src/Simplify_Stmts.cpp
Stmt Simplify::visit(const StreamingStore *op) {
Stmt body = mutate(op->body);
if (is_no_op(body)) {
return Evaluate::make(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

return body?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I just did what Atomic did above?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That actually looks correct since it drops a side-effect-free evaluation. The is_no_op predicate is true iff body is either:

  1. Undefined (i.e. Stmt())
  2. An Evaluate node whose value satisfies is_pure

So Evaluate(0) is more defined than (1) and no more work than (2). Seems fine to me.

Comment thread src/StorageFlattening.cpp Outdated

@abadams abadams left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving in advance so you're not blocked on me, but I did leave a few comments

@alexreinking
alexreinking force-pushed the alexreinking/non-temporal branch from a9baf07 to 44d6168 Compare July 17, 2026 23:18
@alexreinking
alexreinking force-pushed the alexreinking/non-temporal branch from f044531 to 1c5f8ba Compare July 18, 2026 03:58
@alexreinking
alexreinking merged commit a624d06 into main Jul 18, 2026
29 of 45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add non-temporal loads/stores to Halide

3 participants