mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[IRGen] Lowered open_pack_element.
This commit is contained in:
@@ -368,6 +368,214 @@ irgen::emitTypeMetadataPackRef(IRGenFunction &IGF, CanPackType packType,
|
||||
return response;
|
||||
}
|
||||
|
||||
llvm::Value *
|
||||
irgen::emitTypeMetadataPackElementRef(IRGenFunction &IGF, CanPackType packType,
|
||||
llvm::Value *index,
|
||||
DynamicMetadataRequest request) {
|
||||
// If the pack has already been materialized, just gep into it.
|
||||
if (auto pack = tryGetLocalPackTypeMetadata(IGF, packType, request)) {
|
||||
auto *gep = IGF.Builder.CreateInBoundsGEP(IGF.IGM.TypeMetadataPtrTy,
|
||||
pack.getMetadata(), index);
|
||||
auto addr =
|
||||
Address(gep, IGF.IGM.TypeMetadataPtrTy, IGF.IGM.getPointerAlignment());
|
||||
auto *metadata = IGF.Builder.CreateLoad(addr);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
// Otherwise, in general, there's no already available array of metadata
|
||||
// which can be indexed into.
|
||||
auto *shape = IGF.emitPackShapeExpression(packType);
|
||||
|
||||
// If the shape and the index are both constant, the type for which metadata
|
||||
// will be emitted is statically available.
|
||||
auto *constantShape = dyn_cast<llvm::ConstantInt>(shape);
|
||||
auto *constantIndex = dyn_cast<llvm::ConstantInt>(index);
|
||||
if (constantShape && constantIndex) {
|
||||
assert(packType->getNumElements() == constantShape->getValue());
|
||||
auto index = constantIndex->getValue().getZExtValue();
|
||||
assert(packType->getNumElements() > index);
|
||||
auto ty = packType.getElementType(index);
|
||||
auto response = IGF.emitTypeMetadataRef(ty, request);
|
||||
auto *metadata = response.getMetadata();
|
||||
return metadata;
|
||||
}
|
||||
|
||||
// A pack consists of types and pack expansion types. An example:
|
||||
// {repeat each T, Int, repeat each T, repeat each U, String},
|
||||
// The above type has length 5. The type "repeat each U" is at index 3.
|
||||
//
|
||||
// A pack _explosion_ is notionally obtained by flat-mapping the pack by the
|
||||
// the operation of "listing elements" in pack expansion types.
|
||||
//
|
||||
// The explosion of the example pack looks like
|
||||
// {T_0, T_1, ..., Int, T_0, T_1, ..., U_0, U_1, ..., String}
|
||||
// ^^^^^^^^^^^^^
|
||||
// the runtime components of "each T"
|
||||
//
|
||||
// We have an index into the explosion,
|
||||
//
|
||||
// {T_0, T_1, ..., Int, T_0, T_1, ..., U_0, U_1, ... String}
|
||||
// ------------%index------------>
|
||||
//
|
||||
// and we need to obtain the element in the explosion corresponding to it.
|
||||
//
|
||||
// {T_0, T_1, ..., Int, T_0, T_1, ..., T_k, ..., U_0, U_1, ... String}
|
||||
// ------------%index---------------> ^^^
|
||||
//
|
||||
// Unfortunately, the explosion has not (the first check in this function)
|
||||
// been materialized--and doing so is likely wasteful--so we can't simply
|
||||
// index into some array.
|
||||
//
|
||||
// Instead, _notionally_, we will "compute"
|
||||
// (1) the index into the _pack_ and
|
||||
// {repeat each T, Int, repeat each T, repeat each U, String}
|
||||
// ------%outer------> ^^^^^^^^^^^^^
|
||||
// (2) the index within the elements of the pack expansion type
|
||||
// {T_0, T_2, ..., T_k, ...}
|
||||
// ----%inner---> ^^^
|
||||
//
|
||||
// In fact, we won't ever materialize %outer into any register. Instead, we
|
||||
// can just brach to materializing the metadata once we've determined which
|
||||
// outer element's range contains %index.
|
||||
//
|
||||
// As for %inner, it will only be materialized in those blocks corresponding
|
||||
// to pack expansions.
|
||||
//
|
||||
// Create the following control flow:
|
||||
//
|
||||
// +-------+ t_0 is not t_N _is_ an
|
||||
// |entry: | an expansion expansion
|
||||
// |... | +----------+ +----------+ +----------+
|
||||
// |... | --> |check_0: | -> ... -> |check_N: | -> |trap: |
|
||||
// | | | %i == %u0| | %i < %uN | | llvm.trap|
|
||||
// +-------+ +----------+ +----------+ +----------+
|
||||
// %outer = 0 %outer = N
|
||||
// | |
|
||||
// V V
|
||||
// +----------+ +-----------------------+
|
||||
// |emit_1: | |emit_N: |
|
||||
// | %inner=0 | | %inner = %index - %lN |
|
||||
// | %m_1 = | | %m_N = |
|
||||
// +----------+ +-----------------------+
|
||||
// | |
|
||||
// V V
|
||||
// +-------------------------------------------
|
||||
// |exit:
|
||||
// | %m = phi [ %m_1, %emit_1 ],
|
||||
// | ...
|
||||
// | [ %m_N, %emit_N ]
|
||||
auto *current = IGF.Builder.GetInsertBlock();
|
||||
|
||||
// Terminate the block that branches to continue checking or metadata emission
|
||||
// depending on whether the index is in the pack expansion's bounds.
|
||||
auto emitCheckBranch = [&IGF](llvm::Value *condition,
|
||||
llvm::BasicBlock *inBounds,
|
||||
llvm::BasicBlock *outOfBounds) {
|
||||
if (condition) {
|
||||
IGF.Builder.CreateCondBr(condition, inBounds, outOfBounds);
|
||||
} else {
|
||||
assert(!inBounds &&
|
||||
"no condition to check but a metadata materialization block!?");
|
||||
IGF.Builder.CreateBr(outOfBounds);
|
||||
}
|
||||
};
|
||||
|
||||
// The block which emission will continue in after we finish emitting metadata
|
||||
// for this element.
|
||||
auto *exit = IGF.createBasicBlock("pack-index-element-exit");
|
||||
IGF.Builder.emitBlock(exit);
|
||||
auto *metadataPhi = IGF.Builder.CreatePHI(IGF.IGM.TypeMetadataPtrTy,
|
||||
packType.getElementTypes().size());
|
||||
|
||||
IGF.Builder.SetInsertPoint(current);
|
||||
// The previous checkBounds' block's comparision of %index. Use it to emit a
|
||||
// branch to the current block or the previous block's metadata emission
|
||||
// block.
|
||||
llvm::Value *previousCondition = nullptr;
|
||||
// The previous type's materializeMetadata block. Use it as the inBounds
|
||||
// target when branching from the previous block.
|
||||
llvm::BasicBlock *previousInBounds = nullptr;
|
||||
// The lower bound of indices for the current pack expansion. Inclusive.
|
||||
llvm::Value *lowerBound = llvm::ConstantInt::get(IGF.IGM.SizeTy, 0);
|
||||
for (auto elementTy : packType.getElementTypes()) {
|
||||
// The block within which it will be checked whether %index corresponds to
|
||||
// an element of the pack expansion elementTy.
|
||||
auto *checkBounds = IGF.createBasicBlock("pack-index-element-bounds");
|
||||
// Finish emitting the previous block, either entry or check_i-1.
|
||||
//
|
||||
// Branch from the previous bounds-check block either to this bounds-check
|
||||
// block or to the previous metadata-emission block.
|
||||
emitCheckBranch(previousCondition, previousInBounds, checkBounds);
|
||||
|
||||
// (1) Emit check_i {{
|
||||
IGF.Builder.emitBlock(checkBounds);
|
||||
|
||||
// The upper bound for the current pack expansion. Exclusive.
|
||||
llvm::Value *upperBound = nullptr;
|
||||
llvm::Value *condition = nullptr;
|
||||
if (auto expansionTy = dyn_cast<PackExpansionType>(elementTy)) {
|
||||
auto reducedShape = expansionTy.getCountType();
|
||||
auto *length = IGF.emitPackShapeExpression(reducedShape);
|
||||
upperBound = IGF.Builder.CreateAdd(lowerBound, length);
|
||||
// %index < %upperBound
|
||||
//
|
||||
// It's not necessary to check that %index >= %lowerBound. Either
|
||||
// elementTy is the first element type in packType or we branched here
|
||||
// from some series of checkBounds blocks in each of which it was
|
||||
// determined that %index is greater than the indices of the
|
||||
// corresponding element type.
|
||||
condition = IGF.Builder.CreateICmpULT(index, upperBound);
|
||||
} else {
|
||||
upperBound = IGF.Builder.CreateAdd(
|
||||
lowerBound, llvm::ConstantInt::get(IGF.IGM.SizeTy, 1));
|
||||
// %index == %lowerBound
|
||||
condition = IGF.Builder.CreateICmpEQ(lowerBound, index);
|
||||
}
|
||||
// }} Finished emitting check_i, except for the terminator which will be
|
||||
// emitted in the next iteration once the new outOfBounds block is
|
||||
// available.
|
||||
|
||||
// (2) Emit emit_i {{
|
||||
// The block within which the metadata corresponding to %inner will be
|
||||
// materialized.
|
||||
auto *materializeMetadata =
|
||||
IGF.createBasicBlock("pack-index-element-metadata");
|
||||
IGF.Builder.emitBlock(materializeMetadata);
|
||||
|
||||
llvm::Value *metadata = nullptr;
|
||||
if (auto expansionTy = dyn_cast<PackExpansionType>(elementTy)) {
|
||||
// Actually materialize %inner. Then use it to get the metadata from the
|
||||
// pack expansion at that index.
|
||||
auto *relativeIndex = IGF.Builder.CreateSub(index, lowerBound);
|
||||
metadata = emitPackExpansionElementMetadata(IGF, expansionTy,
|
||||
relativeIndex, request);
|
||||
} else {
|
||||
metadata = IGF.emitTypeMetadataRef(elementTy, request).getMetadata();
|
||||
}
|
||||
metadataPhi->addIncoming(metadata, materializeMetadata);
|
||||
IGF.Builder.CreateBr(exit);
|
||||
// }} Finished emitting emit_i.
|
||||
|
||||
// Switch back to emitting check_i. The next iteration will emit its
|
||||
// terminator.
|
||||
IGF.Builder.SetInsertPoint(checkBounds);
|
||||
|
||||
// Set up the values for the next iteration.
|
||||
previousInBounds = materializeMetadata;
|
||||
previousCondition = condition;
|
||||
lowerBound = upperBound;
|
||||
}
|
||||
auto *trap = IGF.createBasicBlock("pack-index-element-trap");
|
||||
emitCheckBranch(previousCondition, previousInBounds, trap);
|
||||
|
||||
IGF.Builder.emitBlock(trap);
|
||||
IGF.emitTrap("Variadic generic index out of bounds",
|
||||
/*EmitUnreachable=*/true);
|
||||
|
||||
IGF.Builder.SetInsertPoint(exit);
|
||||
return metadataPhi;
|
||||
}
|
||||
|
||||
void irgen::cleanupTypeMetadataPack(IRGenFunction &IGF,
|
||||
StackAddress pack,
|
||||
Optional<unsigned> elementCount) {
|
||||
|
||||
@@ -50,6 +50,11 @@ emitTypeMetadataPackRef(IRGenFunction &IGF,
|
||||
CanPackType packType,
|
||||
DynamicMetadataRequest request);
|
||||
|
||||
llvm::Value *emitTypeMetadataPackElementRef(IRGenFunction &IGF,
|
||||
CanPackType packType,
|
||||
llvm::Value *index,
|
||||
DynamicMetadataRequest request);
|
||||
|
||||
void cleanupTypeMetadataPack(IRGenFunction &IGF,
|
||||
StackAddress pack,
|
||||
Optional<unsigned> elementCount);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "swift/AST/ASTContext.h"
|
||||
#include "swift/AST/DiagnosticsIRGen.h"
|
||||
#include "swift/AST/GenericEnvironment.h"
|
||||
#include "swift/AST/IRGenOptions.h"
|
||||
#include "swift/AST/ParameterList.h"
|
||||
#include "swift/AST/Pattern.h"
|
||||
@@ -49,8 +50,8 @@
|
||||
#include "llvm/ADT/TinyPtrVector.h"
|
||||
#include "llvm/IR/Constant.h"
|
||||
#include "llvm/IR/Constants.h"
|
||||
#include "llvm/IR/DebugInfo.h"
|
||||
#include "llvm/IR/DIBuilder.h"
|
||||
#include "llvm/IR/DebugInfo.h"
|
||||
#include "llvm/IR/Function.h"
|
||||
#include "llvm/IR/InlineAsm.h"
|
||||
#include "llvm/IR/Instructions.h"
|
||||
@@ -6863,8 +6864,13 @@ void IRGenSILFunction::visitScalarPackIndexInst(ScalarPackIndexInst *i) {
|
||||
void IRGenSILFunction::visitOpenPackElementInst(swift::OpenPackElementInst *i) {
|
||||
llvm::Value *index = getLoweredSingletonExplosion(i->getIndexOperand());
|
||||
|
||||
// FIXME: bind the archetypes
|
||||
(void) index;
|
||||
i->getOpenedGenericEnvironment()->forEachPackElementBinding(
|
||||
[&](auto *archetype, auto *pack) {
|
||||
auto *metadata = emitTypeMetadataPackElementRef(
|
||||
*this, CanPackType(pack), index, MetadataState::Complete);
|
||||
this->bindArchetype(CanElementArchetypeType(archetype), metadata,
|
||||
MetadataState::Complete, {});
|
||||
});
|
||||
|
||||
// The result is just used for type dependencies.
|
||||
}
|
||||
|
||||
137
test/IRGen/run_variadic_generics.sil
Normal file
137
test/IRGen/run_variadic_generics.sil
Normal file
@@ -0,0 +1,137 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift-dylib(%t/%target-library-name(PrintShims)) %S/../Inputs/print-shims.swift -module-name PrintShims -emit-module -emit-module-path %t/PrintShims.swiftmodule
|
||||
// RUN: %target-codesign %t/%target-library-name(PrintShims)
|
||||
// RUN: %target-build-swift -enable-experimental-feature VariadicGenerics -g -parse-sil %s -emit-ir -I %t -L %t -lPrintShim | %FileCheck %s --check-prefix=CHECK-LL
|
||||
// RUN: %target-build-swift -enable-experimental-feature VariadicGenerics -g -parse-sil %s -module-name main -o %t/main -I %t -L %t -lPrintShims %target-rpath(%t)
|
||||
// RUN: %target-codesign %t/main
|
||||
// RUN: %target-run %t/main %t/%target-library-name(PrintShims) | %FileCheck %s
|
||||
|
||||
// REQUIRES: executable_test
|
||||
|
||||
// Because of -enable-experimental-feature VariadicGenerics
|
||||
// REQUIRES: asserts
|
||||
// Because generic specialization does not work yet.
|
||||
// REQUIRES: swift_test_mode_optimize_none
|
||||
|
||||
import Builtin
|
||||
import Swift
|
||||
import PrintShims
|
||||
|
||||
sil public_external @printGenericType : $@convention(thin) <T> (@thick T.Type) -> ()
|
||||
|
||||
struct A {}
|
||||
struct B {}
|
||||
struct C {}
|
||||
struct D {}
|
||||
struct E {}
|
||||
struct F {}
|
||||
struct G {}
|
||||
|
||||
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
|
||||
bb0(%argc : $Int32, %argv : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
|
||||
%0 = integer_literal $Builtin.Word, 0
|
||||
%1 = integer_literal $Builtin.Word, 1
|
||||
%2 = integer_literal $Builtin.Word, 2
|
||||
%3 = integer_literal $Builtin.Word, 3
|
||||
%4 = integer_literal $Builtin.Word, 4
|
||||
%5 = integer_literal $Builtin.Word, 5
|
||||
|
||||
%two_archetypes_from_two_params_no_singles = function_ref @two_archetypes_from_two_params_no_singles : $@convention(thin) <T_1..., T_2... where (repeat (each T_1, each T_2)): Any> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// ---0--> ^
|
||||
// CHECK: A
|
||||
// U_2 -> {D, E, F, A, B, C}
|
||||
// ---0--> ^
|
||||
// CHECK: D
|
||||
apply %two_archetypes_from_two_params_no_singles<Pack{A, B, C}, Pack{D, E, F}>(%0) : $@convention(thin) <T_1..., T_2... where (repeat (each T_1, each T_2)): Any> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// ----1----> ^
|
||||
// CHECK: B
|
||||
// U_2 -> {D, E, F, A, B, C}
|
||||
// ----1----> ^
|
||||
// CHECK: E
|
||||
apply %two_archetypes_from_two_params_no_singles<Pack{A, B, C}, Pack{D, E, F}>(%1) : $@convention(thin) <T_1..., T_2... where (repeat (each T_1, each T_2)): Any> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// ------2-----> ^
|
||||
// CHECK: C
|
||||
// U_2 -> {D, E, F, A, B, C}
|
||||
// ------2-----> ^
|
||||
// CHECK: F
|
||||
apply %two_archetypes_from_two_params_no_singles<Pack{A, B, C}, Pack{D, E, F}>(%2) : $@convention(thin) <T_1..., T_2... where (repeat (each T_1, each T_2)): Any> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// -------3-------> ^
|
||||
// CHECK: D
|
||||
// U_2 -> {D, E, F, A, B, C}
|
||||
// -------3-------> ^
|
||||
// CHECK: A
|
||||
apply %two_archetypes_from_two_params_no_singles<Pack{A, B, C}, Pack{D, E, F}>(%3) : $@convention(thin) <T_1..., T_2... where (repeat (each T_1, each T_2)): Any> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// ---------4--------> ^
|
||||
// CHECK: E
|
||||
// U_2 -> {D, E, F, A, B, C}
|
||||
// ---------4--------> ^
|
||||
// CHECK: B
|
||||
apply %two_archetypes_from_two_params_no_singles<Pack{A, B, C}, Pack{D, E, F}>(%4) : $@convention(thin) <T_1..., T_2... where (repeat (each T_1, each T_2)): Any> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// -----------5---------> ^
|
||||
// CHECK: F
|
||||
// U_2 -> {D, E, F, A, B, C}
|
||||
// -----------5---------> ^
|
||||
// CHECK: C
|
||||
apply %two_archetypes_from_two_params_no_singles<Pack{A, B, C}, Pack{D, E, F}>(%5) : $@convention(thin) <T_1..., T_2... where (repeat (each T_1, each T_2)): Any> (Builtin.Word) -> ()
|
||||
|
||||
%direct_access_from_parameter = function_ref @direct_access_from_parameter : $@convention(thin) <T_1...> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// --0---> ^
|
||||
apply %direct_access_from_parameter<Pack{A, B, C, E, F, G}>(%0) : $@convention(thin) <T_1...> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// ----1----> ^
|
||||
apply %direct_access_from_parameter<Pack{A, B, C, E, F, G}>(%1) : $@convention(thin) <T_1...> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// -----2------> ^
|
||||
apply %direct_access_from_parameter<Pack{A, B, C, E, F, G}>(%2) : $@convention(thin) <T_1...> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// -------3-------> ^
|
||||
apply %direct_access_from_parameter<Pack{A, B, C, E, F, G}>(%3) : $@convention(thin) <T_1...> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// --------4---------> ^
|
||||
apply %direct_access_from_parameter<Pack{A, B, C, E, F, G}>(%4) : $@convention(thin) <T_1...> (Builtin.Word) -> ()
|
||||
// U_1 -> {A, B, C, D, E, F}
|
||||
// ----------5----------> ^
|
||||
apply %direct_access_from_parameter<Pack{A, B, C, E, F, G}>(%5) : $@convention(thin) <T_1...> (Builtin.Word) -> ()
|
||||
|
||||
%outb = integer_literal $Builtin.Int32, 0
|
||||
%out = struct $Int32 (%outb : $Builtin.Int32)
|
||||
return %out : $Int32
|
||||
}
|
||||
|
||||
sil @two_archetypes_from_two_params_no_singles : $<T_1..., T_2... where (repeat (each T_1, each T_2)): Any> (Builtin.Word) -> () {
|
||||
entry(%intIndex : $Builtin.Word):
|
||||
%innerIndex = dynamic_pack_index %intIndex of $Pack{repeat each T_1, repeat each T_2}
|
||||
%token = open_pack_element %innerIndex of <U_1..., U_2... where (repeat (each U_1, each U_2)): Any> at <Pack{repeat each T_1, repeat each T_2}, Pack{repeat each T_2, repeat each T_1}>, shape $U_2, uuid "01234567-89AB-CDEF-0123-000000000000"
|
||||
%metatype_1 = metatype $@thick (@pack_element("01234567-89AB-CDEF-0123-000000000000") U_1).Type
|
||||
%metatype_2 = metatype $@thick (@pack_element("01234567-89AB-CDEF-0123-000000000000") U_2).Type
|
||||
%printGenericType = function_ref @printGenericType : $@convention(thin) <T> (@thick T.Type) -> ()
|
||||
// Print the first archetype that is bound.
|
||||
apply %printGenericType<(@pack_element("01234567-89AB-CDEF-0123-000000000000") U_1)>(%metatype_1) : $@convention(thin) <T> (@thick T.Type) -> ()
|
||||
// Print the second archetype that is bound.
|
||||
apply %printGenericType<(@pack_element("01234567-89AB-CDEF-0123-000000000000") U_2)>(%metatype_2) : $@convention(thin) <T> (@thick T.Type) -> ()
|
||||
%retval = tuple ()
|
||||
return %retval : $()
|
||||
}
|
||||
|
||||
// Verify that we just gep into a parameter pack when that's all that the pack consists of.
|
||||
// CHECK-LL: define {{.*}}void @direct_access_from_parameter(i64 [[INDEX:%[^,]+]], i64 {{%[^,]+}}, %swift.type** [[PACK:%[^,]+]])
|
||||
// CHECK-LL: [[ELEMENT_ADDRESS:%[^,]+]] = getelementptr inbounds %swift.type*, %swift.type** [[PACK]], i64 [[INDEX]]
|
||||
// CHECK-LL: [[ELEMENT:%[^,]+]] = load %swift.type*, %swift.type** [[ELEMENT_ADDRESS]]
|
||||
// CHECK-LL: call swiftcc void @printGenericType(%swift.type* [[ELEMENT]], %swift.type* [[ELEMENT]])
|
||||
sil @direct_access_from_parameter : $<T_1...> (Builtin.Word) -> () {
|
||||
entry(%intIndex : $Builtin.Word):
|
||||
%innerIndex = dynamic_pack_index %intIndex of $Pack{repeat each T_1}
|
||||
%token = open_pack_element %innerIndex of <U_1...> at <Pack{repeat each T_1}>, shape $U_1, uuid "01234567-89AB-CDEF-0123-000000000001"
|
||||
%metatype_1 = metatype $@thick (@pack_element("01234567-89AB-CDEF-0123-000000000001") U_1).Type
|
||||
%printGenericType = function_ref @printGenericType : $@convention(thin) <T> (@thick T.Type) -> ()
|
||||
apply %printGenericType<(@pack_element("01234567-89AB-CDEF-0123-000000000001") U_1)>(%metatype_1) : $@convention(thin) <T> (@thick T.Type) -> ()
|
||||
%t = tuple ()
|
||||
return %t : $()
|
||||
}
|
||||
@@ -22,3 +22,9 @@ public func printBool(_ bool: Bool) {
|
||||
public func printAny(_ any: Any) {
|
||||
print(any)
|
||||
}
|
||||
|
||||
@_silgen_name("printGenericType")
|
||||
public func printGenericType<T>(_ t: T.Type) {
|
||||
print(T.self)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user