Skip to content

Commit 7cf237d

Browse files
authored
Merge RFC #37: Signature immutability
2 parents 09893fe + 9922d61 commit 7cf237d

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
- Start Date: 2023-12-11
2+
- RFC PR: [amaranth-lang/rfcs#37](https://github.com/amaranth-lang/rfcs/pull/37)
3+
- Amaranth Issue: [amaranth-lang/amaranth#995](https://github.com/amaranth-lang/amaranth/issues/995)
4+
5+
# Make `Signature` immutable
6+
7+
## Summary
8+
[summary]: #summary
9+
10+
Remove mutability from `amaranth.lib.wiring.Signature`.
11+
12+
## Motivation
13+
[motivation]: #motivation
14+
15+
At the time of writing, `Signature` allows limited mutability: members can be added, but not removed or changed; and a signature may be frozen, preventing further mutation.
16+
17+
The intent behind this feature was unfortunately never explicitly described. I (Catherine) came up with it, to cover the following case: suppose an interface without certain features needs to be connected to an interface with such features. For example, a Wishbone initiator that does not handle errors needs to be connected to a Wishbone decoder that does. In this case, a method on the Wishbone initiator's interface could be used to add a dummy `err` output, which will always remain at its reset value, zero.
18+
19+
This intent was never realized as the feature was never actually used by Amaranth SoC. In addition, it turned out to be problematic when combined with variable annotations. Consider this class definition:
20+
21+
```py
22+
class SomeInitiator(wiring.Component):
23+
bus: Out(wishbone.Interface(...))
24+
```
25+
26+
In this case, only one instance of `wishbone.Interface` is created. Mutating this instance would be unsound, since it would affect every instance of the component and not just the one for that particular one, and when this causes issues this would be very difficult to debug.
27+
28+
The presence of this feature encouraged adding other mutable objects to `Signature` subclasses, such as memory maps. That is also unsound, for similar reasons. Because memory maps in Amaranth SoC can also be frozen, considerable additional complexity was introduced since piecewise freezing was now possible.
29+
30+
Because freezing was defined as a property of the signature members dictionary, overloading `Signature.freeze` was meaningless: it would be possible, in rare but legal cases, to have a signature frozen without `freeze` being called for that signature, leaving any additional objects that should have been frozen mutable.
31+
32+
The `wiring.connect` function also supports constants as both inputs and outputs, where a constant input can be connected to a constant output provided their values match. This behavior is also suited for implementing optional features (where the absence of an optional feature means the corresponding ports are fixed at a constant value), and does not pose any hazards.
33+
34+
## Guide-level and reference-level explanation
35+
[guide-level-explanation]: #guide-level-explanation
36+
37+
The `SignatureMembers.freeze`, `SignatureMembers.frozen`, `Signature.freeze`, `Signature.frozen`, `FlippedSignatureMembers.freeze`, `FlippedSignatureMembers.frozen`, `FlippedSignature.freeze`, `FlippedSignature.frozen`, `SignatureMembers.__iadd__`, `SignatureMembers.__setitem__`, `FlippedSignatureMembers.__iadd__`, `FlippedSignatureMembers.__setitem__` methods and properties are removed.
38+
39+
`SignatureMembers` becomes an immutable mapping.
40+
41+
`Signature` becomes immutable. Subclasses of `Signature` are required to ensure any additional methods or properties do not allow mutation.
42+
43+
## Drawbacks
44+
[drawbacks]: #drawbacks
45+
46+
The author is not aware of anyone actually using this feature.
47+
48+
## Rationale and alternatives
49+
[rationale-and-alternatives]: #rationale-and-alternatives
50+
51+
An alternative to this proposal would be to automatically freeze any signature that is used in `wiring.Component` variable annotations. This does not address similar hazards, such as the case of a user-defined constant `SomeSignature = Signature({...})`, which is also a legitimate way to define a signature that does not require parameterization. It also would not address hazards associated with interior mutability.
52+
53+
## Prior art
54+
[prior-art]: #prior-art
55+
56+
None seems applicable. Mutation of interface definitions is uncommon in first place (excluding languages where everything is mutable, like Python).
57+
58+
## Unresolved questions
59+
[unresolved-questions]: #unresolved-questions
60+
61+
- Should *all* interior mutability be prohibited? At the moment it is not completely clear where memory maps should be attached, and requiring no interior mutability would mean it cannot be `Signature` no matter what. Interior mutability could be left to specific `Signature` subclasses instead on an experimental basis, and prohibited later if it turns out to be a bad idea.
62+
63+
## Future possibilities
64+
[future-possibilities]: #future-possibilities
65+
66+
Reintroducing mutability of `Signature` after it has been removed will be unfeasible due to expectation of immutability being baked in widely in downstream code. Once we commit to this RFC we will have to commit to it effectively forever.

0 commit comments

Comments
 (0)