mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[embedded] Introduce class-bound existentials into Embedded Swift
Motivated by need for protocol-based dynamic dispatch, which hasn't been possible in Embedded Swift due to a full ban on existentials. This lifts that restriction but only for class-bound existentials: Class-bound existentials are already (even in desktop Swift) much more lightweight than full existentials, as they don't need type metadata, their containers are typically 2 words only (reference + wtable pointer), don't incur copies (only retains+releases). Included in this PR: [x] Non-generic class-bound existentials, executable tests for those. [x] Extension methods on protocols and using those from a class-bound existential. [x] RuntimeEffects now differentiate between Existential and ExistentialClassBound. [x] PerformanceDiagnostics don't flag ExistentialClassBound in Embedded Swift. [x] WTables are generated in IRGen when needed. Left for follow-up PRs: [ ] Generic classes support
This commit is contained in:
@@ -89,9 +89,23 @@ inline bool isEmbedded(CanType t) {
|
||||
return t->getASTContext().LangOpts.hasFeature(Feature::Embedded);
|
||||
}
|
||||
|
||||
// Metadata is not generated and not allowed to be referenced in Embedded Swift,
|
||||
// expect for classes (both generic and non-generic), dynamic self, and
|
||||
// class-bound existentials.
|
||||
inline bool isMetadataAllowedInEmbedded(CanType t) {
|
||||
return isa<ClassType>(t) || isa<BoundGenericClassType>(t) ||
|
||||
isa<DynamicSelfType>(t);
|
||||
if (isa<ClassType>(t) || isa<BoundGenericClassType>(t) ||
|
||||
isa<DynamicSelfType>(t)) {
|
||||
return true;
|
||||
}
|
||||
if (auto existentialTy = dyn_cast<ExistentialType>(t)) {
|
||||
if (existentialTy->requiresClass())
|
||||
return true;
|
||||
}
|
||||
if (auto archeTy = dyn_cast<ArchetypeType>(t)) {
|
||||
if (archeTy->requiresClass())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool isEmbedded(Decl *d) {
|
||||
@@ -1062,7 +1076,10 @@ public:
|
||||
}
|
||||
|
||||
static LinkEntity forProtocolWitnessTable(const RootProtocolConformance *C) {
|
||||
assert(!isEmbedded(C));
|
||||
if (isEmbedded(C)) {
|
||||
assert(C->getProtocol()->requiresClass());
|
||||
}
|
||||
|
||||
LinkEntity entity;
|
||||
entity.setForProtocolConformance(Kind::ProtocolWitnessTable, C);
|
||||
return entity;
|
||||
|
||||
@@ -55,6 +55,9 @@ enum class RuntimeEffect : unsigned {
|
||||
/// Witness methods, boxing, unboxing, initializing, etc.
|
||||
Existential = 0x80,
|
||||
|
||||
/// Class-bound only existential
|
||||
ExistentialClassBound = 0x200,
|
||||
|
||||
/// Not modelled currently.
|
||||
Concurrency = 0x0,
|
||||
|
||||
|
||||
@@ -1142,8 +1142,9 @@ void IRGenModule::emitGlobalLists() {
|
||||
// Eagerly emit functions that are externally visible. Functions that are
|
||||
// dynamic replacements must also be eagerly emitted.
|
||||
static bool isLazilyEmittedFunction(SILFunction &f, SILModule &m) {
|
||||
// Embedded Swift only emits specialized function, so don't emit generic
|
||||
// functions, even if they're externally visible.
|
||||
// Embedded Swift only emits specialized function (except when they are
|
||||
// protocol witness methods). So don't emit generic functions, even if they're
|
||||
// externally visible.
|
||||
if (f.getASTContext().LangOpts.hasFeature(Feature::Embedded) &&
|
||||
f.getLoweredFunctionType()->getSubstGenericSignature()) {
|
||||
return true;
|
||||
@@ -1332,7 +1333,7 @@ void IRGenerator::emitLazyDefinitions() {
|
||||
assert(LazyFieldDescriptors.empty());
|
||||
// LazyFunctionDefinitions are allowed, but they must not be generic
|
||||
for (SILFunction *f : LazyFunctionDefinitions) {
|
||||
assert(!f->isGeneric());
|
||||
assert(hasValidSignatureForEmbedded(f));
|
||||
}
|
||||
assert(LazyWitnessTables.empty());
|
||||
assert(LazyCanonicalSpecializedMetadataAccessors.empty());
|
||||
@@ -1482,7 +1483,7 @@ void IRGenerator::addLazyFunction(SILFunction *f) {
|
||||
|
||||
// Embedded Swift doesn't expect any generic functions to be referenced.
|
||||
if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
|
||||
assert(!f->isGeneric());
|
||||
assert(hasValidSignatureForEmbedded(f));
|
||||
}
|
||||
|
||||
assert(!FinishedEmittingLazyDefinitions);
|
||||
@@ -3473,6 +3474,24 @@ llvm::CallBase *swift::irgen::emitCXXConstructorCall(
|
||||
return result;
|
||||
}
|
||||
|
||||
// For a SILFunction to be legal in Embedded Swift, it must be either
|
||||
// - non-generic
|
||||
// - generic with parameters thar are either
|
||||
// - fully specialized (concrete)
|
||||
// - a class-bound archetype (class-bound existential)
|
||||
bool swift::irgen::hasValidSignatureForEmbedded(SILFunction *f) {
|
||||
auto s = f->getLoweredFunctionType()->getInvocationGenericSignature();
|
||||
for (auto genParam : s.getGenericParams()) {
|
||||
auto mappedParam = f->getGenericEnvironment()->mapTypeIntoContext(genParam);
|
||||
if (auto archeTy = dyn_cast<ArchetypeType>(mappedParam)) {
|
||||
if (archeTy->requiresClass())
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
StackProtectorMode IRGenModule::shouldEmitStackProtector(SILFunction *f) {
|
||||
const SILOptions &opts = IRGen.SIL.getOptions();
|
||||
return (opts.EnableStackProtection && f->needsStackProtection()) ?
|
||||
@@ -4352,6 +4371,9 @@ static bool conformanceIsVisibleViaMetadata(
|
||||
|
||||
|
||||
void IRGenModule::addProtocolConformance(ConformanceDescription &&record) {
|
||||
if (Context.LangOpts.hasFeature(Feature::Embedded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitProtocolConformance(record);
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@ namespace irgen {
|
||||
llvm::FunctionType *ctorFnType,
|
||||
llvm::Constant *ctorAddress,
|
||||
llvm::ArrayRef<llvm::Value *> args);
|
||||
|
||||
bool hasValidSignatureForEmbedded(SILFunction *f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1791,11 +1791,6 @@ static void forEachProtocolWitnessTable(
|
||||
assert(protocols.size() == witnessConformances.size() &&
|
||||
"mismatched protocol conformances");
|
||||
|
||||
// Don't emit witness tables in embedded Swift.
|
||||
if (srcType->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned i = 0, e = protocols.size(); i < e; ++i) {
|
||||
assert(protocols[i] == witnessConformances[i].getRequirement());
|
||||
auto table = emitWitnessTableRef(IGF, srcType, srcMetadataCache,
|
||||
|
||||
@@ -1512,6 +1512,13 @@ public:
|
||||
/// Add reference to the protocol conformance descriptor that generated
|
||||
/// this table.
|
||||
void addProtocolConformanceDescriptor() {
|
||||
// In Embedded Swift, there are no protocol conformance descriptors. Emit
|
||||
// a null pointer instead to keep the same layout as regular Swift.
|
||||
if (IGM.Context.LangOpts.hasFeature(Feature::Embedded)) {
|
||||
Table.addNullPointer(IGM.Int8PtrTy);
|
||||
return;
|
||||
}
|
||||
|
||||
auto descriptor =
|
||||
IGM.getAddrOfProtocolConformanceDescriptor(&Conformance);
|
||||
if (isRelative)
|
||||
@@ -2560,6 +2567,8 @@ static void addWTableTypeMetadata(IRGenModule &IGM,
|
||||
|
||||
void IRGenModule::emitSILWitnessTable(SILWitnessTable *wt) {
|
||||
if (Context.LangOpts.hasFeature(Feature::Embedded)) {
|
||||
// In Embedded Swift, only class-bound wtables are allowed.
|
||||
if (!wt->getConformance()->getProtocol()->requiresClass())
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3560,9 +3569,13 @@ llvm::Value *irgen::emitWitnessTableRef(IRGenFunction &IGF,
|
||||
CanType srcType,
|
||||
llvm::Value **srcMetadataCache,
|
||||
ProtocolConformanceRef conformance) {
|
||||
assert(!srcType->getASTContext().LangOpts.hasFeature(Feature::Embedded));
|
||||
|
||||
auto proto = conformance.getRequirement();
|
||||
|
||||
// In Embedded Swift, only class-bound wtables are allowed.
|
||||
if (srcType->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
|
||||
assert(proto->requiresClass());
|
||||
}
|
||||
|
||||
assert(Lowering::TypeConverter::protocolRequiresWitnessTable(proto)
|
||||
&& "protocol does not have witness tables?!");
|
||||
|
||||
|
||||
@@ -1407,7 +1407,10 @@ bool IRGenerator::canEmitWitnessTableLazily(SILWitnessTable *wt) {
|
||||
}
|
||||
|
||||
void IRGenerator::addLazyWitnessTable(const ProtocolConformance *Conf) {
|
||||
assert(!SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded));
|
||||
// In Embedded Swift, only class-bound wtables are allowed.
|
||||
if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
|
||||
assert(Conf->getProtocol()->requiresClass());
|
||||
}
|
||||
|
||||
if (auto *wt = SIL.lookUpWitnessTable(Conf)) {
|
||||
// Add it to the queue if it hasn't already been put there.
|
||||
|
||||
@@ -2478,10 +2478,6 @@ void IRGenModule::emitSILFunction(SILFunction *f) {
|
||||
if (f->isExternalDeclaration())
|
||||
return;
|
||||
|
||||
if (Context.LangOpts.hasFeature(Feature::Embedded) &&
|
||||
f->getLoweredFunctionType()->isPolymorphic())
|
||||
return;
|
||||
|
||||
// Do not emit bodies of public_external or package_external functions.
|
||||
if (hasPublicOrPackageVisibility(f->getLinkage(),
|
||||
f->getASTContext().SILOpts.EnableSerializePackage) &&
|
||||
|
||||
@@ -671,9 +671,13 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
|
||||
RuntimeEffect::MetaData | RuntimeEffect::Existential;
|
||||
|
||||
case SILInstructionKind::InitExistentialRefInst:
|
||||
impactType = inst->getOperand(0)->getType();
|
||||
return RuntimeEffect::MetaData | RuntimeEffect::ExistentialClassBound;
|
||||
|
||||
case SILInstructionKind::InitExistentialMetatypeInst:
|
||||
impactType = inst->getOperand(0)->getType();
|
||||
return RuntimeEffect::MetaData | RuntimeEffect::Existential;
|
||||
|
||||
case SILInstructionKind::ObjCToThickMetatypeInst:
|
||||
impactType = inst->getOperand(0)->getType();
|
||||
return RuntimeEffect::MetaData;
|
||||
@@ -693,14 +697,8 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
|
||||
return RuntimeEffect::Existential;
|
||||
|
||||
case SILInstructionKind::OpenExistentialRefInst: {
|
||||
SILType opType = cast<OpenExistentialRefInst>(inst)->getOperand()->getType();
|
||||
impactType = opType;
|
||||
if (opType.getASTType()->isObjCExistentialType()) {
|
||||
return RuntimeEffect::MetaData | RuntimeEffect::Existential;
|
||||
}
|
||||
return RuntimeEffect::MetaData | RuntimeEffect::Existential;
|
||||
// TODO: should be Existential
|
||||
//return RuntimeEffect::Existential;
|
||||
impactType = inst->getOperand(0)->getType();
|
||||
return RuntimeEffect::MetaData | RuntimeEffect::ExistentialClassBound;
|
||||
}
|
||||
|
||||
case SILInstructionKind::UnconditionalCheckedCastInst:
|
||||
@@ -963,7 +961,7 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
|
||||
rt |= RuntimeEffect::ObjectiveC | RuntimeEffect::MetaData;
|
||||
break;
|
||||
case SILFunctionTypeRepresentation::WitnessMethod:
|
||||
rt |= RuntimeEffect::MetaData | RuntimeEffect::Existential;
|
||||
rt |= RuntimeEffect::MetaData; // ???
|
||||
break;
|
||||
case SILFunctionTypeRepresentation::CFunctionPointer:
|
||||
case SILFunctionTypeRepresentation::CXXMethod:
|
||||
|
||||
@@ -534,7 +534,8 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
|
||||
LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc);
|
||||
|
||||
if (perfConstr == PerformanceConstraints::NoExistentials &&
|
||||
(impact & RuntimeEffect::Existential)) {
|
||||
((impact & RuntimeEffect::Existential) ||
|
||||
(impact & RuntimeEffect::ExistentialClassBound))) {
|
||||
PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst);
|
||||
if (impactType) {
|
||||
diagnose(loc, diag::perf_diag_existential_type, impactType.getASTType());
|
||||
@@ -556,6 +557,8 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
|
||||
}
|
||||
|
||||
if (module.getOptions().EmbeddedSwift) {
|
||||
// Explicitly don't detect RuntimeEffect::ExistentialClassBound - those are
|
||||
// allowed in Embedded Swift.
|
||||
if (impact & RuntimeEffect::Existential) {
|
||||
PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst);
|
||||
if (impactType) {
|
||||
|
||||
@@ -457,7 +457,13 @@ void addFunctionPasses(SILPassPipelinePlan &P,
|
||||
P.addMem2Reg();
|
||||
|
||||
// Run the existential specializer Pass.
|
||||
if (!P.getOptions().EmbeddedSwift) {
|
||||
// MandatoryPerformanceOptimizations already took care of all specializations
|
||||
// in embedded Swift mode, running the existential specializer might introduce
|
||||
// more generic calls from non-generic functions, which breaks the assumptions
|
||||
// of embedded Swift.
|
||||
P.addExistentialSpecializer();
|
||||
}
|
||||
|
||||
// Cleanup, which is important if the inliner has restarted the pass pipeline.
|
||||
P.addPerformanceConstantPropagation();
|
||||
|
||||
43
test/embedded/existential-class-bound1.swift
Normal file
43
test/embedded/existential-class-bound1.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo) | %FileCheck %s
|
||||
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo -O) | %FileCheck %s
|
||||
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo -Osize) | %FileCheck %s
|
||||
|
||||
// REQUIRES: swift_in_compiler
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: optimized_stdlib
|
||||
// REQUIRES: OS=macosx || OS=linux-gnu
|
||||
|
||||
protocol ClassBound: AnyObject {
|
||||
func foo()
|
||||
func bar()
|
||||
}
|
||||
|
||||
class MyClass {}
|
||||
extension MyClass: ClassBound {
|
||||
func foo() { print("MyClass.foo()") }
|
||||
func bar() { print("MyClass.bar()") }
|
||||
}
|
||||
|
||||
class MyOtherClass {}
|
||||
extension MyOtherClass: ClassBound {
|
||||
func foo() { print("MyOtherClass.foo()") }
|
||||
func bar() { print("MyOtherClass.bar()") }
|
||||
}
|
||||
|
||||
func test(existential: any ClassBound) {
|
||||
existential.foo()
|
||||
existential.bar()
|
||||
}
|
||||
|
||||
@main
|
||||
struct Main {
|
||||
static func main() {
|
||||
test(existential: MyClass())
|
||||
// CHECK: MyClass.foo()
|
||||
// CHECK: MyClass.bar()
|
||||
test(existential: MyOtherClass())
|
||||
// CHECK: MyOtherClass.foo()
|
||||
// CHECK: MyOtherClass.bar()
|
||||
}
|
||||
}
|
||||
|
||||
50
test/embedded/existential-class-bound2.swift
Normal file
50
test/embedded/existential-class-bound2.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo) | %FileCheck %s
|
||||
|
||||
// REQUIRES: swift_in_compiler
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: optimized_stdlib
|
||||
// REQUIRES: OS=macosx || OS=linux-gnu
|
||||
|
||||
protocol ClassBound: AnyObject {
|
||||
func foo()
|
||||
func bar()
|
||||
}
|
||||
|
||||
extension ClassBound {
|
||||
func extensionMethod() {
|
||||
self.foo()
|
||||
self.bar()
|
||||
}
|
||||
}
|
||||
|
||||
class MyClass {}
|
||||
extension MyClass: ClassBound {
|
||||
func foo() { print("MyClass.foo()") }
|
||||
func bar() { print("MyClass.bar()") }
|
||||
}
|
||||
|
||||
class MyOtherClass {}
|
||||
extension MyOtherClass: ClassBound {
|
||||
func foo() { print("MyOtherClass.foo()") }
|
||||
func bar() { print("MyOtherClass.bar()") }
|
||||
}
|
||||
|
||||
@main
|
||||
struct Main {
|
||||
static func main() {
|
||||
var array: [any ClassBound] = []
|
||||
array.append(MyClass())
|
||||
array.append(MyOtherClass())
|
||||
|
||||
for e in array {
|
||||
e.extensionMethod()
|
||||
}
|
||||
|
||||
// CHECK: MyClass.foo()
|
||||
// CHECK: MyClass.bar()
|
||||
|
||||
// CHECK: MyOtherClass.foo()
|
||||
// CHECK: MyOtherClass.bar()
|
||||
}
|
||||
}
|
||||
|
||||
44
test/embedded/existential-class-bound3.swift
Normal file
44
test/embedded/existential-class-bound3.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo) | %FileCheck %s
|
||||
|
||||
// REQUIRES: swift_in_compiler
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: optimized_stdlib
|
||||
// REQUIRES: OS=macosx || OS=linux-gnu
|
||||
|
||||
protocol ClassBound: AnyObject {
|
||||
func foo()
|
||||
func bar()
|
||||
}
|
||||
|
||||
class MyClass {}
|
||||
extension MyClass: ClassBound {
|
||||
func foo() { print("MyClass.foo()") }
|
||||
func bar() { print("MyClass.bar()") }
|
||||
}
|
||||
|
||||
class MyOtherClass {}
|
||||
extension MyOtherClass: ClassBound {
|
||||
func foo() { print("MyOtherClass.foo()") }
|
||||
func bar() { print("MyOtherClass.bar()") }
|
||||
}
|
||||
|
||||
@main
|
||||
struct Main {
|
||||
static func main() {
|
||||
var array: [any ClassBound] = []
|
||||
array.append(MyClass())
|
||||
array.append(MyOtherClass())
|
||||
|
||||
for e in array {
|
||||
e.foo()
|
||||
e.bar()
|
||||
}
|
||||
|
||||
// CHECK: MyClass.foo()
|
||||
// CHECK: MyClass.bar()
|
||||
|
||||
// CHECK: MyOtherClass.foo()
|
||||
// CHECK: MyOtherClass.bar()
|
||||
}
|
||||
}
|
||||
|
||||
44
test/embedded/existential-class-bound4.swift
Normal file
44
test/embedded/existential-class-bound4.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo) | %FileCheck %s
|
||||
|
||||
// REQUIRES: swift_in_compiler
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: optimized_stdlib
|
||||
// REQUIRES: OS=macosx || OS=linux-gnu
|
||||
|
||||
// Generic classes don't work yet.
|
||||
// XFAIL: *
|
||||
|
||||
protocol ClassBound: AnyObject {
|
||||
func foo()
|
||||
func bar()
|
||||
}
|
||||
|
||||
class MyGenericClass<T> {
|
||||
var typ: String
|
||||
init(typ: String) { self.typ = typ }
|
||||
}
|
||||
extension MyGenericClass: ClassBound {
|
||||
func foo() { print("MyGenericClass<\(typ)>.foo()") }
|
||||
func bar() { print("MyGenericClass<\(typ)>.bar()") }
|
||||
}
|
||||
|
||||
@main
|
||||
struct Main {
|
||||
static func main() {
|
||||
var array: [any ClassBound] = []
|
||||
array.append(MyGenericClass<Int>(typ: "Int"))
|
||||
array.append(MyGenericClass<String>(typ: "String"))
|
||||
|
||||
for e in array {
|
||||
e.foo()
|
||||
e.bar()
|
||||
}
|
||||
|
||||
// CHECK: MyGenericClass<Int>.foo()
|
||||
// CHECK: MyGenericClass<Int>.bar()
|
||||
|
||||
// CHECK: MyGenericClass<String>.foo()
|
||||
// CHECK: MyGenericClass<String>.bar()
|
||||
}
|
||||
}
|
||||
|
||||
35
test/embedded/existential-class-bound5.swift
Normal file
35
test/embedded/existential-class-bound5.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
// RUN: %target-swift-emit-ir -parse-as-library -module-name main -verify %s -enable-experimental-feature Embedded -wmo
|
||||
|
||||
// REQUIRES: swift_in_compiler
|
||||
// REQUIRES: optimized_stdlib
|
||||
// REQUIRES: OS=macosx || OS=linux-gnu
|
||||
|
||||
protocol ClassBound: AnyObject {
|
||||
func foo()
|
||||
}
|
||||
|
||||
protocol NotClassBound {
|
||||
func foo()
|
||||
}
|
||||
|
||||
class MyClass {}
|
||||
extension MyClass: ClassBound, NotClassBound {
|
||||
func foo() { print("MyClass.foo()") }
|
||||
}
|
||||
|
||||
func test(existential: any ClassBound) {
|
||||
existential.foo() // ok
|
||||
}
|
||||
|
||||
func test(existential: any NotClassBound) {
|
||||
existential.foo() // expected-error {{cannot use a value of protocol type in embedded Swift}}
|
||||
// expected-note@+3 {{called from here}}
|
||||
}
|
||||
|
||||
@main
|
||||
struct Main {
|
||||
static func main() {
|
||||
test(existential: MyClass() as (any ClassBound)) // ok
|
||||
test(existential: MyClass() as (any NotClassBound)) // expected-error {{cannot use a value of protocol type 'any NotClassBound' in embedded Swift}}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user