Skip to content

Commit

Permalink
Update template_troubles.md
Browse files Browse the repository at this point in the history
  • Loading branch information
VonTum authored Aug 22, 2024
1 parent 907e0a5 commit f5fab1b
Showing 1 changed file with 18 additions and 17 deletions.
35 changes: 18 additions & 17 deletions philosophy/template_troubles.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ void myFunc() {
}
```

All languages that choose to adopt this standard employ limitations around their use. In Java it's not possible to pass values as template arguments, in C++ often the [`template` keyword must be inserted where they're used for the parser to understand](https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords).
All languages that choose to adopt this standard employ limitations around their use. In Java it's not possible to pass values as template arguments, in C++ often the [`template` keyword must be inserted for the parser to understand](https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords).

Take the fully ambiguous case of the function call:
Take the fully ambiguous case of this function call:
```
myFunc<beep>(3)
```

This can be parsed in two ways:
- The way we as the programmer intend, IE it to be a template instantiation
- Two comparison operators with a value between parentheses: `myfunc<beep > (3)`
- Two comparison operators with a value between parentheses: `(myfunc<beep) > (3)`

This is a proper grammatical ambiguity. We wish to avoid grammatical ambiguities.
This is a proper grammatical ambiguity.

## Template troubles for SUS
In SUS there's quite a few things that come together that make this notation of templates difficult. For starters, SUS also has the `<` `>` comparison operators, which provides an immediate conflict, just like in the above example.

Another less well known conflict from this notation comes from the commas. Take calling:
```cpp
myFunc(myConst<int, bool>, x, y, z);
myFunc(choice<3, 6>(5), x, y, z);
```
If the parser interprets the `<` as a comparison, then the commas separate function arguments, but if it were to interpret them as a template then the first comma separates template arguments.
Expand All @@ -45,33 +45,34 @@ Where the intent is to assign to a newly declared `a`, an existing variable `b`,

This notation combines declarations with assignable expressions.
The issue is that if the compiler can accept both declarations and arbitrary expressions, then there's two perfectly valid parses for `d`. Either the one we intend, or it becomes two expressions `myType<int` and `bool> d`.
While it's perhaps a bit dumb to assign to the output of a comparison operator to the parser it's all `_expression`.
While it may seem a bit dumb to assign to the output of a comparison operator to the parser it's all `_expression`.

## Solutions
There's two solution paths I see: The Rust solution, and the Verilog solution.
There's two solution archetypes I see: The Rust solution, and the Verilog solution. I've chosen a mix of the two.
### Rust
In so-called "type contexts", where the only this that's allowed to be written is a type, types are simple: `Option<i32>`, `Result<Vec<i32>, String>`, etc.
Rust solves it with adding `::<` in cases where it would otherwise be a parsing ambiguity, like `my_func::<3>(5)`. This disambiguates it from the comparison operators. But here still, a comparison expression inside the arguments list breaks it again: `my_func::<3 > 1>`.
Luckily, Rust sidesteps this by banning expressions in template all-together, as allowing that itself would also introduce a whole lot of dependent types mess that [turns pre-monomorphization into an undecidable problem](https://hackmd.io/OZG_XiLFRs2Xmw5s39jRzA?view).

### Verilog
The Verilog solution is to simply move away from the angle bracket notation, and use a different one that doesn't conflict so heavily. In verilog's case, that's the `#(.varA(x), .varB(y), ...)` notation.
The Verilog solution is to simply move away from the angle bracket notation, and use a different one that doesn't conflict so heavily. In verilog's case, that's the `#(.varA(x), .varB(y), ...)` notation. The advantage here is explicitness, which is important in HW design, since otherwise you'd be instantiating modules like `FIFO #(32, 6, 2)` and you wouldn't know what the numbers mean.

Verilog does have some redundancy here though, with note the `defparam` syntax. It is wholly unnecessary.
This is not the only way to use templates in Verilog though, but in my opinion the `defparam` syntax is so verbose I'll leave it out of consideration.

### SUS
Honestly, for languages where the vast majority of template instantiations do not depend on types, but rather on values, using an unambiguous syntax may just be the solution here.
And yes, while Verilog's solution may not be familiar to software programmers, but it's the standard hardware programmers are used to.
In Hardware Design, as opposed to software design, the vast majority of template instantiations do not depend on types, but rather on values. While software is full of things like `Result<Vec<i32>, ErrorObject>`, in hardware the sizes and parameters are numbers.
The typical example is instantiating a library Memory block: `Memory #(int DATA_WIDTH, int DEPTH, bool USE_OUTPUT_PIPELINE_STAGE, bool READ_PIPELINE_STAGE, etc)`. Using an unambiguous syntax may just be the solution here.
And yes, while Verilog's solution may not be familiar to software programmers, hardware designers are used to it.

I will, however, make one change to it. Taking inspiration from Rust, and in accordance with Hardware programmers' desire for explicitness, I'll change the template instantiation syntax to use named arguments with short form syntax:
I will, however, make one change to Verilog's notation. Taking inspiration from Rust, and in accordance with Hardware programmers' desire for explicitness, I'll change the template instantiation syntax to use named arguments with short form syntax:
```sus
module FIFO<T, int SIZE, int LATENCY> {...}
module FIFO#(T, int SIZE, int LATENCY) {...}
module use_FIFO {
gen int LATENCY = 3
FIFO#(SIZE: 32, LATENCY, type T: int[3])
FIFO#(SIZE: 32, LATENCY, type T: int[3]) myFIFO
myFIFO.push(...)
}
```
So in this example, `SIZE` is set to the result of the expression '32', `LATENCY` happens to be named identically to the variable we assign to it, thus short form. And the type we pass in requires a special `type` keyword so the parser can distinguish it.

Critically, this syntax is aimed at the hardware designers, because templates in hardware design far more commonly involve values rather than types. And in many of those cases, types are easier to infer than values so the `type` fallback syntax should be a rare occurence.
So in this example, `SIZE` is set to the result of the expression `32`, `LATENCY` happens to be named identically to the variable we assign to it, thus Rust's short form. And the type we pass in requires a special `type` keyword so the parser can distinguish it. Perhaps that could still be changed, since grammatically types appear to be a proper subset of expressions, but it also seems dangerous from an IDE perspective, as now it's not clear from the parse tree what it's supposed to be. Regardless, in many of those cases, types are easier to infer than values so the `type` fallback syntax should be a rare occurence.

0 comments on commit f5fab1b

Please sign in to comment.