Template Type Equivalence - The HAC Language
Description: The HAC Language

Next: Template Type Parameters, Previous: Default template values, Up: Templates

### 9.4 Type-equivalence

NEW: In HAC, there are two subclasses of template parameters: strict parameters are required to be equivalent for type-matching, whereas relaxed parameters are ignored when type matching. We refer to the strict type of an entity as the underlying template type with fully-specified strict parameters, disregarding its relaxed parameters. The rationale for making this distinction is that frequently, one wishes to declare sparse or dense collections of the same logical type while allowing some internal variation. For example, ROM cells hard-wired to $0$ or $1$ are permitted in the same collection, and would be templated with one relaxed parameter for the cell's value1. The user has the freedom to decide what parameters considered relaxed.

The proposed syntax for distinguishing the two types of parameters is:

template-signature:

- `template` `<` strict-parameter-list `>`
- `template` `<` strict-parameter-list_opt `>` `<` relaxed-parameter-list `>`

Both parameter lists are syntactically identical. When there are no relaxed parameters, the second set of angle-brackets may be omitted. If there are only relaxed parameters, the first set of angle-brackets are still required but empty.

There are two levels of type equivalence. Two entities are collection-equivalent or collectible if their underlying template type is the same and their strict parameters are equivalent, but not necessarily their relaxed parameters. Two entities are connection-equivalent or connectible if they are collectible, their relaxed parameters are equivalent, and their public interface (port-for-port) is connectible, i.e., the dimensions, sizes, and strict types of the ports are themselves, connection-equivalent. (Note the recursive definition.) Connectibility implies collectibility, but not vice versa.

TODO: denotational semantics

Definition: a type is complete is it is either a strict type, or a relaxed type with its relaxed parameters bound. Connectible-equivalence is the same as equivalence between complete types.

DEBATE: (Resolved, see above: NEW) Should the fourth criterion be required for type-equivalence, or can we allow non-equivalent template parameters as long as the ports interface is equivalent?

Implementation requirement: type errors must be caught by the end of the create-phase of compilation.

Definition requirement: For a template definition to be well-formed, all instances (aliases) local to that definition, including ports, must have complete type. Note that a given definition template may instantiate a definition for each set of unique template parameters, some of which may be meet this requirement, others which may not. [Quick examples of valid and invalid definition?]

Syntax and semantics: The basic syntax for explicitly binding instance alias type parameters is:

- instance-reference `<` relaxed-parameter-list `>`

The instance-reference may reference a collection of aliases (with ranged specification), in which case the same relaxed parameters are bound to each referenced alias. (Technically, member references could be rejected in this context because all ports are required to have complete type by construction.)

Application: Arrays. The rationale for introducing relaxed types is to be able to declare an array of elements whose members are not identical, though their interfaces remain compatible. The following valid example shows how relax-typed arrays are declared:

         template <pint X><pbool B>
         defproc foo(...) { ... }

         foo<2> bar[2];
         bar[0]<true>;  // bind relaxed parameter
         bar[1]<false>; // bind different relaxed parameter

Had one of the parameter-binding statements been omitted, compilation would eventually result in a error indicating that the unbound instance had incomplete type.

Syntactic sugar.

`foo<1> bar<true>;` is equivalent to `foo<1> bar; bar<true>;`.

`foo<1> bar<true>(...);` is equivalent to `foo<1> bar; bar<true>; bar(...)`.

`foo<1> bar<true>[N];` is equivalent to `foo<1> bar[N]; bar[0..N-1]<true>;`.

Aliasing and collection strictness. Consider the following declarations: `foo<1><true> bar;` and `foo<1> car<true>;`. `bar` cannot be aliased to `car` because the collections (even though they are scalar) have different strictness. Strictly speaking, their respective array types are `foo<1><true>` and `foo<1>`, which are not equivalent. For a well-formed connection between two aliases, the collections of the respective aliases must be equivalent, match in strictness, and the relaxed types bound to each alias must be equivalent or compatible. In other words, members of strictly typed collections (arrays) cannot alias members of relaxed typed collections.

Implicit type binding though connections. When two aliases are connected to each other, the connection is valid if, in addition to their parent collections' types being equivalent, one of the following holds: both aliases are bound to equivalent parameters or at least one alias has unbound parameters. When an alias is formed, the relaxed type parameters are automatically `synchronized,' which effectively binds the type of aliases through other aliases of bound type. [Examples from test suite needed.]

Implementation detail: As soon as an instance alias is bound to a type, it is instantiated. All (reachable) aliases thereof are also instantiated and recursively connected. Attempts to reference members of type-unbound aliases can be rejected as errors. (Theoretically, since the public ports may not depend on relaxed parameters, such references may not be treated as errors in the future. It is possible to instantiate the ports before the type is bound, however, internal aliases cannot be replayed until the type is bound, either explicitly or implicitly. The current policy is just a conservative approximation of the eventual operational semantics.)

Type propagation through ports. Since definition members and ports are required to have complete type, the relaxed parameters must be propagated from the formal definition to each instantiating context. Consider the example:

         defproc wrap(foo<1> x) {
         x<false>;
         }
         wrap Z;

upon instantiating `Z` in the top-level, `Z.x` has type `foo<1><false>`.

Restrictions. Where are relaxed parameters forbidden? In port specifications, types of ports and their array sizes may not depend on relaxed parameters, i.e. they may only depend on strict template parameters. This guarantees that the port interfaces remain the same among members of a relax-typed array. In typedef templates, the canonical type's strict template arguments may not depend on relaxed parameters. (Why not? Allowing so would give a means of subverting template parameter strictness.) Anywhere else in the body of a definition template, relaxed parameters may be used freely, within the constraints of the fundamental typing rules.

When should relaxed template parameters be used? General guideline: when the port interfaces do not depend on the said parameters.

#### Footnotes

[1] The motivation for this comes from the fact that in CAST, environment sources could not be arrayed if they differed in values.