[DSLX] type inference v2: "No constexpr value found for node `T`" for a proc with an explicit `type` parametric
- Dominant language
- C++
- Stars
- 1.9k
- Forks
- 283
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 135
Description
**Describe the bug**
Under type inference v2, IR conversion / typechecking fails with
```
NOT_FOUND: No constexpr value found for node `T` (NameDef) @ producer.x:5:19-5:20 in TypeInfo root
=== Source Location Trace: ===
xls/dslx/type_system/type_info.cc:297
xls/dslx/type_system_v2/constant_collector.cc:987
xls/dslx/type_system_v2/constant_collector.cc:1044
xls/dslx/type_system_v2/inference_table_converter_impl.cc:1320
```
when a `proc` has an explicitly-supplied `T: type` parametric and its
initializer (the impl-style `new`, i.e. a proc `StructInstance`) is built.
Value parametrics — and *defaulted* type parametrics — have a constexpr noted
on the binding, but an explicitly supplied `T: type` does not, so the
unconditional `GetConstExpr(binding->name_def())` in the constant collector
fails.
The failure only shows up in a specific (but common) shape: two parametric
procs in **separate modules** connected by a channel whose payload is a
parametric struct, where one proc is instantiated with `T` bound to that
struct type and the other reaches the same struct through its own `T: type`
parametric.
**To Reproduce**
Three files.
`producer.x`
```rust
#![feature(generics)]
#![feature(explicit_state_access)]
// A proc generic over the element type `T` it sends.
pub proc Producer {
sink: chan out,
state: uN[WIDTH],
}
impl Producer {
pub fn new(sink: chan out) -> Self {
Producer { sink, state: uN[WIDTH]:0 }
}
fn next(self) { }
}
```
`consumer.x`
```rust
#![feature(generics)]
#![feature(explicit_state_access)]
pub struct Request {
reg: T[DEGREE + 1],
}
impl Request {
fn default() -> Self { zero!>() }
}
pub proc Consumer {
request: chan> in,
sample_out: chan out,
state: Request,
}
impl Consumer {
fn new(request: chan> in, sample_out: chan out) -> Self {
Consumer {
request, sample_out, state: Request::default(),
}
}
fn next(self) { }
}
```
`main.x`
```rust
#![feature(generics)]
#![feature(explicit_state_access)]
import producer;
import consumer;
type Elem = s16;
const DEGREE = u32:3;
type Req = consumer::Request;
pub proc Top {}
impl Top {
fn new() -> Self {
let (req_s, req_r) = chan("req");
// Producer is instantiated with T = the parametric struct type.
producer::Producer()}>::new(req_s).spawn();
let (res_s, res_r) = chan("res");
// Consumer reaches the same struct through its own type parametric.
consumer::Consumer::new(req_r, res_s).spawn();
Top {}
}
}
```
```
ir_converter_main --top=Top --type_inference_v2=true \
--dslx_path= main.x
```
fails with the `No constexpr value found for node `T`` error above.
Collapsing the two procs into a single module makes the error disappear,
which is why it is easy to miss in small tests.
**Expected behavior**
Conversion succeeds; the explicitly-supplied `T: type` parametric is resolved
when the proc initializer is built.
**Root cause / suggested fix**
In `xls/dslx/type_system_v2/constant_collector.cc`, when `HandleStructInstance`
builds a proc initializer it collects the proc's parametrics with an
unconditional `GetConstExpr(binding->name_def())`. That call has no value for
an explicitly supplied `type` parametric. Looking the value up in the
parametric env first, and falling back to an opaque type reference for a
`GenericTypeAnnotation`, fixes it (positions must be preserved, since proc
initializers are compared element-wise):
```diff
--- a/xls/dslx/type_system_v2/constant_collector.cc
+++ b/xls/dslx/type_system_v2/constant_collector.cc
@@ -981,11 +981,27 @@ class Visitor : public AstNodeVisitorWithDefault {
std::vector parametrics;
parametrics.reserve(type.struct_def_base().parametric_bindings().size());
+ const ParametricEnv env = table_.GetParametricEnv(parametric_context_);
for (const ParametricBinding* binding :
type.struct_def_base().parametric_bindings()) {
- XLS_ASSIGN_OR_RETURN(InterpValue value,
- ti_->GetConstExpr(binding->name_def()));
- parametrics.push_back(value);
+ // Value parametrics (and defaulted type parametrics) have a constexpr
+ // noted on the binding. An explicitly supplied `T: type` parametric does
+ // not, so fall back to the parametric env, then to an opaque type
+ // reference. Positions must be preserved: proc initializers are compared
+ // element-wise by index.
+ std::optional value =
+ ti_->GetConstExprOption(binding->name_def());
+ if (!value.has_value()) {
+ value = env.GetValue(binding->name_def());
+ }
+ if (!value.has_value() &&
+ binding->type_annotation()->IsAnnotation()) {
+ value = InterpValue::MakeTypeReference(binding->type_annotation());
+ }
+ if (!value.has_value()) {
+ XLS_ASSIGN_OR_RETURN(value, ti_->GetConstExpr(binding->name_def()));
+ }
+ parametrics.push_back(*value);
}
```
With this change the reproduction above converts successfully.
**Environment**
- Built from `main` (google/xls @ 1b53da61b).
- Linux x86_64, type inference v2.
---
_Disclaimer: I hit this while working on a hardware project of mine. I used
Claude to locate the root cause in the codebase and to draft the patch above;
I have verified both the reproduction and the fix and am filing it._
Contributor guide
Research direction
Start in xls/dslx/type_system_v2/constant_collector.cc at HandleStructInstance and the parametric collection path around the reported constant-collector locations. Run the provided three-file reproduction with ir_converter_main and type inference v2; done means conversion succeeds for the separate-module proc case without the missing constexpr error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100