google / google/xls

send in unroll_for! without let binding seems to get lost

Open
#1,953 1 comment 0 reactions 0 assignees View on GitHub
bug dslx
Dominant language
C++
Stars
1.9k
Forks
283
Avg merge
2d 10h
Merged PRs (30d)
135

Description

**Describe the bug**
I tried to save a few characters by inlining a `send` inside a `join` within an `unroll_for!` (the idea being to send in parallel and order subsequent channel ops after all the sends completing). The test behaved as if the send never happened (deadlock).

But if I instead use an explicit `let` binding for the `send` token, the procs no longer deadlock.

**To Reproduce**
Minimal repro:

```
proc DutProc {
control: chan in;
data: chan[2] in;
ack: chan out;

config(control: chan in, data: chan[2] in, ack: chan out) {
(control, data, ack)
}

init { }

next(state: ()) {
let (tok, ctrl, _) = recv_non_blocking(join(), control, false);
let (tok, _) = recv_if(tok, data[0], ctrl, u32:0);
let (tok, _) = recv_if(tok, data[1], ctrl, u32:0);
let tok = send_if(tok, ack, ctrl, true);
}
}

#[test_proc]
proc my_dut_test {
control: chan out;
data: chan[2] out;
ack: chan in;
terminator: chan out;

config(terminator: chan out) {
let (control_s, control_r) = chan("control");
let (data_s, data_r) = chan[2]("data");
let (ack_s, ack_r) = chan("complete");
spawn DutProc(control_r, data_r, ack_s);
(control_s, data_s, ack_r, terminator)
}

init { () }

next(state: ()) {
let tok = send(join(), control, true);
let sent_data_tok = unroll_for! (i, data_tok): (u32, token) in u32:0..u32:2 {
join(data_tok, send(tok, data[i], u32:1 + i))
}(join());

let (tok, done) = recv(sent_data_tok, ack);
assert_eq(done, true);

let tok = send(tok, terminator, true);
}
}
```

Running this DSLX test, you get the following error:
```
: internal error: DEADLINE_EXCEEDED: Procs are deadlocked:
dut.x:28:31-28:58: proc `DutProc` is blocked on receive on channel `my_dut_test->DutProc#0::data[0]`
dut.x:57:31-57:51: proc `my_dut_test` is blocked on receive on channel `my_dut_test::ack`
```

If you change the test_proc `next` so that the `send` gets its own `let` binding:

```
next(state: ()) {
let tok = send(join(), control, true);
let sent_data_tok = unroll_for! (i, data_tok): (u32, token) in u32:0..u32:2 {
let tok = send(tok, data[i], u32:1 + i);
join(data_tok, tok)
}(join());

let (tok, done) = recv(sent_data_tok, ack);
assert_eq(done, true);

let tok = send(tok, terminator, true);
}
```

the test will no longer deadlock.

**Expected behavior**
It shouldn't matter whether the `send` gets its own `let` binding.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.