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

Formalizing parameter directions #5742

Open
bmillsNV opened this issue Dec 4, 2024 Discussed in #3260 · 1 comment
Open

Formalizing parameter directions #5742

bmillsNV opened this issue Dec 4, 2024 Discussed in #3260 · 1 comment
Labels
Backlog goal:forward looking Feature needed at a later date, not connected to a specific use case. SPF:Proposal Slang Proposed Feature: Proposal - A Slang feature which has been proposed, but not yet implemented

Comments

@bmillsNV
Copy link
Collaborator

bmillsNV commented Dec 4, 2024

Discussed in #3260

Originally posted by csyonghe October 4, 2023
(Original slack thread by Tess)

The basic HLSL/Cg/GLSL setup is to have:
in (the default)
out
in out / inout

They historically had copy-in/copy-out behavior, but then HLSL is kind of getting slopping and giving inout by-reference behavior in some cases.

There's a few ways where this set of directions and those semantics are insufficient:
They can't easily handle non-copyable types (which require things akin to Rust-like borrows)
The mandatory copying and by-value passing is inefficient for many targets (seemingly not just C++/CPU targets, but also SPIR-V?)
Some operations have (or require) genuine by-reference passing (e.g., atomics)

We added ref (well, __ref) as a direction modifier to handle the last case, where ref is currently a by-reference analog to inout.

And we also made our semantics for inout more subtle to address the second case, by saying that it is "either by-reference or by-value, at the discretion of the compiler" and that any case where the user wrote code that would depend on the difference, they are in error.

(And then we subsequently added constref direction, with the implication that it is like an input-only ref, but with the actual semantics being closer to our modified inout semantics of "maybe by-reference and maybe not")

It's relevant for all of this stuff to think of the l-values we pass as arguments to these parameters as "abstract storage locations" with examples including:
A global or local variable
this
A field of an abstract storage location of struct type
A property of an abstract storage location with properties
An element of an abstract storage location with array elements
Matrix rows, vector elements, etc.

A swizzle of a vector-type abstract storage location, so long as no element appears more than once in the swizzle (basically just a property... except not always)

The property case is kind of the most complicated (and therefore interesting) because it might be implemented with distinct get and set accessors, so that any read-modify-write necessarily involves copy-in and copy-out.

(There are details about how to support properties together with non-copyable types, but that's even more to lump in here)

The key point is that when an abstract storage location is used as part of an expression, we can think of that as an "access" operation that has a defined beginning and ending point in the execution flow of a single thread.

(Multithreading is also out of scope of what I'm trying to say here...)

An access can be a read, a write, or a read-modify-write, depending on how the l-value is used.

In the case of an abstract storage location backed by a property:
A read access starts when we begin invoking the get accessor, and ends when that accessor returns
A write access starts when we begin invoking the set accessor, and ends when that accessor returns
A read-modify-write access starts when we begin invoking the get accessor, and ends when the set accessor returns.

If two accesses to the same abstract storage location overlap in time, then either:
The two accesses are both reads, and everything is cool
The program is invalid, and UB may result
(edited)

The above rule is what allows us to say that inout can either be by-value or by-reference. Any program that could observe the difference is invalid by our rules.

(And its easy to argue that no useful program needs to break the given rule)

Okay, with the background out of the way, I propose the following as the (hopefully) complete set of parameter directions we want to support.

Guaranteed by-reference semantics:
in ref - a guaranteed by-reference immutable borrow. A Slang in ref T would translate to a C T const*.
inout ref / in out ref / ref a guaranteed by-reference mutable borrow. A Slang inout ref T would translate to a C T*

Guaranteed by-value semantics:
in / default - may be implemented as pass-by-reference or pass-by-value at ABI level, but the compiler (not the programmer) takes responsibility for ruling out overlapping accesses and inserting copies as needed

By-value or by-reference at compiler's discretion:
borrow - semantically an immutable borrow
inout / in out - semantically a mutable borrow, but compiler may insert copy-in-copy-out in cases where there is a property/swizzle/etc.

The by-reference/-value distinction doesn't really matter:
out - effectively just sugar for additional function results
consume - for non-copyable types this is is the equivalent of a Rust function parameter that has been moved into. For copyable types this is semantically equivalent to in.

I should ammend my earlier comment about accesses and their durations to note that when a value is being passed as an argument to a parameter that uses an immutable borrow (in ref, or borrow) the duration of the access to that abstract storage location lasts until the callee function returns (to account for the fact that the callee might have received a pointer to the storage location).

@bmillsNV bmillsNV added this to the Q1 2025 (Winter) milestone Dec 5, 2024
@bmillsNV bmillsNV added SPF:Proposal Slang Proposed Feature: Proposal - A Slang feature which has been proposed, but not yet implemented goal:forward looking Feature needed at a later date, not connected to a specific use case. labels Dec 5, 2024
@bmillsNV bmillsNV removed this from the Q1 2025 (Winter) milestone Feb 6, 2025
@bmillsNV
Copy link
Collaborator Author

bmillsNV commented Feb 6, 2025

Moving to the backlog for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Backlog goal:forward looking Feature needed at a later date, not connected to a specific use case. SPF:Proposal Slang Proposed Feature: Proposal - A Slang feature which has been proposed, but not yet implemented
Projects
None yet
Development

No branches or pull requests

1 participant