Skip to content

Commit 8c324df

Browse files
gottesmmnvzqz
authored andcommitted
[builtin] Implement polymorphic builtins for all BUILTIN_BINARY_OPERATIONs.
TLDR: This patch introduces a new kind of builtin, "a polymorphic builtin". One calls it like any other builtin, e.x.: ``` Builtin.generic_add(x, y) ``` but it has a contract: eventually At constant propagation time, the optimizer attempts to specialize the generic_add to the conc DISCUSSION ---------- Today there are polymorphic like instructions in LLVM-IR. Yet, at the swift and SIL level we represent these operations instead as Builtins whose names are resolved by splatting the builtin into the name. For example, adding two things in LLVM: ``` %2 = add i64 %0, %1 %2 = add <2 x i64> %0, %1 %2 = add <4 x i64> %0, %1 %2 = add <8 x i64> %0, %1 ``` Each of the add operations are done by the same polymorphic instruction. In constrast, we splat out these Builtins in swift today, i.e.: ``` let x, y: Builtin.Int32 Builtin.add_Int32(x, y) let x, y: Builtin.Vec2xInt32 Builtin.add_Vec2xInt32(x, y) ... ``` In SIL, we translate these verbatim and then IRGen just lowers them to the appropriate polymorphic instruction. Beyond being verbose, these prevent these Builtins (which need static types) from being used in polymorphic contexts. These operations in Swift look like: Builtin.add_Vec2
1 parent bd222ad commit 8c324df

19 files changed

+1184
-57
lines changed

include/swift/AST/Builtins.def

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,30 +51,97 @@ BUILTIN_CAST_OR_BITCAST_OPERATION(SExtOrBitCast, "sextOrBitCast", "n")
5151
#undef BUILTIN_CAST_OR_BITCAST_OPERATION
5252

5353
/// Binary operations have type (T,T) -> T.
54+
///
55+
/// We define two different sorts of operations varying when T is static,
56+
/// specifically:
57+
///
58+
/// 1. Overloaded statically typed operations. E.x:
59+
///
60+
/// builtin "add_Vec4xInt32"(Vec4xInt32, Vec4xInt32) : Vec4xInt32.
61+
///
62+
/// 2. Polymorphic typed operations that are valid only in raw SIL. By the time
63+
/// diagnostic constant propagation runs, these must have as its operand a
64+
/// fully specialized type. If the builtin has a type that is not one of its
65+
/// overloaded types, diagnostic constant propagation will emit a diagnostic
66+
/// saying the builtin's type has not been fully resolved. Otherwise,
67+
/// diagnostic constant propagation will transform the builtin to the
68+
/// relevant static overloaded builtin form. E.x.:
69+
///
70+
/// builtin "add"(Self, Self) : Self // *error*
71+
///
72+
/// OR
73+
///
74+
/// builtin "generic_add"(Vec4xInt32, Vec4xInt32) : Vec4xInt32
75+
/// ->
76+
/// builtin "add_Vec4xInt32"(Vec4xInt32, Vec4xInt32) : Vec4xInt32
77+
///
78+
/// NOTE: If a polymorphic typed operation is not static by the time guaranteed
79+
/// constant propagation runs, we emit a diagnostic to inform the user (who is
80+
/// assumed to be an expert user) to tell them the value was unspecialized. The
81+
/// typical way this specialization occurs today is via transparent inlining
82+
/// since the transparent inliner devirtualizes and specializes as it goes. Of
83+
/// course this means mandatory inlining must /always/ occur before diagnostic
84+
/// constant propagation.
85+
///
86+
/// NOTE: Often times the builtin infrastructure wants to treat all
87+
/// binary operation builtins generic or not the same way. To ensure
88+
/// we support all use cases in the compiler, we do not declare the
89+
/// operations as part of this builtin since often times this macro is
90+
/// used to generic code. Instead, we stamp this out using the
91+
/// overloaded_static, polymorphic, and all suffixed operations.
5492
#ifndef BUILTIN_BINARY_OPERATION
55-
#define BUILTIN_BINARY_OPERATION(Id, Name, Attrs, Overload) \
56-
BUILTIN(Id, Name, Attrs)
93+
#define BUILTIN_BINARY_OPERATION(Id, Name, Attrs) BUILTIN(Id, Name, Attrs)
5794
#endif
58-
BUILTIN_BINARY_OPERATION(Add, "add", "n", IntegerOrVector)
59-
BUILTIN_BINARY_OPERATION(FAdd, "fadd", "n", FloatOrVector)
60-
BUILTIN_BINARY_OPERATION(And, "and", "n", IntegerOrVector)
61-
BUILTIN_BINARY_OPERATION(AShr, "ashr", "n", IntegerOrVector)
62-
BUILTIN_BINARY_OPERATION(LShr, "lshr", "n", IntegerOrVector)
63-
BUILTIN_BINARY_OPERATION(Or, "or", "n", IntegerOrVector)
64-
BUILTIN_BINARY_OPERATION(FDiv, "fdiv", "n", FloatOrVector)
65-
BUILTIN_BINARY_OPERATION(Mul, "mul", "n", IntegerOrVector)
66-
BUILTIN_BINARY_OPERATION(FMul, "fmul", "n", FloatOrVector)
67-
BUILTIN_BINARY_OPERATION(SDiv, "sdiv", "n", IntegerOrVector)
68-
BUILTIN_BINARY_OPERATION(ExactSDiv, "sdiv_exact", "n", IntegerOrVector)
69-
BUILTIN_BINARY_OPERATION(Shl, "shl", "n", IntegerOrVector)
70-
BUILTIN_BINARY_OPERATION(SRem, "srem", "n", IntegerOrVector)
71-
BUILTIN_BINARY_OPERATION(Sub, "sub", "n", IntegerOrVector)
72-
BUILTIN_BINARY_OPERATION(FSub, "fsub", "n", FloatOrVector)
73-
BUILTIN_BINARY_OPERATION(UDiv, "udiv", "n", IntegerOrVector)
74-
BUILTIN_BINARY_OPERATION(ExactUDiv, "udiv_exact", "n", IntegerOrVector)
75-
BUILTIN_BINARY_OPERATION(URem, "urem", "n", Integer)
76-
BUILTIN_BINARY_OPERATION(FRem, "frem", "n", FloatOrVector)
77-
BUILTIN_BINARY_OPERATION(Xor, "xor", "n", IntegerOrVector)
95+
96+
#ifdef BUILTIN_BINARY_OPERATION_GENERIC_HELPER_STR
97+
#error "Do not define BUILTIN_BINARY_OPERATION_GENERIC_HELPER_STR before including this .def file"
98+
#endif
99+
100+
#define BUILTIN_BINARY_OPERATION_GENERIC_HELPER_STR(NAME) #NAME
101+
102+
#ifndef BUILTIN_BINARY_OPERATION_OVERLOADED_STATIC
103+
#define BUILTIN_BINARY_OPERATION_OVERLOADED_STATIC(Id, Name, Attrs, Overload) \
104+
BUILTIN_BINARY_OPERATION(Id, Name, Attrs)
105+
#endif
106+
107+
#ifndef BUILTIN_BINARY_OPERATION_POLYMORPHIC
108+
#define BUILTIN_BINARY_OPERATION_POLYMORPHIC(Id, Name, Attrs) \
109+
BUILTIN_BINARY_OPERATION(Id, Name, Attrs)
110+
#endif
111+
112+
// TODO: This needs a better name. We stringify generic_ in *_{OVERLOADED_STATIC,POLYMORPHIC}
113+
#ifndef BUILTIN_BINARY_OPERATION_ALL
114+
#define BUILTIN_BINARY_OPERATION_ALL(Id, Name, Attrs, Overload) \
115+
BUILTIN_BINARY_OPERATION_OVERLOADED_STATIC(Id, BUILTIN_BINARY_OPERATION_GENERIC_HELPER_STR(Name), Attrs, Overload) \
116+
BUILTIN_BINARY_OPERATION_POLYMORPHIC(Generic##Id, BUILTIN_BINARY_OPERATION_GENERIC_HELPER_STR(generic_##Name), Attrs)
117+
#endif
118+
119+
// NOTE: Here we need our name field to be bare. We stringify them as
120+
// appropriately in BUILTIN_BINARY_OPERATION_{OVERLOADED_STATIC,POLYMORPHIC}.
121+
BUILTIN_BINARY_OPERATION_ALL(Add, add, "n", IntegerOrVector)
122+
BUILTIN_BINARY_OPERATION_ALL(FAdd, fadd, "n", FloatOrVector)
123+
BUILTIN_BINARY_OPERATION_ALL(And, and, "n", IntegerOrVector)
124+
BUILTIN_BINARY_OPERATION_ALL(AShr, ashr, "n", IntegerOrVector)
125+
BUILTIN_BINARY_OPERATION_ALL(LShr, lshr, "n", IntegerOrVector)
126+
BUILTIN_BINARY_OPERATION_ALL(Or, or, "n", IntegerOrVector)
127+
BUILTIN_BINARY_OPERATION_ALL(FDiv, fdiv, "n", FloatOrVector)
128+
BUILTIN_BINARY_OPERATION_ALL(Mul, mul, "n", IntegerOrVector)
129+
BUILTIN_BINARY_OPERATION_ALL(FMul, fmul, "n", FloatOrVector)
130+
BUILTIN_BINARY_OPERATION_ALL(SDiv, sdiv, "n", IntegerOrVector)
131+
BUILTIN_BINARY_OPERATION_ALL(ExactSDiv, sdiv_exact, "n", IntegerOrVector)
132+
BUILTIN_BINARY_OPERATION_ALL(Shl, shl, "n", IntegerOrVector)
133+
BUILTIN_BINARY_OPERATION_ALL(SRem, srem, "n", IntegerOrVector)
134+
BUILTIN_BINARY_OPERATION_ALL(Sub, sub, "n", IntegerOrVector)
135+
BUILTIN_BINARY_OPERATION_ALL(FSub, fsub, "n", FloatOrVector)
136+
BUILTIN_BINARY_OPERATION_ALL(UDiv, udiv, "n", IntegerOrVector)
137+
BUILTIN_BINARY_OPERATION_ALL(ExactUDiv, udiv_exact, "n", IntegerOrVector)
138+
BUILTIN_BINARY_OPERATION_ALL(URem, urem, "n", Integer)
139+
BUILTIN_BINARY_OPERATION_ALL(FRem, frem, "n", FloatOrVector)
140+
BUILTIN_BINARY_OPERATION_ALL(Xor, xor, "n", IntegerOrVector)
141+
#undef BUILTIN_BINARY_OPERATION_ALL
142+
#undef BUILTIN_BINARY_OPERATION_POLYMORPHIC
143+
#undef BUILTIN_BINARY_OPERATION_OVERLOADED_STATIC
144+
#undef BUILTIN_BINARY_OPERATION_GENERIC_HELPER_STR
78145
#undef BUILTIN_BINARY_OPERATION
79146

80147
/// These builtins are analogous the similarly named llvm intrinsics. The

include/swift/AST/Builtins.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ enum class BuiltinValueKind {
8383
#include "swift/AST/Builtins.def"
8484
};
8585

86+
/// Returns true if this is a polymorphic builtin that is only valid
87+
/// in raw sil and thus must be resolved to have concrete types by the
88+
/// time we are in canonical SIL.
89+
bool isPolymorphicBuiltin(BuiltinValueKind Id);
90+
8691
/// Decode the type list of a builtin (e.g. mul_Int32) and return the base
8792
/// name (e.g. "mul").
8893
StringRef getBuiltinBaseName(ASTContext &C, StringRef Name,

include/swift/Basic/STLExtras.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,12 @@ using all_true =
874874
template <class... Ts>
875875
using are_all_compound = all_true<std::is_compound<Ts>::value...>;
876876

877+
/// Overload for producing optionals of type T where c++ type inference does not
878+
/// let you consider optional a subtype of T.
879+
template <typename T> Optional<T> make_optional(const T &t) {
880+
return Optional<T>(t);
881+
}
882+
877883
} // end namespace swift
878884

879885
#endif // SWIFT_BASIC_INTERLEAVE_H

include/swift/SIL/InstructionUtils.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,38 @@ bool onlyUsedByAssignByWrapper(PartialApplyInst *PAI);
142142
void findClosuresForFunctionValue(SILValue V,
143143
TinyPtrVector<PartialApplyInst *> &results);
144144

145+
/// Given a polymorphic builtin \p bi that may be generic and thus have in/out
146+
/// params, stash all of the information needed for either specializing while
147+
/// inlining or propagating the type in constant propagation.
148+
///
149+
/// NOTE: If we perform this transformation, our builtin will no longer have any
150+
/// substitutions since we only substitute to concrete static overloads.
151+
struct PolymorphicBuiltinSpecializedOverloadInfo {
152+
Identifier staticOverloadIdentifier;
153+
SmallVector<SILType, 8> argTypes;
154+
SILType resultType;
155+
bool hasOutParam = false;
156+
157+
#ifndef NDEBUG
158+
private:
159+
bool isInitialized = false;
160+
#endif
161+
162+
public:
163+
PolymorphicBuiltinSpecializedOverloadInfo() = default;
164+
165+
bool init(SILModule &mod, BuiltinValueKind builtinKind,
166+
ArrayRef<SILType> oldOperandTypes, SILType oldResultType);
167+
168+
bool init(BuiltinInst *bi);
169+
};
170+
171+
/// Given a polymorphic builtin \p bi, analyze its types and create a builtin
172+
/// for the static overload that the builtin corresponds to. If \p bi is not a
173+
/// polymorphic builtin or does not have any available overload for these types,
174+
/// return SILValue().
175+
SILValue getStaticOverloadForSpecializedPolymorphicBuiltin(BuiltinInst *bi);
176+
145177
} // end namespace swift
146178

147179
#endif

include/swift/SIL/PatternMatch.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ using BuiltinApplyTy = typename Apply_match<BuiltinValueKind, Tys...>::Ty;
649649
#define BUILTIN_CAST_OR_BITCAST_OPERATION(Id, Name, Attrs) \
650650
BUILTIN_UNARY_OP_MATCH_WITH_ARG_MATCHER(Id, Id)
651651

652-
#define BUILTIN_BINARY_OPERATION(Id, Name, Attrs, Overload) \
652+
#define BUILTIN_BINARY_OPERATION(Id, Name, Attrs) \
653653
BUILTIN_BINARY_OP_MATCH_WITH_ARG_MATCHER(Id, Id)
654654

655655
#define BUILTIN_BINARY_PREDICATE(Id, Name, Attrs, Overload) \

include/swift/SIL/SILInstruction.h

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,16 @@ class SILInstruction
523523
getAllOperands()[Num1].swap(getAllOperands()[Num2]);
524524
}
525525

526+
private:
527+
/// Predicate used to filter OperandTypeRange.
528+
struct OperandToType;
529+
530+
public:
531+
using OperandTypeRange =
532+
OptionalTransformRange<ArrayRef<Operand>, OperandToType>;
533+
// NOTE: We always skip type dependent operands.
534+
OperandTypeRange getOperandTypes() const;
535+
526536
/// Return the list of results produced by this instruction.
527537
bool hasResults() const { return !getResults().empty(); }
528538
SILInstructionResultArray getResults() const { return getResultsImpl(); }
@@ -700,6 +710,22 @@ SILInstruction::getOperandValues(bool skipTypeDependentOperands) const
700710
OperandToValue(*this, skipTypeDependentOperands));
701711
}
702712

713+
struct SILInstruction::OperandToType {
714+
const SILInstruction &i;
715+
716+
OperandToType(const SILInstruction &i) : i(i) {}
717+
718+
Optional<SILType> operator()(const Operand &use) const {
719+
if (i.isTypeDependentOperand(use))
720+
return None;
721+
return use.get()->getType();
722+
}
723+
};
724+
725+
inline auto SILInstruction::getOperandTypes() const -> OperandTypeRange {
726+
return OperandTypeRange(getAllOperands(), OperandToType(*this));
727+
}
728+
703729
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
704730
const SILInstruction &I) {
705731
I.print(OS);
@@ -3009,7 +3035,7 @@ class BuiltinInst final
30093035

30103036
/// Looks up the BuiltinKind of this builtin. Returns None if this is
30113037
/// not a builtin.
3012-
llvm::Optional<BuiltinValueKind> getBuiltinKind() const {
3038+
Optional<BuiltinValueKind> getBuiltinKind() const {
30133039
auto I = getBuiltinInfo();
30143040
if (I.ID == BuiltinValueKind::None)
30153041
return None;

lib/AST/Builtins.cpp

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,15 @@ static ValueDecl *getOnceOperation(ASTContext &Context,
11811181
}
11821182
}
11831183

1184+
static ValueDecl *getPolymorphicBinaryOperation(ASTContext &ctx,
1185+
Identifier id) {
1186+
BuiltinGenericSignatureBuilder builder(ctx);
1187+
builder.addParameter(makeGenericParam());
1188+
builder.addParameter(makeGenericParam());
1189+
builder.setResult(makeGenericParam());
1190+
return builder.build(id);
1191+
}
1192+
11841193
/// An array of the overloaded builtin kinds.
11851194
static const OverloadedBuiltinKind OverloadedBuiltinKinds[] = {
11861195
OverloadedBuiltinKind::None,
@@ -1191,8 +1200,10 @@ static const OverloadedBuiltinKind OverloadedBuiltinKinds[] = {
11911200
OverloadedBuiltinKind::Special,
11921201
#define BUILTIN_CAST_OR_BITCAST_OPERATION(id, attrs, name) \
11931202
OverloadedBuiltinKind::Special,
1194-
#define BUILTIN_BINARY_OPERATION(id, name, attrs, overload) \
1195-
OverloadedBuiltinKind::overload,
1203+
#define BUILTIN_BINARY_OPERATION_OVERLOADED_STATIC(id, name, attrs, overload) \
1204+
OverloadedBuiltinKind::overload,
1205+
#define BUILTIN_BINARY_OPERATION_POLYMORPHIC(id, name, attrs) \
1206+
OverloadedBuiltinKind::Special,
11961207
#define BUILTIN_BINARY_OPERATION_WITH_OVERFLOW(id, name, _, attrs, overload) \
11971208
OverloadedBuiltinKind::overload,
11981209
#define BUILTIN_BINARY_PREDICATE(id, name, attrs, overload) \
@@ -1777,11 +1788,22 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
17771788
return getEndUnpairedAccessOperation(Context, Id);
17781789

17791790
#define BUILTIN(id, name, Attrs)
1780-
#define BUILTIN_BINARY_OPERATION(id, name, attrs, overload) case BuiltinValueKind::id:
1791+
#define BUILTIN_BINARY_OPERATION(id, name, attrs)
1792+
#define BUILTIN_BINARY_OPERATION_OVERLOADED_STATIC(id, name, attrs, overload) \
1793+
case BuiltinValueKind::id:
17811794
#include "swift/AST/Builtins.def"
17821795
if (Types.size() != 1) return nullptr;
17831796
return getBinaryOperation(Id, Types[0]);
17841797

1798+
#define BUILTIN(id, name, attrs)
1799+
#define BUILTIN_BINARY_OPERATION(id, name, attrs)
1800+
#define BUILTIN_BINARY_OPERATION_POLYMORPHIC(id, name, attrs) \
1801+
case BuiltinValueKind::id:
1802+
#include "swift/AST/Builtins.def"
1803+
if (!Types.empty())
1804+
return nullptr;
1805+
return getPolymorphicBinaryOperation(Context, Id);
1806+
17851807
#define BUILTIN(id, name, Attrs)
17861808
#define BUILTIN_BINARY_OPERATION_WITH_OVERFLOW(id, name, _, attrs, overload) case BuiltinValueKind::id:
17871809
#include "swift/AST/Builtins.def"
@@ -2034,6 +2056,21 @@ StringRef swift::getBuiltinName(BuiltinValueKind ID) {
20342056
llvm_unreachable("bad BuiltinValueKind");
20352057
}
20362058

2059+
bool swift::isPolymorphicBuiltin(BuiltinValueKind id) {
2060+
switch (id) {
2061+
case BuiltinValueKind::None:
2062+
llvm_unreachable("no builtin kind");
2063+
#define BUILTIN(Id, Name, Attrs) \
2064+
case BuiltinValueKind::Id: \
2065+
return false;
2066+
#define BUILTIN_BINARY_OPERATION_POLYMORPHIC(Id, Name, Attrs) \
2067+
case BuiltinValueKind::Id: \
2068+
return true;
2069+
#include "swift/AST/Builtins.def"
2070+
}
2071+
llvm_unreachable("bad BuiltinValueKind");
2072+
}
2073+
20372074
BuiltinTypeKind BuiltinType::getBuiltinTypeKind() const {
20382075
// If we do not have a vector or an integer our job is easy.
20392076
return BuiltinTypeKind(std::underlying_type<TypeKind>::type(getKind()));

lib/IRGen/GenBuiltin.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,18 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
305305
if (Builtin.ID == BuiltinValueKind::id) \
306306
return emitCastOrBitCastBuiltin(IGF, resultType, out, args, \
307307
BuiltinValueKind::id);
308-
309-
#define BUILTIN_BINARY_OPERATION(id, name, attrs, overload) \
310-
if (Builtin.ID == BuiltinValueKind::id) { \
311-
llvm::Value *lhs = args.claimNext(); \
312-
llvm::Value *rhs = args.claimNext(); \
313-
llvm::Value *v = IGF.Builder.Create##id(lhs, rhs); \
314-
return out.add(v); \
308+
309+
#define BUILTIN_BINARY_OPERATION_OVERLOADED_STATIC(id, name, attrs, overload) \
310+
if (Builtin.ID == BuiltinValueKind::id) { \
311+
llvm::Value *lhs = args.claimNext(); \
312+
llvm::Value *rhs = args.claimNext(); \
313+
llvm::Value *v = IGF.Builder.Create##id(lhs, rhs); \
314+
return out.add(v); \
315315
}
316+
#define BUILTIN_BINARY_OPERATION_POLYMORPHIC(id, name, attrs) \
317+
assert(Builtin.ID != BuiltinValueKind::id && \
318+
"This builtin should never be seen by IRGen. It should have been " \
319+
"lowered by IRGenPrepare");
316320

317321
#define BUILTIN_RUNTIME_CALL(id, name, attrs) \
318322
if (Builtin.ID == BuiltinValueKind::id) { \

0 commit comments

Comments
 (0)