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

Should functions not be allowed to return aliases by default? #1675

Open
athas opened this issue May 9, 2022 · 4 comments
Open

Should functions not be allowed to return aliases by default? #1675

athas opened this issue May 9, 2022 · 4 comments

Comments

@athas
Copy link
Member

athas commented May 9, 2022

I am plugging some holes in alias tracking, and I discovered something uncomfortable. We currently have a rule that functions may not return aliases of global variables. This rule was instituted such that we can assume that the value returned by e.g. transpose A can at most alias A, which allows us to make in-place updates to transposed arrays, as long as we can consume the original array.

Now consider the following program:

module type monoid = {
  type t
  val zero : t
  val plus : t -> t -> t
}

module pm (M: monoid) = {
  def f (x: M.t) : M.t =
    M.plus M.zero x
}

While this is currently allowed by the type checker due to a bug, it should actually be a type error. The reason is that the result of the f function aliases M.zero (a global variable), which we do not want to allow. This is because the plus function does not declare its return type to be unique/alias-free, meaning it is allowed to alias either of its arguments. We may know that it is unlikely that a monoidal plus operation will do that, but the type checker does not. Worse, all of our builtin mathematical module types define their operations like this, and allow the result to alias the operands. Fixing this bug will require fixing a significant number of programs. Now, that's doable. The fix is to add an asterisk to the return types, indicating that the result is alias-free:

module type monoid = {
  type t
  val zero : t
  val plus : t -> t -> *t
}

...but to be honest, I think this adds a lot of visual clutter everywhere. I am considering whether we should change the default behaviour. Maybe t -> t should denote a function that does not alias any of its parameters (i.e make our current treatment of * in return types the default). We could then use another symbol, say @, to indicate that the result may alias the parameters (but still not global variables). For example, transpose would then be defined as

def transpose 'a [n] [m] (A: [n][m]a) : @[m][n]a = ...

This is based on the assumption (which I have not yet checked) that the common case is that functions return fresh results, and that aliasing is somewhat rare.

This is a pretty major bug that we should fix quickly, but the fix could have significant impact on the language. @melsman? @coancea? Anyone else? I'd like input.

@FluxusMagna
Copy link

FluxusMagna commented May 9, 2022

I think your proposed solution seems quite reasonable. Having return values aliasing arguments require special syntax honestly makes more sense to me than the opposite (although I am rather used to the current system, which has worked well too). At any rate I agree avoiding clutter for the most common case is important, and not breaking code should not be prioritized over good long term design solutions.

edit: I would assume that rather few top level functions, that would be most likely to have an explicit type annotation, return values that alias their arguments, so I think impact on existing code is somewhat limited, and pretty easy to fix too.

@athas
Copy link
Member Author

athas commented May 10, 2022

edit: I would assume that rather few top level functions, that would be most likely to have an explicit type annotation, return values that alias their arguments, so I think impact on existing code is somewhat limited, and pretty easy to fix too.

Unfortunately, closures also have aliases (their lexical environment). This means that programs that depend heavily on function-based representations, say a library for expressing neural networks, might be significantly affected. I don't yet have a full grasp of what the impact might be.

athas added a commit that referenced this issue May 10, 2022
* Simplify handling of uniqueness.

I want to eventually move away from the term "uniqueness types" and
recast this language feature as a kind of effect system.

You should never see `*` in non-function types, and they are better
thought of as effect indicators.

* Fix aliasing of global variables.

* Keep the hack around for a bit until we can handle #1675 properly.
@FluxusMagna
Copy link

Unfortunately, closures also have aliases (their lexical environment). This means that programs that depend heavily on function-based representations, say a library for expressing neural networks, might be significantly affected. I don't yet have a full grasp of what the impact might be.

Didn't think of that. Well, as long as it's fixable by just changing type annotations, it's not too big of a deal for me (and by extension hopefully not for most people). If you are really worried about the impact on higher order functions and the like, you can make a test branch. I can be a subject.

@athas
Copy link
Member Author

athas commented Jul 1, 2023

This is becoming more urgent. The tracking of aliases at the IR level is now good enough that it notices the things we let slip through the cracks in the frontend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants