Skip to content

Commit cda8e1c

Browse files
committed
[AutoDiff] Support ref_element_addr differentiation.
Support differentiation of `ref_element_addr`: class stored property references. Teach activity analysis about `ref_element_addr`, handling it like `struct_element_addr`. Do not propagate activity for `ref_element_addr` to `@noDerivative` members. Pullback generation rules: ``` Original: y = ref_element_addr x, <n> Adjoint: adj[x] += struct (0, ..., #field': adj[y], ..., 0) ^~~~~~~ field in tangent space corresponding to #field ``` Exposes TF-1149: cannot differentiate active value with loadable type but address-only `TangentVector` type. Diagnose for now. Resolves SR-12152.
1 parent 152d71c commit cda8e1c

File tree

11 files changed

+210
-60
lines changed

11 files changed

+210
-60
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,10 @@ WARNING(autodiff_nonvaried_result_fixit,none,
491491
"result does not depend on differentiation arguments and will always "
492492
"have a zero derivative; do you want to use 'withoutDerivative(at:)'?",
493493
())
494+
// TODO(TF-1149): Remove this diagnostic.
495+
NOTE(autodiff_loadable_value_addressonly_tangent_unsupported,none,
496+
"cannot differentiate value with loadable type %0 but address-only "
497+
"'TangentVector' type %1", (Type, Type))
494498
NOTE(autodiff_enums_unsupported,none,
495499
"differentiating enum values is not yet supported", ())
496500
NOTE(autodiff_global_let_closure_not_differentiable,none,
@@ -543,9 +547,6 @@ NOTE(autodiff_jvp_control_flow_not_supported,none,
543547
"forward-mode differentiation does not yet support control flow", ())
544548
NOTE(autodiff_control_flow_not_supported,none,
545549
"cannot differentiate unsupported control flow", ())
546-
// TODO(TF-645): Remove when differentiation supports `ref_element_addr`.
547-
NOTE(autodiff_class_property_not_supported,none,
548-
"differentiating class properties is not yet supported", ())
549550
// TODO(TF-1080): Remove when differentiation supports `begin_apply`.
550551
NOTE(autodiff_coroutines_not_supported,none,
551552
"differentiation of coroutine calls is not yet supported", ())

include/swift/SILOptimizer/Analysis/DifferentiableActivityAnalysis.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ class DifferentiableActivityInfo {
157157
/// Propagates variedness from the given operand to its user's results.
158158
void propagateVaried(Operand *operand, unsigned independentVariableIndex);
159159
/// Marks the given value as varied and recursively propagates variedness
160-
/// inwards (to operands) through projections. Skips `@noDerivative` struct
161-
/// field projections.
160+
/// inwards (to operands) through projections. Skips `@noDerivative` field
161+
/// projections.
162162
void
163163
propagateVariedInwardsThroughProjections(SILValue value,
164164
unsigned independentVariableIndex);
@@ -172,9 +172,9 @@ class DifferentiableActivityInfo {
172172
unsigned dependentVariableIndex);
173173
/// Propagates usefulnesss to the operands of the given instruction.
174174
void propagateUseful(SILInstruction *inst, unsigned dependentVariableIndex);
175-
/// Marks the given address as useful and recursively propagates usefulness
176-
/// inwards (to operands) through projections. Skips `@noDerivative` struct
177-
/// field projections.
175+
/// Marks the given address or class-typed value as useful and recursively
176+
/// propagates usefulness inwards (to operands) through projections. Skips
177+
/// `@noDerivative` field projections.
178178
void propagateUsefulThroughAddress(SILValue value,
179179
unsigned dependentVariableIndex);
180180
/// If the given value is an `array.uninitialized_intrinsic` application,

include/swift/SILOptimizer/Utils/Differentiation/PullbackEmitter.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,13 @@ class PullbackEmitter final : public SILInstructionVisitor<PullbackEmitter> {
354354
/// field in tangent space corresponding to #field
355355
void visitStructExtractInst(StructExtractInst *sei);
356356

357+
/// Handle `ref_element_addr` instruction.
358+
/// Original: y = ref_element_addr x, <n>
359+
/// Adjoint: adj[x] += struct (0, ..., #field': adj[y], ..., 0)
360+
/// ^~~~~~~
361+
/// field in tangent space corresponding to #field
362+
void visitRefElementAddrInst(RefElementAddrInst *reai);
363+
357364
/// Handle `tuple` instruction.
358365
/// Original: y = tuple (x0, x1, x2, ...)
359366
/// Adjoint: (adj[x0], adj[x1], adj[x2], ...) += destructure_tuple adj[y]
@@ -421,8 +428,6 @@ class PullbackEmitter final : public SILInstructionVisitor<PullbackEmitter> {
421428
UnconditionalCheckedCastAddrInst *uccai);
422429

423430
#define NOT_DIFFERENTIABLE(INST, DIAG) void visit##INST##Inst(INST##Inst *inst);
424-
425-
NOT_DIFFERENTIABLE(RefElementAddr, autodiff_class_property_not_supported)
426431
#undef NOT_DIFFERENTIABLE
427432

428433
#define NO_ADJOINT(INST) \

lib/SIL/SILFunctionType.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,13 @@ CanSILFunctionType SILFunctionType::getAutoDiffDerivativeFunctionType(
275275
switch (origResConv) {
276276
case ResultConvention::Owned:
277277
case ResultConvention::Autoreleased:
278-
conv = tl.isTrivial()
279-
? ParameterConvention::Direct_Unowned
280-
: ParameterConvention::Direct_Guaranteed;
278+
if (tl.isAddressOnly()) {
279+
conv = ParameterConvention::Indirect_In_Guaranteed;
280+
} else {
281+
conv = tl.isTrivial()
282+
? ParameterConvention::Direct_Unowned
283+
: ParameterConvention::Direct_Guaranteed;
284+
}
281285
break;
282286
case ResultConvention::Unowned:
283287
case ResultConvention::UnownedInnerPointer:
@@ -301,9 +305,12 @@ CanSILFunctionType SILFunctionType::getAutoDiffDerivativeFunctionType(
301305
case ParameterConvention::Direct_Owned:
302306
case ParameterConvention::Direct_Guaranteed:
303307
case ParameterConvention::Direct_Unowned:
304-
conv = tl.isTrivial()
305-
? ResultConvention::Unowned
306-
: ResultConvention::Owned;
308+
if (tl.isAddressOnly()) {
309+
conv = ResultConvention::Indirect;
310+
} else {
311+
conv = tl.isTrivial() ? ResultConvention::Unowned
312+
: ResultConvention::Owned;
313+
}
307314
break;
308315
case ParameterConvention::Indirect_In:
309316
case ParameterConvention::Indirect_Inout:

lib/SILOptimizer/Analysis/DifferentiableActivityAnalysis.cpp

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,20 @@ void DifferentiableActivityInfo::propagateVaried(
166166
setVariedAndPropagateToUsers(teai, i);
167167
}
168168
}
169-
// Handle `struct_extract` and `struct_element_addr` instructions.
169+
// Handle element projection instructions:
170+
// `struct_extract`, `struct_element_addr`, `ref_element_addr`.
170171
// - If the field is marked `@noDerivative`, do not set the result as
171172
// varied because it does not need a derivative.
172173
// - Otherwise, propagate variedness from operand to result as usual.
173-
#define PROPAGATE_VARIED_FOR_STRUCT_EXTRACTION(INST) \
174-
else if (auto *sei = dyn_cast<INST##Inst>(inst)) { \
175-
if (isVaried(sei->getOperand(), i) && \
176-
!sei->getField()->getAttrs().hasAttribute<NoDerivativeAttr>()) \
177-
setVariedAndPropagateToUsers(sei, i); \
174+
#define PROPAGATE_VARIED_FOR_ELEMENT_PROJECTION(INST) \
175+
else if (auto *projInst = dyn_cast<INST##Inst>(inst)) { \
176+
if (isVaried(projInst->getOperand(), i) && \
177+
!projInst->getField()->getAttrs().hasAttribute<NoDerivativeAttr>()) \
178+
setVariedAndPropagateToUsers(projInst, i); \
178179
}
179-
PROPAGATE_VARIED_FOR_STRUCT_EXTRACTION(StructExtract)
180-
PROPAGATE_VARIED_FOR_STRUCT_EXTRACTION(StructElementAddr)
180+
PROPAGATE_VARIED_FOR_ELEMENT_PROJECTION(StructExtract)
181+
PROPAGATE_VARIED_FOR_ELEMENT_PROJECTION(StructElementAddr)
182+
PROPAGATE_VARIED_FOR_ELEMENT_PROJECTION(RefElementAddr)
181183
#undef PROPAGATE_VARIED_FOR_STRUCT_EXTRACTION
182184
// Handle `br`.
183185
else if (auto *bi = dyn_cast<BranchInst>(inst)) {
@@ -222,13 +224,14 @@ static Optional<AccessorKind> getAccessorKind(SILFunction *fn) {
222224
void DifferentiableActivityInfo::propagateVariedInwardsThroughProjections(
223225
SILValue value, unsigned independentVariableIndex) {
224226
auto i = independentVariableIndex;
225-
// Skip `@noDerivative` struct projections.
227+
// Skip `@noDerivative` projections.
226228
#define SKIP_NODERIVATIVE(INST) \
227-
if (auto *sei = dyn_cast<INST##Inst>(value)) \
228-
if (sei->getField()->getAttrs().hasAttribute<NoDerivativeAttr>()) \
229+
if (auto *projInst = dyn_cast<INST##Inst>(value)) \
230+
if (projInst->getField()->getAttrs().hasAttribute<NoDerivativeAttr>()) \
229231
return;
230232
SKIP_NODERIVATIVE(StructExtract)
231233
SKIP_NODERIVATIVE(StructElementAddr)
234+
SKIP_NODERIVATIVE(RefElementAddr)
232235
#undef SKIP_NODERIVATIVE
233236
// Set value as varied and propagate to users.
234237
setVariedAndPropagateToUsers(value, i);
@@ -274,7 +277,8 @@ void DifferentiableActivityInfo::setUsefulAndPropagateToOperands(
274277
// Skip already-useful values to prevent infinite recursion.
275278
if (isUseful(value, dependentVariableIndex))
276279
return;
277-
if (value->getType().isAddress()) {
280+
if (value->getType().isAddress() ||
281+
value->getType().getClassOrBoundGenericClass()) {
278282
propagateUsefulThroughAddress(value, dependentVariableIndex);
279283
return;
280284
}
@@ -331,15 +335,16 @@ void DifferentiableActivityInfo::propagateUseful(
331335
PROPAGATE_USEFUL_THROUGH_STORE(CopyAddr)
332336
PROPAGATE_USEFUL_THROUGH_STORE(UnconditionalCheckedCastAddr)
333337
#undef PROPAGATE_USEFUL_THROUGH_STORE
334-
// Handle struct element extraction, skipping `@noDerivative` fields:
335-
// `struct_extract`, `struct_element_addr`.
336-
#define PROPAGATE_USEFUL_THROUGH_STRUCT_EXTRACTION(INST) \
337-
else if (auto *sei = dyn_cast<INST##Inst>(inst)) { \
338-
if (!sei->getField()->getAttrs().hasAttribute<NoDerivativeAttr>()) \
339-
setUsefulAndPropagateToOperands(sei->getOperand(), i); \
338+
// Handle element projections, skipping `@noDerivative` fields:
339+
// `struct_extract`, `struct_element_addr`, `ref_element_addr`.
340+
#define PROPAGATE_USEFUL_THROUGH_ELEMENT_PROJECTION(INST) \
341+
else if (auto *projInst = dyn_cast<INST##Inst>(inst)) { \
342+
if (!projInst->getField()->getAttrs().hasAttribute<NoDerivativeAttr>()) \
343+
setUsefulAndPropagateToOperands(projInst->getOperand(), i); \
340344
}
341-
PROPAGATE_USEFUL_THROUGH_STRUCT_EXTRACTION(StructExtract)
342-
PROPAGATE_USEFUL_THROUGH_STRUCT_EXTRACTION(StructElementAddr)
345+
PROPAGATE_USEFUL_THROUGH_ELEMENT_PROJECTION(StructExtract)
346+
PROPAGATE_USEFUL_THROUGH_ELEMENT_PROJECTION(StructElementAddr)
347+
PROPAGATE_USEFUL_THROUGH_ELEMENT_PROJECTION(RefElementAddr)
343348
#undef PROPAGATE_USEFUL_THROUGH_STRUCT_EXTRACTION
344349
// Handle everything else.
345350
else {
@@ -350,7 +355,8 @@ void DifferentiableActivityInfo::propagateUseful(
350355

351356
void DifferentiableActivityInfo::propagateUsefulThroughAddress(
352357
SILValue value, unsigned dependentVariableIndex) {
353-
assert(value->getType().isAddress());
358+
assert(value->getType().isAddress() ||
359+
value->getType().getClassOrBoundGenericClass());
354360
// Skip already-useful values to prevent infinite recursion.
355361
if (isUseful(value, dependentVariableIndex))
356362
return;
@@ -364,13 +370,15 @@ void DifferentiableActivityInfo::propagateUsefulThroughAddress(
364370
propagateUseful(use->getUser(), dependentVariableIndex);
365371
for (auto res : use->getUser()->getResults()) {
366372
#define SKIP_NODERIVATIVE(INST) \
367-
if (auto *sei = dyn_cast<INST##Inst>(res)) \
368-
if (sei->getField()->getAttrs().hasAttribute<NoDerivativeAttr>()) \
373+
if (auto *projInst = dyn_cast<INST##Inst>(res)) \
374+
if (projInst->getField()->getAttrs().hasAttribute<NoDerivativeAttr>()) \
369375
continue;
370376
SKIP_NODERIVATIVE(StructExtract)
371377
SKIP_NODERIVATIVE(StructElementAddr)
378+
SKIP_NODERIVATIVE(RefElementAddr)
372379
#undef SKIP_NODERIVATIVE
373-
if (Projection::isAddressProjection(res) || isa<BeginAccessInst>(res))
380+
if (Projection::isAddressProjection(res) || isa<BeginAccessInst>(res) ||
381+
isa<BeginBorrowInst>(res))
374382
propagateUsefulThroughAddress(res, dependentVariableIndex);
375383
}
376384
}

lib/SILOptimizer/Utils/Differentiation/PullbackEmitter.cpp

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ SILValue PullbackEmitter::getAdjointProjection(SILBasicBlock *origBB,
328328
auto adjSource = getAdjointBuffer(origBB, seai->getOperand());
329329
auto *tangentVectorDecl =
330330
adjSource->getType().getStructOrBoundGenericStruct();
331+
// TODO(TF-970): Emit diagnostic when `TangentVector` is not a struct.
331332
auto tanFieldLookup =
332333
tangentVectorDecl->lookupDirect(seai->getField()->getName());
333334
assert(tanFieldLookup.size() == 1);
@@ -505,22 +506,52 @@ bool PullbackEmitter::run() {
505506
auto &domBBActiveValues = activeValues[domNode->getBlock()];
506507
bbActiveValues.append(domBBActiveValues.begin(), domBBActiveValues.end());
507508
}
509+
// Booleans tracking whether active-value-related errors have been emitted.
510+
// This prevents duplicate diagnostics for the same active values.
508511
bool diagnosedActiveEnumValue = false;
512+
bool diagnosedActiveValueTangentValueCategoryIncompatible = false;
509513
// Mark the activity of a value if it has not yet been visited.
510514
auto markValueActivity = [&](SILValue v) {
511515
if (visited.count(v))
512516
return;
513517
visited.insert(v);
518+
auto type = v->getType();
514519
// Diagnose active enum values. Differentiation of enum values requires
515520
// special adjoint value handling and is not yet supported. Diagnose
516521
// only the first active enum value to prevent too many diagnostics.
517-
if (!diagnosedActiveEnumValue &&
518-
v->getType().getEnumOrBoundGenericEnum()) {
522+
if (!diagnosedActiveEnumValue && type.getEnumOrBoundGenericEnum()) {
519523
getContext().emitNondifferentiabilityError(
520524
v, getInvoker(), diag::autodiff_enums_unsupported);
521525
errorOccurred = true;
522526
diagnosedActiveEnumValue = true;
523527
}
528+
// Diagnose active values whose value category is incompatible with their
529+
// tangent types's value category.
530+
//
531+
// Let $L be a loadable type and $*A be an address-only type.
532+
// Table of supported combinations:
533+
//
534+
// Original type | Tangent type | Currently supported?
535+
// --------------|--------------|---------------------
536+
// $L | $L | Yes (no mismatch)
537+
// $*A | $L | Yes (can create $*L adjoint buffer)
538+
// $L | $*A | No (cannot create $A adjoint value)
539+
// $*A | $*A | Yes (no mismatch)
540+
if (!diagnosedActiveValueTangentValueCategoryIncompatible) {
541+
if (auto tanSpace = getTangentSpace(remapType(type).getASTType())) {
542+
auto tanASTType = tanSpace->getCanonicalType();
543+
auto &origTL = getTypeLowering(type.getASTType());
544+
auto &tanTL = getTypeLowering(tanASTType);
545+
if (!origTL.isAddressOnly() && tanTL.isAddressOnly()) {
546+
getContext().emitNondifferentiabilityError(
547+
v, getInvoker(),
548+
diag::autodiff_loadable_value_addressonly_tangent_unsupported,
549+
type.getASTType(), tanASTType);
550+
diagnosedActiveValueTangentValueCategoryIncompatible = true;
551+
errorOccurred = true;
552+
}
553+
}
554+
}
524555
// Skip address projections.
525556
// Address projections do not need their own adjoint buffers; they
526557
// become projections into their adjoint base buffer.
@@ -1050,7 +1081,7 @@ PullbackEmitter::getArrayAdjointElementBuffer(SILValue arrayAdjoint,
10501081
auto subMap = SubstitutionMap::get(subscriptFnGenSig, {eltTanType},
10511082
{addArithConf, diffConf});
10521083
// %elt_adj = alloc_stack $T.TangentVector
1053-
// Create a local allocation and register it.
1084+
// Create and register a local allocation.
10541085
localAllocBuilder.setInsertionPoint(
10551086
getPullback().getEntryBlock(),
10561087
getNextFunctionLocalAllocationInsertionPoint());
@@ -1293,6 +1324,7 @@ void PullbackEmitter::visitStructExtractInst(StructExtractInst *sei) {
12931324
assert(!getTypeLowering(tangentVectorTy).isAddressOnly());
12941325
auto tangentVectorSILTy = SILType::getPrimitiveObjectType(tangentVectorTy);
12951326
auto *tangentVectorDecl = tangentVectorTy->getStructOrBoundGenericStruct();
1327+
// TODO(TF-970): Emit diagnostic when `TangentVector` is not a struct.
12961328
assert(tangentVectorDecl);
12971329
// Find the corresponding field in the tangent space.
12981330
VarDecl *tanField = nullptr;
@@ -1343,6 +1375,53 @@ void PullbackEmitter::visitStructExtractInst(StructExtractInst *sei) {
13431375
}
13441376
}
13451377

1378+
void PullbackEmitter::visitRefElementAddrInst(RefElementAddrInst *reai) {
1379+
auto *bb = reai->getParent();
1380+
auto adjBuf = getAdjointBuffer(bb, reai);
1381+
auto classTy = remapType(reai->getOperand()->getType()).getASTType();
1382+
auto tangentVectorTy =
1383+
getTangentSpace(classTy)->getType()->getCanonicalType();
1384+
assert(!getTypeLowering(tangentVectorTy).isAddressOnly());
1385+
auto tangentVectorSILTy = SILType::getPrimitiveObjectType(tangentVectorTy);
1386+
auto *tangentVectorDecl = tangentVectorTy->getStructOrBoundGenericStruct();
1387+
// TODO(TF-970): Emit diagnostic when `TangentVector` is not a struct.
1388+
assert(tangentVectorDecl);
1389+
// Look up the corresponding field in the tangent space by name.
1390+
VarDecl *tanField = nullptr;
1391+
auto tanFieldLookup =
1392+
tangentVectorDecl->lookupDirect(reai->getField()->getName());
1393+
if (tanFieldLookup.empty()) {
1394+
getContext().emitNondifferentiabilityError(
1395+
reai, getInvoker(),
1396+
diag::autodiff_stored_property_no_corresponding_tangent,
1397+
reai->getClassDecl()->getNameStr(), reai->getField()->getNameStr());
1398+
errorOccurred = true;
1399+
return;
1400+
}
1401+
tanField = cast<VarDecl>(tanFieldLookup.front());
1402+
// Accumulate adjoint for the `ref_element_addr` operand.
1403+
SmallVector<AdjointValue, 8> eltVals;
1404+
for (auto *field : tangentVectorDecl->getStoredProperties()) {
1405+
if (field == tanField) {
1406+
auto adjElt = builder.emitLoadValueOperation(
1407+
reai->getLoc(), adjBuf, LoadOwnershipQualifier::Copy);
1408+
eltVals.push_back(makeConcreteAdjointValue(adjElt));
1409+
recordTemporary(adjElt);
1410+
} else {
1411+
auto substMap = tangentVectorTy->getMemberSubstitutionMap(
1412+
field->getModuleContext(), field);
1413+
auto fieldTy = field->getType().subst(substMap);
1414+
auto fieldSILTy = getContext().getTypeConverter().getLoweredType(
1415+
fieldTy, TypeExpansionContext::minimal());
1416+
assert(fieldSILTy.isObject());
1417+
eltVals.push_back(makeZeroAdjointValue(fieldSILTy));
1418+
}
1419+
}
1420+
addAdjointValue(bb, reai->getOperand(),
1421+
makeAggregateAdjointValue(tangentVectorSILTy, eltVals),
1422+
reai->getLoc());
1423+
}
1424+
13461425
void PullbackEmitter::visitTupleInst(TupleInst *ti) {
13471426
auto *bb = ti->getParent();
13481427
auto av = getAdjointValue(bb, ti);
@@ -1558,7 +1637,6 @@ void PullbackEmitter::visitUnconditionalCheckedCastAddrInst(
15581637
errorOccurred = true; \
15591638
return; \
15601639
}
1561-
NOT_DIFFERENTIABLE(RefElementAddr, autodiff_class_property_not_supported)
15621640
#undef NOT_DIFFERENTIABLE
15631641

15641642
AdjointValue PullbackEmitter::makeZeroAdjointValue(SILType type) {

test/AutoDiff/downstream/activity_analysis.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,35 @@ func testBeginApplyActiveButInitiallyNonactiveInoutArgument(x: Float) -> Float {
569569
// CHECK: [NONE] %34 = apply %33<Float>(%32, %29, %31) : $@convention(method) <τ_0_0> (Int, @guaranteed Array<τ_0_0>) -> @out τ_0_0
570570
// CHECK: [ACTIVE] %35 = load [trivial] %32 : $*Float
571571

572+
//===----------------------------------------------------------------------===//
573+
// Class differentiation
574+
//===----------------------------------------------------------------------===//
575+
576+
class C: Differentiable {
577+
@differentiable
578+
var float: Float
579+
580+
init(_ float: Float) {
581+
self.float = float
582+
}
583+
584+
@differentiable
585+
func method(_ x: Float) -> Float {
586+
x * float
587+
}
588+
589+
// CHECK-LABEL: [AD] Activity info for ${{.*}}1CC6methodyS2fF at (source=0 parameters=(0 1))
590+
// CHECK: bb0:
591+
// CHECK: [ACTIVE] %0 = argument of bb0 : $Float
592+
// CHECK: [ACTIVE] %1 = argument of bb0 : $C
593+
// CHECK: [USEFUL] %4 = metatype $@thin Float.Type
594+
// CHECK: [VARIED] %5 = class_method %1 : $C, #C.float!getter.1 : (C) -> () -> Float, $@convention(method) (@guaranteed C) -> Float
595+
// CHECK: [ACTIVE] %6 = apply %5(%1) : $@convention(method) (@guaranteed C) -> Float
596+
// CHECK: [NONE] // function_ref static Float.* infix(_:_:)
597+
// CHECK: %7 = function_ref @$sSf1moiyS2f_SftFZ : $@convention(method) (Float, Float, @thin Float.Type) -> Float
598+
// CHECK: [ACTIVE] %8 = apply %7(%0, %6, %4) : $@convention(method) (Float, Float, @thin Float.Type) -> Float
599+
}
600+
572601
//===----------------------------------------------------------------------===//
573602
// Enum differentiation
574603
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)