Skip to content

Commit f365316

Browse files
authored
Merge pull request #70327 from Azoy/inject-get-enum-tag-builtins
[IRGen] Add getEnumTag and injectEnumTag builtins
2 parents a56c89f + 0ed4a19 commit f365316

File tree

12 files changed

+166
-0
lines changed

12 files changed

+166
-0
lines changed

include/swift/AST/Builtins.def

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,21 @@ BUILTIN_MISC_OPERATION_WITH_SILGEN(BuildDefaultActorExecutorRef,
10481048
BUILTIN_MISC_OPERATION_WITH_SILGEN(BuildMainActorExecutorRef,
10491049
"buildMainActorExecutorRef", "n", Special)
10501050

1051+
/// getEnumTag: <T>(_: T) -> Builtin.Int32
1052+
///
1053+
/// Given a dynamic generic value, unsafely assume it is an enum type and call
1054+
/// the getEnumTag vwt function.
1055+
BUILTIN_MISC_OPERATION_WITH_SILGEN(GetEnumTag, "getEnumTag", "", Special)
1056+
1057+
/// injectEnumTag: <T>(_: inout T, _: Builtin.Int32) -> ()
1058+
///
1059+
/// Given a dynamic inout generic value, unsafely assume it is an enum type and
1060+
/// inject the given tag into it.
1061+
///
1062+
/// Note: This assume that either 1. the given tag has no payload or 2. the
1063+
/// tag's payload is already initialized with the given source.
1064+
BUILTIN_MISC_OPERATION_WITH_SILGEN(InjectEnumTag, "injectEnumTag", "", Special)
1065+
10511066
#undef BUILTIN_MISC_OPERATION_WITH_SILGEN
10521067

10531068
#undef BUILTIN_MISC_OPERATION

include/swift/SIL/AddressWalker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ TransitiveAddressWalker<Impl>::walk(SILValue projectedAddress) && {
259259
case BuiltinValueKind::GenericXor:
260260
case BuiltinValueKind::TaskRunInline:
261261
case BuiltinValueKind::ZeroInitializer:
262+
case BuiltinValueKind::GetEnumTag:
263+
case BuiltinValueKind::InjectEnumTag:
262264
callVisitUse(op);
263265
continue;
264266
default:

lib/AST/Builtins.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,6 +1978,26 @@ static ValueDecl *getPackLength(ASTContext &ctx, Identifier id) {
19781978
return builder.build(id);
19791979
}
19801980

1981+
static ValueDecl *getGetEnumTag(ASTContext &ctx, Identifier id) {
1982+
BuiltinFunctionBuilder builder(ctx, /* genericParamCount */ 1);
1983+
1984+
auto paramTy = makeGenericParam();
1985+
builder.addParameter(paramTy);
1986+
builder.setResult(makeConcrete(BuiltinIntegerType::get(32, ctx)));
1987+
1988+
return builder.build(id);
1989+
}
1990+
1991+
static ValueDecl *getInjectEnumTag(ASTContext &ctx, Identifier id) {
1992+
BuiltinFunctionBuilder builder(ctx, /* genericParamCount */ 1);
1993+
1994+
builder.addParameter(makeGenericParam(), ParamSpecifier::InOut);
1995+
builder.addParameter(makeConcrete(BuiltinIntegerType::get(32, ctx)));
1996+
builder.setResult(makeConcrete(TupleType::getEmpty(ctx)));
1997+
1998+
return builder.build(id);
1999+
}
2000+
19812001
/// An array of the overloaded builtin kinds.
19822002
static const OverloadedBuiltinKind OverloadedBuiltinKinds[] = {
19832003
OverloadedBuiltinKind::None,
@@ -3019,6 +3039,12 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
30193039

30203040
case BuiltinValueKind::PackLength:
30213041
return getPackLength(Context, Id);
3042+
3043+
case BuiltinValueKind::GetEnumTag:
3044+
return getGetEnumTag(Context, Id);
3045+
3046+
case BuiltinValueKind::InjectEnumTag:
3047+
return getInjectEnumTag(Context, Id);
30223048
}
30233049

30243050
llvm_unreachable("bad builtin value!");

lib/IRGen/GenBuiltin.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
#include "GenCast.h"
3232
#include "GenConcurrency.h"
3333
#include "GenDistributed.h"
34+
#include "GenEnum.h"
3435
#include "GenPointerAuth.h"
3536
#include "GenIntegerLiteral.h"
37+
#include "GenOpaque.h"
3638
#include "IRGenFunction.h"
3739
#include "IRGenModule.h"
3840
#include "LoadableTypeInfo.h"
@@ -1503,6 +1505,7 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
15031505
out.add(pointerSrc);
15041506
return;
15051507
}
1508+
15061509
if (Builtin.ID == BuiltinValueKind::AllocVector) {
15071510
(void)args.claimAll();
15081511
IGF.emitTrap("escaped vector allocation", /*EmitUnreachable=*/true);
@@ -1511,5 +1514,47 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
15111514
IGF.Builder.emitBlock(contBB);
15121515
return;
15131516
}
1517+
1518+
if (Builtin.ID == BuiltinValueKind::GetEnumTag) {
1519+
auto arg = args.claimNext();
1520+
auto ty = argTypes[0];
1521+
auto &ti = IGF.getTypeInfo(ty);
1522+
1523+
// If the type is just an archetype, then we know nothing about the enum
1524+
// strategy for it. Just call the vwt function. Otherwise, we know that this
1525+
// is at least an enum and can optimize away some of the cost of getEnumTag.
1526+
if (!ty.is<ArchetypeType>()) {
1527+
assert(ty.getEnumOrBoundGenericEnum() && "expected enum type in "
1528+
"getEnumTag builtin!");
1529+
1530+
auto &strategy = getEnumImplStrategy(IGF.IGM, ty);
1531+
1532+
out.add(strategy.emitGetEnumTag(IGF, ty, ti.getAddressForPointer(arg)));
1533+
return;
1534+
}
1535+
1536+
out.add(emitGetEnumTagCall(IGF, ty, ti.getAddressForPointer(arg)));
1537+
return;
1538+
}
1539+
1540+
if (Builtin.ID == BuiltinValueKind::InjectEnumTag) {
1541+
auto input = args.claimNext();
1542+
auto tag = args.claimNext();
1543+
auto inputTy = argTypes[0];
1544+
auto &inputTi = IGF.getTypeInfo(inputTy);
1545+
1546+
// In order for us to call 'storeTag' on an enum strategy (when type is not
1547+
// an archetype), we'd need to be able to map the tag back into an
1548+
// EnumElementDecl which might be fragile. We don't really care about being
1549+
// able to optimize this vwt function call anyway because we expect most
1550+
// use cases to be the truly dynamic case where the compiler has no static
1551+
// information about the type to be able to optimize it away. Just call the
1552+
// vwt function.
1553+
1554+
emitDestructiveInjectEnumTagCall(IGF, inputTy, tag,
1555+
inputTi.getAddressForPointer(input));
1556+
return;
1557+
}
1558+
15141559
llvm_unreachable("IRGen unimplemented for this builtin!");
15151560
}

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,8 @@ BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, PoundAssert)
879879
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, GlobalStringTablePointer)
880880
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, TypePtrAuthDiscriminator)
881881
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, TargetOSVersionAtLeast)
882+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, GetEnumTag)
883+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, InjectEnumTag)
882884
OperandOwnership OperandOwnershipBuiltinClassifier::visitCopy(BuiltinInst *bi,
883885
StringRef) {
884886
if (bi->getFunction()->getConventions().useLoweredAddresses()) {

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,8 @@ CONSTANT_OWNERSHIP_BUILTIN(None, CreateTaskGroupWithFlags)
618618
CONSTANT_OWNERSHIP_BUILTIN(None, DestroyTaskGroup)
619619
CONSTANT_OWNERSHIP_BUILTIN(None, TaskRunInline)
620620
CONSTANT_OWNERSHIP_BUILTIN(None, Copy)
621+
CONSTANT_OWNERSHIP_BUILTIN(None, GetEnumTag)
622+
CONSTANT_OWNERSHIP_BUILTIN(None, InjectEnumTag)
621623

622624
#undef CONSTANT_OWNERSHIP_BUILTIN
623625

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2647,6 +2647,12 @@ static void visitBuiltinAddress(BuiltinInst *builtin,
26472647
}
26482648
return;
26492649

2650+
// These builtins take a generic 'T' as their operand.
2651+
case BuiltinValueKind::GetEnumTag:
2652+
case BuiltinValueKind::InjectEnumTag:
2653+
visitor(&builtin->getAllOperands()[0]);
2654+
return;
2655+
26502656
// Arrays: (T.Type, Builtin.RawPointer, Builtin.RawPointer,
26512657
// Builtin.Word)
26522658
case BuiltinValueKind::CopyArray:

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,11 @@ struct ImmutableAddressUseVerifier {
559559
if (isPolymorphicBuiltin(*builtinKind)) {
560560
break;
561561
}
562+
563+
// Get enum tag borrows its operand address value.
564+
if (builtinKind == BuiltinValueKind::GetEnumTag) {
565+
return false;
566+
}
562567
}
563568

564569
// Otherwise this is a builtin that we are not expecting to see, so bail

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,34 @@ static ManagedValue emitBuiltinBuildMainActorExecutorRef(
18171817
BuiltinValueKind::BuildMainActorExecutorRef);
18181818
}
18191819

1820+
static ManagedValue emitBuiltinGetEnumTag(SILGenFunction &SGF, SILLocation loc,
1821+
SubstitutionMap subs,
1822+
ArrayRef<ManagedValue> args,
1823+
SGFContext C) {
1824+
auto &ctx = SGF.getASTContext();
1825+
1826+
auto bi = SGF.B.createBuiltin(
1827+
loc, ctx.getIdentifier(getBuiltinName(BuiltinValueKind::GetEnumTag)),
1828+
SILType::getBuiltinIntegerType(32, ctx), subs,
1829+
{ args[0].getValue() });
1830+
1831+
return ManagedValue::forObjectRValueWithoutOwnership(bi);
1832+
}
1833+
1834+
static ManagedValue emitBuiltinInjectEnumTag(SILGenFunction &SGF, SILLocation loc,
1835+
SubstitutionMap subs,
1836+
ArrayRef<ManagedValue> args,
1837+
SGFContext C) {
1838+
auto &ctx = SGF.getASTContext();
1839+
1840+
auto bi = SGF.B.createBuiltin(
1841+
loc, ctx.getIdentifier(getBuiltinName(BuiltinValueKind::InjectEnumTag)),
1842+
SILType::getEmptyTupleType(ctx), subs,
1843+
{ args[0].getValue(), args[1].getValue() });
1844+
1845+
return ManagedValue::forObjectRValueWithoutOwnership(bi);
1846+
}
1847+
18201848
llvm::Optional<SpecializedEmitter>
18211849
SpecializedEmitter::forDecl(SILGenModule &SGM, SILDeclRef function) {
18221850
// Only consider standalone declarations in the Builtin module.

lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ static bool isBarrier(SILInstruction *inst) {
157157
case BuiltinValueKind::StackDealloc:
158158
case BuiltinValueKind::AllocVector:
159159
case BuiltinValueKind::AssumeAlignment:
160+
case BuiltinValueKind::GetEnumTag:
161+
case BuiltinValueKind::InjectEnumTag:
160162
return false;
161163

162164
// Handle some rare builtins that may be sensitive to object lifetime

test/IRGen/builtins.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,5 +827,19 @@ func convertTaskToJob(_ task: Builtin.NativeObject) -> Builtin.Job {
827827
return Builtin.convertTaskToJob(task)
828828
}
829829

830+
// CHECK-LABEL: define {{.*}} swiftcc i32 @"$s8builtins10getEnumTagys6UInt32VxlF"(ptr {{.*}} %0, ptr %T)
831+
// CHECK: %GetEnumTag = load ptr, ptr {{%.*}}
832+
// CHECK: [[TAG:%.*]] = call i32 %GetEnumTag(ptr {{.*}} %0, ptr %T)
833+
// CHECK: ret i32 [[TAG]]
834+
func getEnumTag<T>(_ x: T) -> UInt32 {
835+
UInt32(Builtin.getEnumTag(x))
836+
}
837+
838+
// CHECK-LABEL: define {{.*}} swiftcc void @"$s8builtins13injectEnumTag_3tagyxz_s6UInt32VtlF"(ptr %0, i32 %1, ptr %T)
839+
// CHECK: %DestructiveInjectEnumTag = load ptr, ptr {{%.*}}
840+
// CHECK: call void %DestructiveInjectEnumTag(ptr {{.*}} %0, i32 %1, ptr %T)
841+
func injectEnumTag<T>(_ x: inout T, tag: UInt32) {
842+
Builtin.injectEnumTag(&x, tag._value)
843+
}
830844

831845
// CHECK: ![[R]] = !{i64 0, i64 9223372036854775807}

test/SILGen/builtins.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,3 +882,22 @@ func assumeAlignment(_ p: Builtin.RawPointer, _ x: Builtin.Word) {
882882
func packCount<each T>(_ x: repeat each T) -> Builtin.Word {
883883
Builtin.packLength((repeat each T).self)
884884
}
885+
886+
// CHECK-LABEL: sil hidden [ossa] @$s8builtins10getEnumTagyBi32_xlF : $@convention(thin) <T> (@in_guaranteed T) -> Builtin.Int32 {
887+
// CHECK: bb0([[INPUT:%.*]] : $*T):
888+
// CHECK-NOT: copy_addr
889+
// CHECK: [[TAG:%.*]] = builtin "getEnumTag"<T>([[INPUT]] : $*T)
890+
// CHECK: return [[TAG]] : $Builtin.Int32
891+
func getEnumTag<T>(_ x: T) -> Builtin.Int32 {
892+
Builtin.getEnumTag(x)
893+
}
894+
895+
// CHECK-LABEL: sil hidden [ossa] @$s8builtins13injectEnumTag_3tagyxz_Bi32_tlF : $@convention(thin) <T> (@inout T, Builtin.Int32) -> () {
896+
// CHECK: bb0([[INPUT:%.*]] : $*T, [[TAG:%.*]] : $Builtin.Int32):
897+
// CHECK-NOT: copy_addr
898+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[INPUT]] : $*T
899+
// CHECK: builtin "injectEnumTag"<T>([[ACCESS]] : $*T, [[TAG]] : $Builtin.Int32)
900+
// CHECK: end_access [[ACCESS]]
901+
func injectEnumTag<T>(_ x: inout T, tag: Builtin.Int32) {
902+
Builtin.injectEnumTag(&x, tag)
903+
}

0 commit comments

Comments
 (0)