-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[SR-15808] [AutoDiff] Non-differentiable closure parameters not parsed correctly #58085
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
Comments
Have you already checked whether `TypeChecker::isDifferentiable` returns |
I have narrowed down the bug's source to line 594 on TypeChecker.cpp. I need to ensure that we can reproduce this behavior for any arbitrary |
@AnthonyLatsis I don't have the experience necessary to test the hypothesis above efficiently. Do you have the resources to do that at the moment? |
It does indeed return |
But the empty tuple doesn't have any differentiable elements! They might have set it to be differentiable because it's the return type of pullbacks in mutating functions. Could you find a way to conditionally set empty tuples to not-differentiable, and see what happens? |
Or we could just make an exception at the problematic call site, where we bypass this if it's an empty tuple. If you change it to register an empty tuple as non-differentiable, I might ping some AutoDiff experts for their insight. |
I can, but I'd be taking shots in the dark. If |
I don't think this behavior is intentional. Void should not be differentiable, and in some examples below, the system gives diagnostics about that. When you pass a function in as a closure parameter, then it starts behaving differently than normal. The following crash: func helloWorld(_ x: @differentiable (Void) -> Void) {}
func helloWorld(_ x: @differentiable (Void) -> Float) {}
func helloWorld(_ x: @differentiable (Float) -> Void) {}
func helloWorld(_ x: @differentiable (@noDerivative Float, Void) -> Float) {} The following produce an error diagnostic: // These functions are inconsistent with their counterparts above
@differentiable func helloWorld(_ x: Void) -> Void {}
@differentiable func helloWorld(_ x: Void) -> Float {}
@differentiable func helloWorld(_ x: Float) -> Void {}
func helloWorld(_ x: @differentiable (@noDerivative Float) -> Float) {}
func helloWorld(_ x: @differentiable (@noDerivative Void) -> Void) {}
func helloWorld(_ x: @differentiable (@noDerivative Void) -> Float) {}
@differentiable func helloWorld(_ x: inout Float, y: Float) -> Int {}
@differentiable func helloWorld(_ x: inout Float, y: Float) -> (Void, Int) {}
@differentiable func helloWorld(_ x: inout Float, y: Float) -> (Void, Void) {}
func helloWorld(_ x: @differentiable (Float) -> Int) {}
func helloWorld(_ x: @differentiable (inout Float, Float) -> Int) {}
// Counterpart cited below
func helloWorld(_ x: @differentiable (inout Float, Float, Int) -> Void) {} The following do not produce an error diagnostic or crash: @differentiable func helloWorld(_ x: inout Float, y: Float) -> Void {}
@differentiable func helloWorld(_ x: inout Float, y: Float) -> (Void) {}
@differentiable func helloWorld(_ x: inout Float, y: Float, z: Void) -> (Void) {}
// This function is inconsistent with its counterpart above
@differentiable func helloWorld(_ x: inout Float, y: Float, z: Int) -> (Void) {}
func helloWorld(_ x: @differentiable (inout Float, Float) -> Void) {}
func helloWorld(_ x: @differentiable (inout Float, Float) -> (Void)) {}
func helloWorld(_ x: @differentiable (inout Float, Float) -> ((Void))) {}
func helloWorld(_ x: @differentiable (inout Float, Float) -> (((Void)))) {} This means Swift has special treatment for when a function's return is either Void or recursive tuple of Void. It should never classify Void as differentiable in the first place; rather, it should do special-case checking at the return site of a function. @AnthonyLatsis how did you prove that Swift always says Void is differentiable? The diagnostics above suggest otherwise. Some interesting combinations from above: // This compiles successfully
@differentiable func helloWorld(_ x: inout Float, _ y: Float, _ z: Int) -> Void {}
// This produces an error
func helloWorld(_ x: @differentiable (inout Float, Float, Int) -> Void) {}
// These both compile successfully
@differentiable func helloWorld(_ x: inout Float, _ y: Float, _ z: Void) -> (Void) {}
func helloWorld(_ x: @differentiable (inout Float, Float, Void) -> Void) {}
// These both compile successfully
@differentiable func helloWorld(_ x: inout Float, _ y: Float, _ z: @noDerivative Int) -> (Void) {}
func helloWorld(_ x: @differentiable (inout Float, Float, @noDerivative Int) -> Void) {}
// These both compile successfully
@differentiable func helloWorld(_ x: inout Float, _ y: Float, _ z: @noDerivative Void) -> (Void) {}
func helloWorld(_ x: @differentiable (inout Float, Float, @noDerivative Void) -> Void) {} |
I think the compiler is supposed to insert an implicit `@noDerivative` at some point in AST. If so, and it was just skipping that step in some edge cases, that would explain everything. Except this crasher: func helloWorld(_ x: @differentiable (Float) -> (Void)) {} Its assertion failure crash says "result indices must not be empty". This parallels the original crasher that started this thread, where instead parameter indices were empty. |
Fix submitted as #41174 |
Attachment: Download
Additional Detail from JIRA
md5: a3df518fba66afe2f96d3bbdb0bf649a
Issue Description:
The following code produces a compiler crash during SILGen because it skipped classifying the closure as <<error type>> during AST creation:
While this code, which correctly identifies the closure as <<error type>> during AST creation, does not crash the compiler:
The crash log is attached.
The text was updated successfully, but these errors were encountered: