E6039
E6039 — Aspawnin a loop of itstasksblock, with a count of passes that is not known at compile time, and tasks that use a memory with a capacity. Each pass starts a task, and the tasks run at the same time, so their working set has no bound. Use aforloop over a constant range, or join each task in its pass. Downgraded to W1028 when the space is declaredovercommit.
What the compiler reports
Error[E6039] at {{.*}}.vx:[[@LINE+1]]:7: this `spawn` is in a loop with a count of passes that is not known at compile time, and its tasks use memory space 'W'
Error[E6039] at {{.*}}.vx:[[@LINE+1]]:7: this `spawn` is in a loop
{{^}}Semantic check failed on {{.*}}: 6 error(s) emitted
The fragments the test suite holds the compiler to. An ellipsis marks text the test does not constrain; the emitted message also carries a source location.
A program that triggers it
//
// Part of the Vx Project, under the Apache License v2.0 with LLVM Exceptions.
// See LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// A `spawn` in a loop starts a task in each pass, and the tasks of all the passes run at the same
// time. So the memory that one task uses counts once for each pass. `use_one` places 1 MiB in `W`,
// which holds 4 MiB.
//
Memory CPU_DRAM {}
Memory W {
within: Memory::CPU_DRAM, capacity: 4 MiB, managed: explicit
}
Topology Dev {
arch: nvptx64,
memory: Memory::W,
visible: [Memory::W],
transfer Memory::CPU_DRAM -> Memory::W : 10,
transfer Memory::W -> Memory::CPU_DRAM : 10
}
const PASSES: i64 = 5;
fn use_one() -> f32 {
let aT = unsafe { Tensor<f32, [512, 512]>::uninit() };
let a = transfer(aT, Memory::W);
let r = transfer(a, Memory::CPU_DRAM);
return r[0][0];
}
// Five tasks call it at the same time.
fn five_tasks_call_it() -> void {
tasks {
for i in 0..PASSES {
spawn {
let _x = use_one();
};
}
}
}
// Two loops of two passes start four tasks. Four fit.
fn four_tasks_call_it() -> void {
tasks {
for i in 0..2 {
for j in 0..2 {
spawn {
let _x = use_one();
};
}
}
}
}
// The block code calls it after the loop, while the four tasks run. The error is at the first
// call that reaches the peak.
fn the_block_calls_it_after_four_tasks() -> void {
tasks {
for i in 0..4 {
spawn {
let _x = use_one();
};
}
let _y = use_one();
}
}
// In a loop, the call before the `spawn` runs while the tasks of the earlier passes run.
fn the_block_calls_it_before_the_spawn_in_the_loop() -> void {
tasks {
for i in 0..4 {
let _y = use_one();
spawn {
let _x = use_one();
};
}
}
}
// Each task places a tile of 1 MiB itself, with no call.
fn five_tasks_place_a_tile() -> void {
tasks {
for i in 0..5 {
spawn {
let bT = unsafe { Tensor<f32, [512, 512]>::uninit() };
let b = transfer(bT, Memory::W);
let _r = transfer(b, Memory::CPU_DRAM);
};
}
}
}
// A join in the same pass ends each task before the next pass starts.
fn each_pass_joins_its_task() -> void {
tasks {
for i in 0..5 {
let h = spawn {
return use_one();
};
let _x = h.join();
}
}
}
// A loop outside the `tasks` block ends the tasks of a pass before the next pass.
fn a_tasks_block_in_each_pass() -> void {
for i in 0..5 {
tasks {
spawn {
let _x = use_one();
};
}
}
}
// The count of passes is known only when the program runs.
fn a_count_that_the_program_gives(n: i64) -> void {
tasks {
let mut i: i64 = 0;
while i < n {
spawn {
let _x = use_one();
};
i = i + 1;
}
}
}
// The same, with a tile that each task places itself.
fn a_count_that_the_program_gives_and_a_tile(n: i64) -> void {
tasks {
let mut i: i64 = 0;
while i < n {
spawn {
let bT = unsafe { Tensor<f32, [512, 512]>::uninit() };
let b = transfer(bT, Memory::W);
let _r = transfer(b, Memory::CPU_DRAM);
};
i = i + 1;
}
}
}
fn main() -> i32 {
return 0;
}
// The errors above are the only ones. `--implicit-check-not` does not look past the last
// `CHECK-DAG` line, so this line counts them.
From tests/frontend/fail/tasks_that_a_loop_starts_add_up_their_memory.vx, which asserts this diagnostic on every commit.
Related
Topologies and memoryThe chapter covering the rule this code enforces.
All diagnosticsEvery code the compiler can emit, grouped by stage.
E6038A
spawn on body for a device that uses a channel end, a lock, or a value that holds one. The standard library marks these types with the trait HostOnly. A wait on the device for the host can wait forever, because a GPU does not promise that its threads make progress. Send or receive before the spawn on, or after it. A spawn on(Topology::CPU) body runs on the host, so it can use them.
E6040A channel of tensors in a memory with a capacity, with a room that is not known at compile time. The channel can hold that many tensors at once, so the working set has no bound. Downgraded to W1028 when the space is declared overcommit.