Skip to content

Commit 482b264

Browse files
committed
Reapply "Merge pull request #1725 from atrick/specialize"
This was mistakenly reverted in an attempt to fix buildbots. Unfortunately it's now smashed into one commit. --- Introduce @_specialize(<type list>) internal attribute. This attribute can be attached to generic functions. The attribute's arguments must be a list of concrete types to be substituted in the function's generic signature. Any number of specializations may be associated with a generic function. This attribute provides a hint to the compiler. At -O, the compiler will generate the specified specializations and emit calls to the specialized code in the original generic function guarded by type checks. The current attribute is designed to be an internal tool for performance experimentation. It does not affect the language or API. This work may be extended in the future to add user-visible attributes that do provide API guarantees and/or direct dispatch to specialized code. This attribute works on any generic function: a freestanding function with generic type parameters, a nongeneric method declared in a generic class, a generic method in a nongeneric class or a generic method in a generic class. A function's generic signature is a concatenation of the generic context and the function's own generic type parameters. e.g. struct S<T> { var x: T @_specialize(Int, Float) mutating func exchangeSecond<U>(u: U, _ t: T) -> (U, T) { x = t return (u, x) } } // Substitutes: <T, U> with <Int, Float> producing: // S<Int>::exchangeSecond<Float>(u: Float, t: Int) -> (Float, Int) --- [SILOptimizer] Introduce an eager-specializer pass. This pass finds generic functions with @_specialized attributes and generates specialized code for the attribute's concrete types. It inserts type checks and guarded dispatch at the beginning of the generic function for each specialization. Since we don't currently expose this attribute as API and don't specialize vtables and witness tables yet, the only way to reach the specialized code is by calling the generic function which performs the guarded dispatch. In the future, we can build on this work in several ways: - cross module dispatch directly to specialized code - dynamic dispatch directly to specialized code - automated specialization based on less specific hints - partial specialization - and so on... I reorganized and refactored the optimizer's generic utilities to support direct function specialization as opposed to apply specialization.
1 parent ebb8d73 commit 482b264

32 files changed

+1862
-280
lines changed

docs/Generics.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,35 @@ the virtual dispatch, inline calls when appropriate, and eliminate the overhead
713713
of the generic system. Such optimizations can be performed based on heuristics,
714714
user direction, or profile-guided optimization.
715715

716+
An internal @_specialize function attribute allows developers to force
717+
full specialization by listing concrete type names corresponding to the
718+
function's generic signature. A function's generic signature is a
719+
concatenation of its generic context and the function's own generic
720+
type parameters.::
721+
722+
struct S<T> {
723+
var x: T
724+
@_specialize(Int, Float)
725+
mutating func exchangeSecond<U>(u: U, _ t: T) -> (U, T) {
726+
x = t
727+
return (u, x)
728+
}
729+
}
730+
731+
// Substitutes: <T, U> with <Int, Float> producing:
732+
// S<Int>::exchangeSecond<Float>(u: Float, t: Int) -> (Float, Int)
733+
734+
@_specialize currently acts as a hint to the optimizer, which
735+
generates type checks and code to dispatch to the specialized routine
736+
without affecting the signature of the generic function. The
737+
intention is to support efforts at evaluating the performance of
738+
specialized code. The performance impact is not guaranteed and is
739+
likely to change with the optimizer. This attribute should only be
740+
used in conjunction with rigorous performance analysis. Eventually,
741+
a similar attribute could be defined in the language, allowing it to be
742+
exposed as part of a function's API. That would allow direct dispatch
743+
to specialized code without type checks, even across modules.
744+
716745
Existential Types and Generics
717746
------------------------------
718747

include/swift/AST/Attr.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ SIMPLE_DECL_ATTR(nonobjc, NonObjC,
164164
SIMPLE_DECL_ATTR(_fixed_layout, FixedLayout,
165165
OnVar | OnClass | OnStruct | OnEnum | UserInaccessible, 31)
166166

167+
DECL_ATTR(_specialize, Specialize,
168+
OnConstructor | OnFunc | AllowMultipleAttributes | LongAttribute
169+
| UserInaccessible, 32)
170+
167171
// Non-serialized attributes.
168172

169173
SIMPLE_DECL_ATTR(mutating, Mutating, OnFunc | DeclModifier | NotSerialized,

include/swift/AST/Attr.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "swift/Basic/Range.h"
2424
#include "swift/AST/Identifier.h"
2525
#include "swift/AST/AttrKind.h"
26+
#include "swift/AST/ConcreteDeclRef.h"
2627
#include "swift/AST/KnownProtocols.h"
2728
#include "swift/AST/Ownership.h"
2829
#include "swift/AST/PlatformKind.h"
@@ -38,6 +39,7 @@ class ASTContext;
3839
struct PrintOptions;
3940
class Decl;
4041
class ClassDecl;
42+
struct TypeLoc;
4143

4244
class InfixData {
4345
unsigned Precedence : 8;
@@ -1136,6 +1138,36 @@ class Swift3MigrationAttr : public DeclAttribute {
11361138
}
11371139
};
11381140

1141+
/// The @_specialize attribute, which forces specialization on the specified
1142+
/// type list.
1143+
class SpecializeAttr : public DeclAttribute {
1144+
unsigned numTypes;
1145+
ConcreteDeclRef specializedDecl;
1146+
1147+
TypeLoc *getTypeLocData() {
1148+
return reinterpret_cast<TypeLoc *>(this + 1);
1149+
}
1150+
1151+
SpecializeAttr(SourceLoc atLoc, SourceRange Range,
1152+
ArrayRef<TypeLoc> typeLocs);
1153+
1154+
public:
1155+
static SpecializeAttr *create(ASTContext &Ctx, SourceLoc atLoc,
1156+
SourceRange Range, ArrayRef<TypeLoc> typeLocs);
1157+
1158+
ArrayRef<TypeLoc> getTypeLocs() const;
1159+
1160+
MutableArrayRef<TypeLoc> getTypeLocs();
1161+
1162+
ConcreteDeclRef getConcreteDecl() const { return specializedDecl; }
1163+
1164+
void setConcreteDecl(ConcreteDeclRef ref) { specializedDecl = ref; }
1165+
1166+
static bool classof(const DeclAttribute *DA) {
1167+
return DA->getKind() == DAK_Specialize;
1168+
}
1169+
};
1170+
11391171
/// \brief Attributes that may be applied to declarations.
11401172
class DeclAttributes {
11411173
/// Linked list of declaration attributes.

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,6 +1976,8 @@ ERROR(generic_type_requires_arguments,none,
19761976
"reference to generic type %0 requires arguments in <...>", (Type))
19771977
NOTE(generic_type_declared_here,none,
19781978
"generic type %0 declared here", (Identifier))
1979+
ERROR(cannot_partially_specialize_generic_function,none,
1980+
"cannot partially specialize a generic function", ())
19791981

19801982
// Ambiguities
19811983
ERROR(ambiguous_decl_ref,none,

include/swift/SIL/SILFunction.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ enum IsTransparent_t { IsNotTransparent, IsTransparent };
3838
enum Inline_t { InlineDefault, NoInline, AlwaysInline };
3939
enum IsThunk_t { IsNotThunk, IsThunk, IsReabstractionThunk };
4040

41+
class SILSpecializeAttr final :
42+
private llvm::TrailingObjects<SILSpecializeAttr, Substitution> {
43+
friend TrailingObjects;
44+
45+
unsigned numSubs;
46+
47+
SILSpecializeAttr(ArrayRef<Substitution> subs);
48+
49+
public:
50+
static SILSpecializeAttr *create(SILModule &M, ArrayRef<Substitution> subs);
51+
52+
ArrayRef<Substitution> getSubstitutions() const {
53+
return { getTrailingObjects<Substitution>(), numSubs };
54+
}
55+
56+
void print(llvm::raw_ostream &OS) const;
57+
};
58+
4159
/// SILFunction - A function body that has been lowered to SIL. This consists of
4260
/// zero or more SIL SILBasicBlock objects that contain the SILInstruction
4361
/// objects making up the function.
@@ -141,6 +159,9 @@ class SILFunction
141159
/// StringRefs?
142160
llvm::SmallVector<std::string, 1> SemanticsAttrSet;
143161

162+
/// The function's remaining set of specialize attributes.
163+
std::vector<SILSpecializeAttr*> SpecializeAttrSet;
164+
144165
/// The function's effects attribute.
145166
EffectsKind EffectsKindAttr;
146167

@@ -385,6 +406,18 @@ class SILFunction
385406
SemanticsAttrSet.erase(Iter);
386407
}
387408

409+
/// \returns the range of specialize attributes.
410+
ArrayRef<SILSpecializeAttr*> getSpecializeAttrs() const {
411+
return SpecializeAttrSet;
412+
}
413+
414+
/// Removes all specialize attributes from this function.
415+
void clearSpecializeAttrs() { SpecializeAttrSet.clear(); }
416+
417+
void addSpecializeAttr(SILSpecializeAttr *attr) {
418+
SpecializeAttrSet.push_back(attr);
419+
}
420+
388421
/// \returns True if the function is optimizable (i.e. not marked as no-opt),
389422
/// or is raw SIL (so that the mandatory passes still run).
390423
bool shouldOptimize() const;

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ PASS(DiagnoseUnreachable, "diagnose-unreachable",
8888
"Diagnose Unreachable Code")
8989
PASS(DiagnosticConstantPropagation, "diagnostic-constant-propagation",
9090
"Propagate constants and emit diagnostics")
91+
PASS(EagerSpecializer, "eager-specializer",
92+
"Specialize speculatively and insert dispatch guarded by type checks")
9193
PASS(EarlyCodeMotion, "early-codemotion",
9294
"Code motion without release hoisting")
9395
PASS(EarlyInliner, "early-inline",

include/swift/SILOptimizer/Utils/GenericCloner.h

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,25 @@ class GenericCloner : public TypeSubstCloner<GenericCloner> {
4040
GenericCloner(SILFunction *F,
4141
const ReabstractionInfo &ReInfo,
4242
TypeSubstitutionMap &ContextSubs,
43+
ArrayRef<Substitution> ParamSubs,
4344
StringRef NewName,
44-
ArrayRef<Substitution> ApplySubs,
4545
CloneCollector::CallbackType Callback)
4646
: TypeSubstCloner(*initCloned(F, ReInfo, NewName), *F, ContextSubs,
47-
ApplySubs), ReInfo(ReInfo), Callback(Callback) {
47+
ParamSubs), ReInfo(ReInfo), Callback(Callback) {
4848
assert(F->getDebugScope()->Parent != getCloned()->getDebugScope()->Parent);
4949
}
5050
/// Clone and remap the types in \p F according to the substitution
5151
/// list in \p Subs. Parameters are re-abstracted (changed from indirect to
5252
/// direct) according to \p ReInfo.
53-
static SILFunction *cloneFunction(SILFunction *F,
54-
const ReabstractionInfo &ReInfo,
55-
TypeSubstitutionMap &ContextSubs,
56-
StringRef NewName, ApplySite Caller,
57-
CloneCollector::CallbackType Callback =nullptr) {
53+
static SILFunction *
54+
cloneFunction(SILFunction *F,
55+
const ReabstractionInfo &ReInfo,
56+
TypeSubstitutionMap &ContextSubs,
57+
ArrayRef<Substitution> ParamSubs,
58+
StringRef NewName,
59+
CloneCollector::CallbackType Callback =nullptr) {
5860
// Clone and specialize the function.
59-
GenericCloner SC(F, ReInfo, ContextSubs, NewName,
60-
Caller.getSubstitutions(), Callback);
61+
GenericCloner SC(F, ReInfo, ContextSubs, ParamSubs, NewName, Callback);
6162
SC.populateCloned();
6263
SC.cleanUp(SC.getCloned());
6364
return SC.getCloned();

include/swift/SILOptimizer/Utils/Generics.h

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,51 @@
2222
#include "swift/SIL/SILFunction.h"
2323
#include "swift/SIL/SILInstruction.h"
2424
#include "swift/SILOptimizer/Utils/Local.h"
25-
#include "llvm/ADT/BitVector.h"
25+
#include "llvm/ADT/SmallBitVector.h"
2626
#include "llvm/Support/CommandLine.h"
2727
#include "llvm/Support/Debug.h"
2828

2929
namespace swift {
3030

31+
/// Tries to specialize an \p Apply of a generic function. It can be a full
32+
/// apply site or a partial apply.
33+
/// Replaced and now dead instructions are returned in \p DeadApplies.
34+
/// New created functions, like the specialized callee and thunks, are returned
35+
/// in \p NewFunctions.
36+
///
37+
/// This is the top-level entry point for specializing an existing call site.
38+
void trySpecializeApplyOfGeneric(
39+
ApplySite Apply,
40+
llvm::SmallVectorImpl<SILInstruction *> &DeadApplies,
41+
llvm::SmallVectorImpl<SILFunction *> &NewFunctions);
42+
3143
/// Helper class to describe re-abstraction of function parameters done during
3244
/// specialization.
3345
///
3446
/// Specifically, it contains information which parameters and returns are
3547
/// changed from indirect values to direct values.
3648
class ReabstractionInfo {
49+
/// A 1-bit means that this parameter/return value is converted from indirect
50+
/// to direct.
51+
llvm::SmallBitVector Conversions;
52+
53+
/// The first NumResults bits in Conversions refer to indirect out-parameters.
54+
unsigned NumResults;
55+
56+
/// The function type after applying the substitutions of the original
57+
/// apply site.
58+
CanSILFunctionType SubstitutedType;
59+
60+
/// The function type after applying the re-abstractions on the
61+
/// SubstitutedType.
62+
CanSILFunctionType SpecializedType;
63+
3764
public:
38-
/// Constructs the ReabstractionInfo for an apply site \p AI calling the
39-
/// generic function \p Orig.
65+
/// Constructs the ReabstractionInfo for generic function \p Orig with
66+
/// substitutions \p ParamSubs.
4067
/// If specialization is not possible getSpecializedType() will return an
4168
/// invalid type.
42-
ReabstractionInfo(SILFunction *Orig, ApplySite AI);
69+
ReabstractionInfo(SILFunction *Orig, ArrayRef<Substitution> ParamSubs);
4370

4471
/// Does the \p ArgIdx refer to an indirect out-parameter?
4572
bool isResultIndex(unsigned ArgIdx) const {
@@ -88,8 +115,8 @@ class ReabstractionInfo {
88115
return Conversions.size() - numArgs;
89116
}
90117

91-
/// Get the function type after applying the substitutions of the original
92-
/// apply site.
118+
/// Get the function type after applying the substitutions to the original
119+
/// generic function.
93120
CanSILFunctionType getSubstitutedType() const { return SubstitutedType; }
94121

95122
/// Get the function type after applying the re-abstractions on the
@@ -101,41 +128,61 @@ class ReabstractionInfo {
101128
/// SubstFTy by applying the re-abstractions.
102129
CanSILFunctionType createSpecializedType(CanSILFunctionType SubstFTy,
103130
SILModule &M) const;
104-
private:
105-
/// A 1-bit means that this parameter/return value is converted from indirect
106-
/// to direct.
107-
llvm::BitVector Conversions;
108-
109-
/// The first NumResults bits in Conversions refer to indirect out-parameters.
110-
unsigned NumResults;
131+
};
111132

112-
/// The function type after applying the substitutions of the original
113-
/// apply site.
114-
CanSILFunctionType SubstitutedType;
133+
/// Helper class for specializing a generic function given a list of
134+
/// substitutions.
135+
class GenericFuncSpecializer {
136+
SILModule &M;
137+
SILFunction *GenericFunc;
138+
ArrayRef<Substitution> ParamSubs;
139+
const ReabstractionInfo &ReInfo;
115140

116-
/// The function type after applying the re-abstractions on the
117-
/// SubstitutedType.
118-
CanSILFunctionType SpecializedType;
141+
TypeSubstitutionMap ContextSubs;
142+
std::string ClonedName;
143+
public:
144+
GenericFuncSpecializer(SILFunction *GenericFunc,
145+
ArrayRef<Substitution> ParamSubs,
146+
const ReabstractionInfo &ReInfo);
147+
148+
/// If we already have this specialization, reuse it.
149+
SILFunction *lookupSpecialization();
150+
151+
/// Return a newly created specialized function.
152+
SILFunction *tryCreateSpecialization();
153+
154+
/// Try to specialize GenericFunc given a list of ParamSubs.
155+
/// Returns either a new or existing specialized function, or nullptr.
156+
SILFunction *trySpecialization() {
157+
if (!ReInfo.getSpecializedType())
158+
return nullptr;
159+
160+
SILFunction *SpecializedF = lookupSpecialization();
161+
if (!SpecializedF)
162+
SpecializedF = tryCreateSpecialization();
163+
164+
return SpecializedF;
165+
}
119166
};
120167

121-
/// Tries to specialize an \p Apply of a generic function. It can be a full
122-
/// apply site or a partial apply.
123-
/// Replaced and now dead instructions are returned in \p DeadApplies.
124-
/// New created functions, like the specialized callee and thunks, are returned
125-
/// in \p NewFunctions.
126-
void trySpecializeApplyOfGeneric(ApplySite Apply,
127-
llvm::SmallVectorImpl<SILInstruction *> &DeadApplies,
128-
llvm::SmallVectorImpl<SILFunction *> &NewFunctions);
168+
// =============================================================================
169+
// Prespecialized symbol lookup.
170+
// =============================================================================
129171

130-
/// Checks if a given mangled name could be a name of a whitelisted specialization.
172+
/// Checks if a given mangled name could be a name of a whitelisted
173+
/// specialization.
131174
bool isWhitelistedSpecialization(StringRef SpecName);
132175

133176
/// Create a new apply based on an old one, but with a different
134177
/// function being applied.
135178
ApplySite replaceWithSpecializedFunction(ApplySite AI, SILFunction *NewF,
136179
const ReabstractionInfo &ReInfo);
137180

138-
SILFunction *getExistingSpecialization(SILModule &M, StringRef FunctionName);
181+
/// Returns a SILFunction for the symbol specified by FunctioName if it is
182+
/// visible to the current SILModule. This is used to link call sites to
183+
/// externally defined specialization and should only be used when the function
184+
/// body is not required for further optimization or inlining (-Onone).
185+
SILFunction *lookupPrespecializedSymbol(SILModule &M, StringRef FunctionName);
139186

140187
} // end namespace swift
141188

include/swift/Serialization/ModuleFormat.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const uint16_t VERSION_MAJOR = 0;
5353
/// in source control, you should also update the comment to briefly
5454
/// describe what change you made. The content of this comment isn't important;
5555
/// it just ensures a conflict if two people change the module format.
56-
const uint16_t VERSION_MINOR = 245; // Last change: re-number SIL stuff
56+
const uint16_t VERSION_MINOR = 246; // Last change: @_specialize attribute
5757

5858
using DeclID = PointerEmbeddedInt<unsigned, 31>;
5959
using DeclIDField = BCFixed<31>;
@@ -1379,6 +1379,11 @@ namespace decls_block {
13791379
// strings, separated by the prior index
13801380
>;
13811381

1382+
using SpecializeDeclAttrLayout = BCRecordLayout<
1383+
Specialize_DECL_ATTR,
1384+
BCArray<TypeIDField> // concrete types
1385+
>;
1386+
13821387
#define SIMPLE_DECL_ATTR(X, CLASS, ...) \
13831388
using CLASS##DeclAttrLayout = BCRecordLayout< \
13841389
CLASS##_DECL_ATTR, \

0 commit comments

Comments
 (0)