E6038

E6038 — A 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.

What the compiler reports

Error[E6038] at {{.*}}.vx:[[@LINE+1]]:5: the body of the `spawn on` at line [[@LINE-2]] runs on GPU[0], and it uses 'tx': 'Sender<i64>' can wait for the host. A channel end and a lock stay on the host, because a wait on the device for the host can wait forever. Use 'tx' before the `spawn on`, or after it
Error[E6038] at {{.*}}.vx:[[@LINE+1]]:11: {{.*}} it uses 'rx': 'Receiver<i64>' can wait for the host.
Error[E6038] at {{.*}}.vx:[[@LINE+1]]:11: {{.*}} it uses 'w': field 'rx' of 'Waiter' has type 'Receiver<i64>', and 'Receiver<i64>' can wait for the host.
Error[E6038] at {{.*}}.vx:[[@LINE+1]]:14: {{.*}} it uses 'm': 'Mutex<i64>' can wait for the host.
Error[E6038] at {{.*}}.vx:[[@LINE+1]]:14: {{.*}} it uses 'g': 'MutexGuard<i64>' can wait for the host.
{{^}}Semantic check failed on {{.*}}: 5 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 on` body for a device must not use a channel end or a lock, because a wait on the
// device for the host can wait forever. A body on the CPU can use them, and so can the code
// before and after a `spawn on`.
//

import std::sync::channel;
import std::sync::mutex;

struct Waiter {
  rx : Receiver<i64>,
}

fn send_on_the_gpu(tx : Sender<i64>) -> void {
  let mut r = unsafe { Tensor<f32, [4], Memory::GPU_HBM>::uninit() };
  spawn on(Topology::GPU) {
    r[0] = 1.0;
    tx.send(7);
  }
}

fn receive_through_a_reference_on_the_gpu(rx : &Receiver<i64>) -> void {
  let mut r = unsafe { Tensor<f32, [4], Memory::GPU_HBM>::uninit() };
  spawn on(Topology::GPU) {
    r[0] = 1.0;
    match rx.recv() {
      Result<i64, RecvError>::Ok(v) => {
        r[1] = v as f32;
      }
      Result<i64, RecvError>::Err(_e) => {
        r[1] = 0.0;
      }
    }
    r[2] = 2.0;
  }
}

fn a_struct_that_holds_a_channel_end(w : &Waiter) -> void {
  let mut r = unsafe { Tensor<f32, [4], Memory::GPU_HBM>::uninit() };
  spawn on(Topology::GPU) {
    r[0] = 1.0;
    match w.rx.recv() {
      Result<i64, RecvError>::Ok(v) => {
        r[1] = v as f32;
      }
      Result<i64, RecvError>::Err(_e) => {
        r[1] = 0.0;
      }
    }
    r[2] = 2.0;
  }
}

fn lock_on_the_gpu(m : &Mutex<i64>) -> void {
  let mut r = unsafe { Tensor<f32, [4], Memory::GPU_HBM>::uninit() };
  spawn on(Topology::GPU) {
    r[0] = 1.0;
    let _g = m.lock();
  }
}

fn a_guard_on_the_gpu(m : &Mutex<i64>) -> void {
  let mut g = m.lock();
  let mut r = unsafe { Tensor<f32, [4], Memory::GPU_HBM>::uninit() };
  spawn on(Topology::GPU) {
    r[0] = 1.0;
    let _v = g.get();
  }
}

// A `spawn on(Topology::CPU)` body runs on the host, so it can use a channel end.
fn send_on_the_cpu(tx : &Sender<i64>) -> void {
  spawn on(Topology::CPU) {
    tx.send(8);
  }
}

// The code before and after a `spawn on` runs on the host.
fn send_around_the_gpu(tx : &Sender<i64>) -> void {
  tx.send(1);
  let mut r = unsafe { Tensor<f32, [4], Memory::GPU_HBM>::uninit() };
  spawn on(Topology::GPU) {
    r[0] = 1.0;
  }
  tx.send(2);
}

fn main() -> i32 {
  return 0;
}

// The five 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/channel_ends_and_locks_in_a_device_body.vx, which asserts this diagnostic on every commit.

Related