Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support paramterized multiports and banks through generics #349

Closed
cmnrd opened this issue May 28, 2021 · 7 comments · Fixed by #387
Closed

Support paramterized multiports and banks through generics #349

cmnrd opened this issue May 28, 2021 · 7 comments · Fixed by #387
Labels
c Related to C target cpp Related to C++ target enhancement Enhancement of existing feature

Comments

@cmnrd
Copy link
Collaborator

cmnrd commented May 28, 2021

The C target currently supports multiports and reactor banks with a variable sized width (provided by a parameter). This is possible, because the C code generator treats parameter as compile time constants. Thus, it can resolve the precise value of any width specification during compile-time. However, this is not possible in the C++ target as parameters are runtime constants. They are initialized when the application starts and may be overwritten by CLI parameters that the generated binary parses.

It occurred to me that generics present a potential solution for the problem as they are always resolved at compile time. We currently have syntax for this:

reactor Foo<T> {
  output out:T;
}

We could enhance it like this:

reactor Foo<T: type, w: width(4)> {
  output[w] out:T;
}

Where the types with <> would be restricted to either type or width. This would also provide a viable solution for the C target should it allow overwriting parameters via the CLI in the future.

@cmnrd cmnrd added enhancement Enhancement of existing feature c Related to C target cpp Related to C++ target labels May 28, 2021
@edwardalee
Copy link
Collaborator

I don't understand why you say "this is not possible." The Cpp target invokes constructors at runtime. It just has to be able to evaluate the parameter values while invoking the constructors.

@cmnrd
Copy link
Collaborator Author

cmnrd commented May 31, 2021

With "this is not possible" I was referring to the process of resolving and checking parameterized width at compile time. Of course this checking could be done at runtime, but we would lose the possibility to throw compile time errors. Also, I am not sure how well the visualizations would work if the precise width is unknown. The point I try to make is that type parameters are a viable alternative to constructor parameters, as they can be resolved at compile time (both by the LF compiler and by the C++ compiler).

@edwardalee
Copy link
Collaborator

I guess you mean "it is not possible to check at compile time widths that are specified as command-line parameters at run time." This is correct, of course. I would argue that we should not allow widths to specified via command-line parameters (illustrations below about why this would not be a good idea, just in case we do not agree about that part). If so, the essential problem here is how to prohibit this.

One possibility is simply to generate code that rejects command-line updates of any parameter that is used to specify widths. I don't think this would be hard to do.

I guess you are proposing using type variables because programmers don't expect to be able to change type variables at runtime, correct? This makes sense, in which case, I guess the only issue is whether using type variables will lose anything in expressivity. There is also the practical question of who will implement this...

Here are two arguments for why widths should not be specifiable on the command line:

Consider a federated execution specified at the top level as a bank of reactors. The command line program that is generated will have to figure out how many programs to start and on which machines. I guess this would have to done in the generated launcher script? The RTI would also now have to repeat all the logic in the code generator that determines which federates are connected to which. I suspect this means the RTI will need access to the source code or some abstracted version of it.

Also, if you can override widths on the command line, then you can make them inconsistent no matter what the code generator and validator have done. E.g., suppose you have a multiport with width w1 feeding two multiports with widths w2 and w3. Currently, the code generator checks that, with the default parameter values given in the source code, w1 = w2 + w3. If each of these parameters can be overridden on the command line, then doesn’t the runtime code have to repeat this check?

@cmnrd
Copy link
Collaborator Author

cmnrd commented Jun 1, 2021

I guess you are proposing using type variables because programmers don't expect to be able to change type variables at runtime, correct? This makes sense, in which case, I guess the only issue is whether using type variables will lose anything in expressivity. There is also the practical question of who will implement this...

Yes, I think this summarizes it well. While I agree that it would be possible to check whether a parameter is used somewhere in the context of a width specification and to enable/disable command line overrides accordingly, I don't think this would be very intuitive for the programmer. I think type variables are an intriguing alternative as they make the difference obvious. Actually parameterized width specifications directly modify the interface of a Reactor (which we can interpret as its type) and hence I think there is a semantic difference between standard parameters that change the behavior of a reactor and width parameters that change the interface.

So far I cannot see any downside in using type parameters instead of reactor parameters for width specifications. But of course it would need to be implemented. There is already rudimentary support for type parameters in C++, so I think it wouldn't be too hard to make it work, but I guess this is more complicated for C. In any case, I don't think this is urgent. I opened the issue as a brain dump to keep track of the thought.

@cmnrd
Copy link
Collaborator Author

cmnrd commented Jun 3, 2021

This is a summary of yesterday's discussion in the LF weekly.

The underlying assumption in the discussion above was that we want to validate the widths of connected multiports (and/or banks) at compile time. This is, to check whether the widths of the left-hand side operands match the width of the right-hand side operands of the connection operator (->) and also to resolve precisely which lhs output is connected to which rhs input. This requires that the precise width of each multiport and bank is known at compile time and cannot change during runtime (as by a command line override). Since generally we want parameters to be overridden at runtime time, we are left with two options (that were already discussed above):

  1. As suggested by @edwardalee we could let the LF compiler deduce which parameters are used for width specifications and only allow command line overrides for parameters that are not used for width. This has the downside that there is a non-obvious distinction between width and non-width parameters and programmers will likely be surprised that they cannot change the values of certain parameters at runtime.

  2. Make the distinction between width and non-width explicit in LF code. One way of doing this would be to treat width parameters as type parameters as I proposed above. Then it would be obvious to a programmer that the widths are compile time constants. We could also think of another syntax. For instance, @Soroosh129 proposed a const qualifier for existing parameters to distinguish between runtime and compile-time constants.

Both options have the disadvantage that any change in width's requires recompilation, as @lhstrh pointed out. So maybe we should question the compile-time checks. Allowing applications to scale according to a given parameter would be an interesting and valuable property. Instead of verifying multiport connections at compile-time we could move these checks to the runtime.

In conclusion of this discussion, I think we might want to deal with this issue differently for different targets. While it likely makes sense to have more static programs that are checked at compile time in the C target (which also targets small micro-controllers), we probably want to have more flexibility for other targets. Considering that the C++ runtime already does many checks at runtime (including dependency analysis of reactions), it would only be consequent to extend the runtime with native support for banks and multiports.

@cmnrd
Copy link
Collaborator Author

cmnrd commented Jun 3, 2021

There is actually another aspect to this problem that only occured to me while working on the C++ code generator today. The C++ generator translates each reactor to a class and each reactor instantiation is translated to an instantiation of this class. Parameters are simply passed to the constructor of this class. This means that the code generated for a reactor needs to be general and work with the whole range of parameters that can be supplied. In essence, all instances of a reactor share the same code and are implemented by the same class.

I think the C target generates code for each instance (right?). This means it can generate the connection logic individually for each reactor instance and for the given width parameters. In C++, however, the generated reactor class would need to provide a more general logic, since we don't know in advance which parameters will be passed to the constructor. This means that Edward's suggestion of forbidding command line overrides of width parameters does not work for C++. In fact, parameterized width do not work at all for the C++ code generator without either having runtime logic for drawing connections or making width's class template parameters (which then essentially means that each instance would use a different class). Of course, there is the third option of generating individual classes for each instance, but I would really like to avoid that.

@edwardalee
Copy link
Collaborator

We definitely want to avoid generating individual classes for each instance. That would be very unfortunate!

In the C target, the constructor for a reactor does not construct its contained reactors. Instead, the code generator generates code that constructs all the reactors, sets their parameter values and initial state values, and then connects them together. Presumably, the same thing could be done in C++, at the cost that using the underlying classes independently from the LF code generator would become less convenient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c Related to C target cpp Related to C++ target enhancement Enhancement of existing feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants