Skip to content

Commit fdbdf7e

Browse files
author
marcrasi
authored
[AutoDiff] improve symbol linkage (#28582)
Changed the linkage and serialization of witnesses, derivative functions, and closures to match the design. Made deserialized witnesses from other modules not get emitted into the current module IR, by setting their linkage to public_external, and teaching IRGen not to emit public_external witnesses. (Fixes TF-1025). Added diagnostic for calling private derivatives from serialized functions (Fixes TF-690).
1 parent 7d607ad commit fdbdf7e

20 files changed

+214
-77
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,12 @@ NOTE(autodiff_expression_not_differentiable_note,none,
472472
NOTE(autodiff_external_nondifferentiable_function,none,
473473
"cannot differentiate functions that have not been marked "
474474
"'@differentiable' and that are defined in other files", ())
475+
NOTE(autodiff_private_derivative_from_fragile,none,
476+
"differentiated functions in "
477+
"%select{'@inlinable' functions|default arguments}0 must be marked "
478+
"'@differentiable' or have a public '@derivative'"
479+
"%select{|; this is not possible with a closure, make a top-level "
480+
"function instead}1", (unsigned, bool))
475481
NOTE(autodiff_nondifferentiable_argument,none,
476482
"cannot differentiate through a non-differentiable argument; do you want "
477483
"to use 'withoutDerivative(at:)'?", ())

lib/IRGen/GenDiffWitness.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ void IRGenModule::emitSILDifferentiabilityWitness(
3434
if (dw->isDeclaration())
3535
return;
3636

37+
// Don't emit public_external witnesses.
38+
if (hasPublicVisibility(dw->getLinkage()) &&
39+
isAvailableExternally(dw->getLinkage()))
40+
return;
41+
3742
ConstantInitBuilder builder(*this);
3843
auto diffWitnessContents = builder.beginStruct();
3944

lib/SILGen/SILGen.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,8 @@ void SILGenModule::emitDifferentiabilityWitness(
823823
auto *diffWitness = SILDifferentiabilityWitness::createDefinition(
824824
M, originalFunction->getLinkage(), originalFunction, loweredParamIndices,
825825
config.resultIndices, config.derivativeGenericSignature,
826-
/*jvp*/ nullptr, /*vjp*/ nullptr, originalFunction->isSerialized(),
826+
/*jvp*/ nullptr, /*vjp*/ nullptr,
827+
/*isSerialized*/ hasPublicVisibility(originalFunction->getLinkage()),
827828
diffAttr);
828829

829830
// Set derivative function in differentiability witness.

lib/SILOptimizer/Mandatory/Differentiation.cpp

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -209,15 +209,16 @@ class DifferentiationTransformer {
209209

210210
ADContext &getContext() { return context; }
211211

212-
/// Canonicalize the given witness, filling in JVP/VJPs if missing.
212+
/// Canonicalize the given witness, filling in derivative functions if
213+
/// missing.
213214
///
214-
/// \param explicitDifferentiable specifies whether the witness comes from an
215-
/// explicit `@differentiable` or `@derivative` attribute in the AST.
216-
/// If it does, we emit JVP/VJPs with the same linkage as the original
217-
/// so that they are linkable from other modules.
215+
/// Generated derivative functions have the same linkage as the witness.
216+
///
217+
/// \param serializeFunctions specifies whether generated functions should be
218+
/// serialized.
218219
bool canonicalizeDifferentiabilityWitness(
219220
SILFunction *original, SILDifferentiabilityWitness *witness,
220-
DifferentiationInvoker invoker, bool explicitDifferentiable);
221+
DifferentiationInvoker invoker, IsSerialized_t serializeFunctions);
221222

222223
/// Process the given `differentiable_function` instruction, filling in
223224
/// missing derivative functions if necessary.
@@ -692,17 +693,31 @@ emitDerivativeFunctionReference(
692693
originalFn->getLoweredFunctionType(), desiredParameterIndices,
693694
contextualDerivativeGenSig);
694695
minimalWitness = SILDifferentiabilityWitness::createDefinition(
695-
context.getModule(),
696-
originalFn->isSerialized() ? SILLinkage::Shared : SILLinkage::Hidden,
697-
originalFn, desiredParameterIndices, desiredResultIndices,
696+
context.getModule(), SILLinkage::Private, originalFn,
697+
desiredParameterIndices, desiredResultIndices,
698698
derivativeConstrainedGenSig, /*jvp*/ nullptr,
699-
/*vjp*/ nullptr, originalFn->isSerialized());
699+
/*vjp*/ nullptr, /*isSerialized*/ false);
700700
if (transformer.canonicalizeDifferentiabilityWitness(
701-
originalFn, minimalWitness, invoker,
702-
/*explicitDifferentiable*/ false))
701+
originalFn, minimalWitness, invoker, IsNotSerialized))
703702
return None;
704703
}
705704
assert(minimalWitness);
705+
if (original->getFunction()->isSerialized() &&
706+
!hasPublicVisibility(minimalWitness->getLinkage())) {
707+
enum { Inlinable = 0, DefaultArgument = 1 };
708+
unsigned fragileKind = Inlinable;
709+
// FIXME: This is not a very robust way of determining if the function is
710+
// a default argument. Also, we have not exhaustively listed all the kinds
711+
// of fragility.
712+
if (original->getFunction()->getLinkage() == SILLinkage::PublicNonABI)
713+
fragileKind = DefaultArgument;
714+
context.emitNondifferentiabilityError(
715+
original, invoker, diag::autodiff_private_derivative_from_fragile,
716+
fragileKind,
717+
llvm::isa_and_nonnull<AbstractClosureExpr>(
718+
originalFRI->getLoc().getAsASTNode<Expr>()));
719+
return None;
720+
}
706721
// TODO(TF-482): Move generic requirement checking logic to
707722
// `getExactDifferentiabilityWitness` &
708723
// `getOrCreateMinimalASTDifferentiabilityWitness`.
@@ -1503,12 +1518,11 @@ class VJPEmitter final
15031518
original->getASTContext());
15041519

15051520
SILOptFunctionBuilder fb(context.getTransform());
1506-
// The generated pullback linkage is set to Hidden because generated
1507-
// pullbacks are never called cross-module.
1508-
auto linkage = SILLinkage::Hidden;
1521+
auto linkage =
1522+
vjp->isSerialized() ? SILLinkage::Public : SILLinkage::Private;
15091523
auto *pullback = fb.createFunction(
15101524
linkage, pbName, pbType, pbGenericEnv, original->getLocation(),
1511-
original->isBare(), IsNotTransparent, original->isSerialized(),
1525+
original->isBare(), IsNotTransparent, vjp->isSerialized(),
15121526
original->isDynamicallyReplaceable());
15131527
pullback->setDebugScope(new (module)
15141528
SILDebugScope(original->getLocation(),
@@ -3114,18 +3128,20 @@ class JVPEmitter final
31143128
witness->getSILAutoDiffIndices(), jvp)),
31153129
differentialInfo(context, AutoDiffLinearMapKind::Differential, original,
31163130
jvp, witness->getSILAutoDiffIndices(), activityInfo),
3117-
differentialBuilder(SILBuilder(*createEmptyDifferential(
3118-
context, original, witness, &differentialInfo))),
3131+
differentialBuilder(SILBuilder(
3132+
*createEmptyDifferential(context, witness, &differentialInfo))),
31193133
diffLocalAllocBuilder(getDifferential()) {
31203134
// Create empty differential function.
31213135
context.recordGeneratedFunction(&getDifferential());
31223136
}
31233137

31243138
static SILFunction *
3125-
createEmptyDifferential(ADContext &context, SILFunction *original,
3139+
createEmptyDifferential(ADContext &context,
31263140
SILDifferentiabilityWitness *witness,
31273141
LinearMapInfo *linearMapInfo) {
31283142
auto &module = context.getModule();
3143+
auto *original = witness->getOriginalFunction();
3144+
auto *jvp = witness->getJVP();
31293145
auto origTy = original->getLoweredFunctionType();
31303146
auto lookupConformance = LookUpConformanceInModule(module.getSwiftModule());
31313147

@@ -3186,12 +3202,11 @@ class JVPEmitter final
31863202
original->getASTContext());
31873203

31883204
SILOptFunctionBuilder fb(context.getTransform());
3189-
// The generated tangent linkage is set to Hidden because generated tangent
3190-
// are never called cross-module.
3191-
auto linkage = SILLinkage::Hidden;
3205+
auto linkage =
3206+
jvp->isSerialized() ? SILLinkage::Public : SILLinkage::Hidden;
31923207
auto *differential = fb.createFunction(
31933208
linkage, diffName, diffType, diffGenericEnv, original->getLocation(),
3194-
original->isBare(), IsNotTransparent, original->isSerialized(),
3209+
original->isBare(), IsNotTransparent, jvp->isSerialized(),
31953210
original->isDynamicallyReplaceable());
31963211
differential->setDebugScope(
31973212
new (module) SILDebugScope(original->getLocation(), differential));
@@ -5783,7 +5798,7 @@ bool VJPEmitter::run() {
57835798

57845799
static SILFunction *createEmptyVJP(ADContext &context, SILFunction *original,
57855800
SILDifferentiabilityWitness *witness,
5786-
SILLinkage linkage) {
5801+
IsSerialized_t isSerialized) {
57875802
LLVM_DEBUG({
57885803
auto &s = getADDebugStream();
57895804
s << "Creating VJP:\n\t";
@@ -5817,10 +5832,10 @@ static SILFunction *createEmptyVJP(ADContext &context, SILFunction *original,
58175832
vjpGenericSig);
58185833

58195834
SILOptFunctionBuilder fb(context.getTransform());
5820-
auto *vjp = fb.createFunction(linkage, vjpName, vjpType, vjpGenericEnv,
5821-
original->getLocation(), original->isBare(),
5822-
IsNotTransparent, original->isSerialized(),
5823-
original->isDynamicallyReplaceable());
5835+
auto *vjp = fb.createFunction(
5836+
witness->getLinkage(), vjpName, vjpType, vjpGenericEnv,
5837+
original->getLocation(), original->isBare(), IsNotTransparent,
5838+
isSerialized, original->isDynamicallyReplaceable());
58245839
vjp->setDebugScope(new (module) SILDebugScope(original->getLocation(), vjp));
58255840

58265841
LLVM_DEBUG(llvm::dbgs() << "VJP type: " << vjp->getLoweredFunctionType()
@@ -5830,7 +5845,7 @@ static SILFunction *createEmptyVJP(ADContext &context, SILFunction *original,
58305845

58315846
static SILFunction *createEmptyJVP(ADContext &context, SILFunction *original,
58325847
SILDifferentiabilityWitness *witness,
5833-
SILLinkage linkage) {
5848+
IsSerialized_t isSerialized) {
58345849
LLVM_DEBUG({
58355850
auto &s = getADDebugStream();
58365851
s << "Creating JVP:\n\t";
@@ -5864,10 +5879,10 @@ static SILFunction *createEmptyJVP(ADContext &context, SILFunction *original,
58645879
LookUpConformanceInModule(module.getSwiftModule()), jvpGenericSig);
58655880

58665881
SILOptFunctionBuilder fb(context.getTransform());
5867-
auto *jvp = fb.createFunction(linkage, jvpName, jvpType, jvpGenericEnv,
5868-
original->getLocation(), original->isBare(),
5869-
IsNotTransparent, original->isSerialized(),
5870-
original->isDynamicallyReplaceable());
5882+
auto *jvp = fb.createFunction(
5883+
witness->getLinkage(), jvpName, jvpType, jvpGenericEnv,
5884+
original->getLocation(), original->isBare(), IsNotTransparent,
5885+
isSerialized, original->isDynamicallyReplaceable());
58715886
jvp->setDebugScope(new (module) SILDebugScope(original->getLocation(), jvp));
58725887

58735888
LLVM_DEBUG(llvm::dbgs() << "JVP type: " << jvp->getLoweredFunctionType()
@@ -5878,7 +5893,7 @@ static SILFunction *createEmptyJVP(ADContext &context, SILFunction *original,
58785893
/// Returns true on error.
58795894
bool DifferentiationTransformer::canonicalizeDifferentiabilityWitness(
58805895
SILFunction *original, SILDifferentiabilityWitness *witness,
5881-
DifferentiationInvoker invoker, bool explicitDifferentiable) {
5896+
DifferentiationInvoker invoker, IsSerialized_t serializeFunctions) {
58825897
std::string traceMessage;
58835898
llvm::raw_string_ostream OS(traceMessage);
58845899
OS << "processing ";
@@ -5889,9 +5904,6 @@ bool DifferentiationTransformer::canonicalizeDifferentiabilityWitness(
58895904

58905905
assert(witness->isDefinition());
58915906

5892-
auto derivativeFunctionLinkage =
5893-
explicitDifferentiable ? original->getLinkage() : SILLinkage::Hidden;
5894-
58955907
// If the JVP doesn't exist, need to synthesize it.
58965908
if (!witness->getJVP()) {
58975909
// Diagnose:
@@ -5903,7 +5915,7 @@ bool DifferentiationTransformer::canonicalizeDifferentiabilityWitness(
59035915
return true;
59045916

59055917
witness->setJVP(
5906-
createEmptyJVP(context, original, witness, derivativeFunctionLinkage));
5918+
createEmptyJVP(context, original, witness, serializeFunctions));
59075919
context.recordGeneratedFunction(witness->getJVP());
59085920

59095921
// For now, only do JVP generation if the flag is enabled and if custom VJP
@@ -5974,7 +5986,7 @@ bool DifferentiationTransformer::canonicalizeDifferentiabilityWitness(
59745986
return true;
59755987

59765988
witness->setVJP(
5977-
createEmptyVJP(context, original, witness, derivativeFunctionLinkage));
5989+
createEmptyVJP(context, original, witness, serializeFunctions));
59785990
context.recordGeneratedFunction(witness->getVJP());
59795991
VJPEmitter emitter(context, original, witness, witness->getVJP(), invoker);
59805992
return emitter.run();
@@ -6731,7 +6743,7 @@ void Differentiation::run() {
67316743
auto invoker = invokerPair.second;
67326744

67336745
if (transformer.canonicalizeDifferentiabilityWitness(
6734-
original, witness, invoker, /*explicitDifferentiable*/ true))
6746+
original, witness, invoker, original->isSerialized()))
67356747
errorOccurred = true;
67366748
}
67376749

lib/Serialization/DeserializeSIL.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3534,8 +3534,9 @@ SILDeserializer::readDifferentiabilityWitness(DeclID DId) {
35343534
assert(!isSerialized && "declaration must not be serialized");
35353535
}
35363536

3537-
auto linkage = fromStableSILLinkage(rawLinkage);
3538-
assert(linkage && "Expected value linkage for sil_differentiability_witness");
3537+
auto linkageOpt = fromStableSILLinkage(rawLinkage);
3538+
assert(linkageOpt &&
3539+
"Expected value linkage for sil_differentiability_witness");
35393540
auto originalName = MF->getIdentifierText(originalNameId);
35403541
auto jvpName = MF->getIdentifierText(jvpNameId);
35413542
auto vjpName = MF->getIdentifierText(vjpNameId);
@@ -3572,10 +3573,14 @@ SILDeserializer::readDifferentiabilityWitness(DeclID DId) {
35723573
auto *diffWitness =
35733574
SILMod.lookUpDifferentiabilityWitness({originalName, config});
35743575

3576+
// Witnesses that we deserialize are always available externally; we never
3577+
// want to emit them ourselves.
3578+
auto linkage = swift::addExternalToLinkage(*linkageOpt);
3579+
35753580
// If there is no existing differentiability witness, create one.
35763581
if (!diffWitness)
35773582
diffWitness = SILDifferentiabilityWitness::createDeclaration(
3578-
SILMod, *linkage, original, parameterIndices, resultIndices,
3583+
SILMod, linkage, original, parameterIndices, resultIndices,
35793584
derivativeGenSig);
35803585

35813586
// If the current differentiability witness is merely a declaration, and the

lib/TBDGen/TBDGen.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,30 @@ void TBDGenVisitor::addConformances(DeclContext *DC) {
178178
}
179179

180180
// SWIFT_ENABLE_TENSORFLOW
181+
void TBDGenVisitor::addAutoDiffLinearMapFunction(AbstractFunctionDecl *original,
182+
const DifferentiableAttr *attr,
183+
AutoDiffLinearMapKind kind) {
184+
auto declRef = SILDeclRef(original);
185+
186+
// Linear maps are only public when the original function is serialized.
187+
if (!declRef.isSerialized())
188+
return;
189+
190+
// Differentials are only emitted when forward mode is turned on.
191+
if (kind == AutoDiffLinearMapKind::Differential &&
192+
!original->getASTContext()
193+
.LangOpts.EnableExperimentalForwardModeDifferentiation)
194+
return;
195+
196+
auto *loweredParamIndices = autodiff::getLoweredParameterIndices(
197+
attr->getParameterIndices(),
198+
original->getInterfaceType()->castTo<AnyFunctionType>());
199+
Mangle::ASTMangler mangler;
200+
std::string linearMapName = mangler.mangleAutoDiffLinearMapHelper(
201+
declRef.mangle(), kind, SILAutoDiffIndices(0, loweredParamIndices));
202+
addSymbol(linearMapName);
203+
}
204+
181205
void TBDGenVisitor::addAutoDiffDerivativeFunction(
182206
AbstractFunctionDecl *original, const DifferentiableAttr *attr,
183207
AutoDiffDerivativeFunctionKind kind) {
@@ -208,6 +232,9 @@ void TBDGenVisitor::addDifferentiabilityWitness(
208232

209233
void TBDGenVisitor::addDifferentiableAttr(AbstractFunctionDecl *original,
210234
const DifferentiableAttr *attr) {
235+
addAutoDiffLinearMapFunction(original, attr,
236+
AutoDiffLinearMapKind::Differential);
237+
addAutoDiffLinearMapFunction(original, attr, AutoDiffLinearMapKind::Pullback);
211238
addAutoDiffDerivativeFunction(original, attr,
212239
AutoDiffDerivativeFunctionKind::JVP);
213240
addAutoDiffDerivativeFunction(original, attr,

lib/TBDGen/TBDGenVisitor.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ class TBDGenVisitor : public ASTVisitor<TBDGenVisitor> {
7575
void addBaseConformanceDescriptor(BaseConformance conformance);
7676

7777
// SWIFT_ENABLE_TENSORFLOW
78+
/// Adds the symbol for the linear map function of the given kind associated
79+
/// with the given original function and `@differentiable` attr.
80+
void addAutoDiffLinearMapFunction(AbstractFunctionDecl *original,
81+
const DifferentiableAttr *attr,
82+
AutoDiffLinearMapKind kind);
83+
7884
/// Adds the symbol for the autodiff function of the given kind associated
7985
/// with the given original function and `@differentiable` attr.
8086
void addAutoDiffDerivativeFunction(AbstractFunctionDecl *original,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import DifferentiationUnittest
2+
3+
@differentiable
4+
public func doubleThenApplyDefaultF(_ x: Tracked<Float>) -> Tracked<Float> {
5+
return x
6+
}
7+
8+
@differentiable
9+
public func doubleThenApply(
10+
_ x: Tracked<Float>,
11+
_ f: @differentiable (Tracked<Float>) -> Tracked<Float> = doubleThenApplyDefaultF
12+
) -> Tracked<Float> {
13+
return f(2 * x)
14+
}

test/AutoDiff/control_flow_sil.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func cond(_ x: Float) -> Float {
6969
// CHECK-SIL: return [[VJP_RESULT]]
7070

7171

72-
// CHECK-SIL-LABEL: sil hidden [ossa] @AD__cond__pullback_src_0_wrt_0 : $@convention(thin) (Float, @owned _AD__cond_bb3__PB__src_0_wrt_0) -> Float {
72+
// CHECK-SIL-LABEL: sil private [ossa] @AD__cond__pullback_src_0_wrt_0 : $@convention(thin) (Float, @owned _AD__cond_bb3__PB__src_0_wrt_0) -> Float {
7373
// CHECK-SIL: bb0([[SEED:%.*]] : $Float, [[BB3_PB_STRUCT:%.*]] : @owned $_AD__cond_bb3__PB__src_0_wrt_0):
7474
// CHECK-SIL: [[BB3_PRED:%.*]] = destructure_struct [[BB3_PB_STRUCT]] : $_AD__cond_bb3__PB__src_0_wrt_0
7575
// CHECK-SIL: switch_enum [[BB3_PRED]] : $_AD__cond_bb3__Pred__src_0_wrt_0, case #_AD__cond_bb3__Pred__src_0_wrt_0.bb2!enumelt.1: bb3, case #_AD__cond_bb3__Pred__src_0_wrt_0.bb1!enumelt.1: bb1
@@ -217,7 +217,7 @@ func cond_tuple_var(_ x: Float) -> Float {
217217
return y.1
218218
}
219219

220-
// CHECK-SIL-LABEL: sil hidden [ossa] @AD__cond_tuple_var__pullback_src_0_wrt_0 : $@convention(thin) (Float, @owned _AD__cond_tuple_var_bb3__PB__src_0_wrt_0) -> Float {
220+
// CHECK-SIL-LABEL: sil private [ossa] @AD__cond_tuple_var__pullback_src_0_wrt_0 : $@convention(thin) (Float, @owned _AD__cond_tuple_var_bb3__PB__src_0_wrt_0) -> Float {
221221
// CHECK-SIL: bb0([[SEED:%.*]] : $Float, [[BB3_PB_STRUCT:%.*]] : $_AD__cond_tuple_var_bb3__PB__src_0_wrt_0):
222222
// CHECK-SIL: [[BB3_PRED:%.*]] = destructure_struct [[BB3_PB_STRUCT]] : $_AD__cond_tuple_var_bb3__PB__src_0_wrt_0
223223
// CHECK-SIL: copy_addr {{%.*}} to {{%.*}} : $*(Float, Float)

test/AutoDiff/differentiation_transform_diagnostics.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,3 +466,35 @@ _ = gradient(at: Float(1), Float(2), in: (+) as @differentiable (Float, @nondiff
466466

467467
// expected-error @+1 {{conversion to '@differentiable(linear)' function type is not yet supported}}
468468
let _: @differentiable(linear) (Float) -> Float = { x in x }
469+
470+
//===----------------------------------------------------------------------===//
471+
// Differentiating from fragile functions
472+
//===----------------------------------------------------------------------===//
473+
474+
public func implicitlyDifferentiableFromFragile(_ x: Float) -> Float { x }
475+
476+
public func hasImplicitlyDifferentiatedTopLevelDefaultArgument(
477+
// expected-error @+2 {{function is not differentiable}}
478+
// expected-note @+1 {{differentiated functions in default arguments must be marked '@differentiable' or have a public '@derivative'}}
479+
_ f: @differentiable (Float) -> Float = implicitlyDifferentiableFromFragile
480+
) {}
481+
482+
// TODO(TF-1030): This will eventually not be an error.
483+
// expected-error @+2 {{function is not differentiable}}
484+
// expected-note @+1 {{differentiated functions in default arguments must be marked '@differentiable' or have a public '@derivative'; this is not possible with a closure, make a top-level function instead}}
485+
public func hasImplicitlyDifferentiatedClosureDefaultArgument(_ f: @differentiable (Float) -> Float = { $0 }) {}
486+
487+
@inlinable
488+
public func fragileFuncWithGradient() {
489+
// expected-error @+2 {{function is not differentiable}}
490+
// expected-note @+1 {{differentiated functions in '@inlinable' functions must be marked '@differentiable' or have a public '@derivative'}}
491+
let _ = gradient(at: 0, in: implicitlyDifferentiableFromFragile)
492+
}
493+
494+
@inlinable
495+
@differentiable
496+
public func fragileDifferentiable(_ x: Float) -> Float {
497+
// expected-error @+2 {{expression is not differentiable}}
498+
// expected-note @+1 {{differentiated functions in '@inlinable' functions must be marked '@differentiable' or have a public '@derivative'}}
499+
implicitlyDifferentiableFromFragile(x)
500+
}

0 commit comments

Comments
 (0)