vx-lang / vx-lang/Vx

Nothing frees a `memref.alloc`: every buffer the compiler allocates is an unmatched `malloc`

Open
#642 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug codegen
Dominant language
Rust
Stars
14
Forks
2
Avg merge
12h 42m
Merged PRs (30d)
61

Description

The string dealloc does not appear anywhere in src/. No emitter writes a
memref.dealloc, and neither pass pipeline -- get_optimization_pipeline
(src/driver.rs:1389) or the copy in src/codegen/mod.rs:134 -- runs a buffer-deallocation
pass. Every memref.alloc the compiler emits therefore becomes a malloc that is never
freed.

There are seven sites that emit one:

  • src/codegen/flat/emit/tensor.rs:90 (TensorAlloc: array literals and Tensor<T>(...))
  • src/codegen/flat/emit/tensor.rs:205 (transpose result)
  • src/codegen/flat/emit/tensor.rs:267 (TensorMap result)
  • src/codegen/flat/emit/tensor.rs:883
  • src/codegen/flat/emit/control.rs:100
  • src/codegen/flat/emit/arith.rs:164 (elementwise linalg result)
  • src/codegen/generator.rs:317 (the AST path)

This leaks per call, not per program:

fn sum_one() -> i32 {
  let l = [1, 2, 3, 4];
  return l[0] + l[3];
}

fn main() -> i32 {
  let mut total = 0;
  for i in 0..1000000 {
    total = total + sum_one();
  }
  return total % 7;
}

sum_one compiles to %1 = call ptr @malloc(i64 16) with no matching free. Linked at
-O0 -- which is what --run uses -- the binary peaks at 32.6 MB RSS for a program
whose live data is one i32. It answers correctly; it just keeps everything.

Two halves, and they want different answers.

The buffers that are plainly frame-local should never have been heap in the first place:
that is #641, and it removes most of the traffic rather than balancing it.

What remains is genuinely dynamic -- a ?-shaped Tensor<T>(n), an elementwise or
map result whose extent is read off an operand -- and those need an owner. Worth
deciding deliberately rather than reaching for MLIR's buffer-deallocation: the ownership
question is a language question here, and #495 (Vec has no drop, TYPE_NEEDS_DROP
reserved and never set) and #340 (a device transfer's free is emitted at end of block
rather than from a lifetime) are asking the same one from two other directions. A buffer
whose lifetime the frontend already knows should get its free from that lifetime, not from
a late pass re-deriving it.

Ordering note: this is the half that cannot be fixed by lowering alone, so it is the one
that should shape the model. #641 is worth landing first regardless, because it is
correct on its own terms and shrinks the problem this issue has to solve.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the seven allocation sites in src/codegen/flat/emit/tensor.rs, src/codegen/flat/emit/control.rs, src/codegen/flat/emit/arith.rs, and src/codegen/generator.rs, then read the pipelines in src/driver.rs:1389 and src/codegen/mod.rs:134. Compare the ownership questions with #641, #495, and #340 before choosing a model for dynamic buffers. Done means dynamically sized or derived buffers have an explicit frontend-known owner and are freed according to that lifetime, without relying on late buffer deallocation.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.