[JS IR] Extract adding of function call to another function
[JS IR] Add option for dce mode [JS IR] Add logging to non useful declarations if appropriate dce mode [JS IR] Add mode with throwing exception [JS IR] unreachableDeclaration method is in rootDeclarations [JS IR] Add js extra help arg with dce mode and include debug.kt to compile unreachableMethod [JS IR] unreachableDeclaration as internal to not reproduce stdlib api [JS IR] Fix description of dce mode argument - Use console.error instead of console.log - Use JsError instead Kotlin exception for lightweight [JS IR] Remove body for throwing exception [JS IR] Remove default parameter in unreachableDeclaration [JS IR] Process without removing fields and declaration containers [JS IR] Rename dce mode on dce runtime diagnostic [JS IR] Use console.trace instead of console.error [JS IR] Extract JsError - Fix naming in prependFunctionCall - Fix description on runtime diagnostic argument - Using message collector instead of throwing exception [JS IR] Distinguish unreachableMethods for log and exception [JS IR] Extract checking of Kotlin packages of IrField ^KT-45059 fixed
This commit is contained in:
+8
-2
@@ -5,8 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.arguments
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.CALL
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.NO_CALL
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.*
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
@@ -126,6 +125,13 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
|
||||
@Argument(value = "-Xir-dce", description = "Perform experimental dead code elimination")
|
||||
var irDce: Boolean by FreezableVar(false)
|
||||
|
||||
@Argument(
|
||||
value = "-Xir-dce-runtime-diagnostic",
|
||||
valueDescription = "{$DCE_RUNTIME_DIAGNOSTIC_LOG|$DCE_RUNTIME_DIAGNOSTIC_EXCEPTION}",
|
||||
description = "Enable runtime diagnostics when performing DCE instead of removing declarations"
|
||||
)
|
||||
var irDceRuntimeDiagnostic: String? by NullableStringFreezableVar(null)
|
||||
|
||||
@Argument(value = "-Xir-dce-driven", description = "Perform a more experimental faster dead code elimination")
|
||||
var irDceDriven: Boolean by FreezableVar(false)
|
||||
|
||||
|
||||
+3
@@ -28,4 +28,7 @@ public interface K2JsArgumentConstants {
|
||||
String SOURCE_MAP_SOURCE_CONTENT_ALWAYS = "always";
|
||||
String SOURCE_MAP_SOURCE_CONTENT_NEVER = "never";
|
||||
String SOURCE_MAP_SOURCE_CONTENT_INLINING = "inlining";
|
||||
|
||||
String DCE_RUNTIME_DIAGNOSTIC_LOG = "log";
|
||||
String DCE_RUNTIME_DIAGNOSTIC_EXCEPTION = "exception";
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode.OK
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.*
|
||||
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot
|
||||
import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension
|
||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
||||
@@ -259,12 +260,17 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
mainArguments = mainCallArguments,
|
||||
generateFullJs = !arguments.irDce,
|
||||
generateDceJs = arguments.irDce,
|
||||
dceRuntimeDiagnostic = DceRuntimeDiagnostic.resolve(
|
||||
arguments.irDceRuntimeDiagnostic,
|
||||
messageCollector
|
||||
),
|
||||
dceDriven = arguments.irDceDriven,
|
||||
multiModule = arguments.irPerModule,
|
||||
relativeRequirePath = true,
|
||||
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
||||
)
|
||||
|
||||
|
||||
val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!!
|
||||
outputFile.writeText(jsCode.mainModule)
|
||||
jsCode.dependencies.forEach { (name, content) ->
|
||||
@@ -422,6 +428,19 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun DceRuntimeDiagnostic.Companion.resolve(
|
||||
value: String?,
|
||||
messageCollector: MessageCollector
|
||||
): DceRuntimeDiagnostic? = when (value?.toLowerCase()) {
|
||||
DCE_RUNTIME_DIAGNOSTIC_LOG -> DceRuntimeDiagnostic.LOG
|
||||
DCE_RUNTIME_DIAGNOSTIC_EXCEPTION -> DceRuntimeDiagnostic.EXCEPTION
|
||||
null -> null
|
||||
else -> {
|
||||
messageCollector.report(STRONG_WARNING, "Unknown DCE runtime diagnostic '$value'")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun messageCollectorLogger(collector: MessageCollector) = object : Logger {
|
||||
override fun warning(message: String) = collector.report(STRONG_WARNING, message)
|
||||
override fun error(message: String) = collector.report(ERROR, message)
|
||||
|
||||
@@ -7,7 +7,9 @@ package org.jetbrains.kotlin.ir.backend.js
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.ir.isMemberOfOpenClass
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.export.isExported
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
@@ -20,7 +22,9 @@ import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.js.config.removingBody
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import java.util.*
|
||||
|
||||
@@ -34,7 +38,11 @@ fun eliminateDeadDeclarations(
|
||||
val usefulDeclarations = usefulDeclarations(allRoots, context)
|
||||
|
||||
context.irFactory.stageController.unrestrictDeclarationListsAccess {
|
||||
removeUselessDeclarations(modules, usefulDeclarations)
|
||||
processUselessDeclarations(
|
||||
modules,
|
||||
usefulDeclarations,
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +55,7 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackend
|
||||
(modules.flatMap { it.files } + context.packageLevelJsModules + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file ->
|
||||
file.declarations.flatMap { if (it is IrProperty) listOfNotNull(it.backingField, it.getter, it.setter) else listOf(it) }
|
||||
.filter {
|
||||
it is IrField && it.initializer != null && it.fqNameWhenAvailable?.asString()?.startsWith("kotlin") != true
|
||||
it is IrField && it.initializer != null && !it.isKotlinPackage()
|
||||
|| it.isExported(context)
|
||||
|| it.isEffectivelyExternal()
|
||||
|| it is IrField && it.correspondingPropertySymbol?.owner?.isExported(context) == true
|
||||
@@ -57,6 +65,11 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackend
|
||||
|
||||
rootDeclarations += context.testRoots.values
|
||||
|
||||
val dceRuntimeDiagnostic = context.dceRuntimeDiagnostic
|
||||
if (dceRuntimeDiagnostic != null) {
|
||||
rootDeclarations += dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner
|
||||
}
|
||||
|
||||
JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction ->
|
||||
rootDeclarations += mainFunction
|
||||
if (mainFunction.isSuspend) {
|
||||
@@ -67,7 +80,17 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, context: JsIrBackend
|
||||
return rootDeclarations
|
||||
}
|
||||
|
||||
private fun removeUselessDeclarations(modules: Iterable<IrModuleFragment>, usefulDeclarations: Set<IrDeclaration>) {
|
||||
private fun DceRuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) =
|
||||
when (this) {
|
||||
DceRuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog
|
||||
DceRuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException
|
||||
}
|
||||
|
||||
private fun processUselessDeclarations(
|
||||
modules: Iterable<IrModuleFragment>,
|
||||
usefulDeclarations: Set<IrDeclaration>,
|
||||
context: JsIrBackendContext
|
||||
) {
|
||||
modules.forEach { module ->
|
||||
module.files.forEach {
|
||||
it.acceptVoid(object : IrElementVisitorVoid {
|
||||
@@ -99,7 +122,7 @@ private fun removeUselessDeclarations(modules: Iterable<IrModuleFragment>, usefu
|
||||
private fun process(container: IrDeclarationContainer) {
|
||||
container.declarations.transformFlat { member ->
|
||||
if (member !in usefulDeclarations) {
|
||||
emptyList()
|
||||
member.processUselessDeclaration(context)
|
||||
} else {
|
||||
member.acceptVoid(this)
|
||||
null
|
||||
@@ -111,6 +134,53 @@ private fun removeUselessDeclarations(modules: Iterable<IrModuleFragment>, usefu
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrDeclaration.processUselessDeclaration(context: JsIrBackendContext): List<IrDeclaration>? {
|
||||
return when {
|
||||
context.dceRuntimeDiagnostic != null -> {
|
||||
processWithDiagnostic(context)
|
||||
return null
|
||||
}
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrDeclaration.processWithDiagnostic(context: JsIrBackendContext) {
|
||||
when (this) {
|
||||
is IrFunction -> processFunctionWithDiagnostic(context)
|
||||
is IrField -> processFieldWithDiagnostic()
|
||||
is IrDeclarationContainer -> declarations.forEach { it.processWithDiagnostic(context) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrFunction.processFunctionWithDiagnostic(context: JsIrBackendContext) {
|
||||
val dceRuntimeDiagnostic = context.dceRuntimeDiagnostic!!
|
||||
|
||||
val isRemovingBody = dceRuntimeDiagnostic.removingBody()
|
||||
val targetMethod = dceRuntimeDiagnostic.unreachableDeclarationMethod(context)
|
||||
val call = JsIrBuilder.buildCall(
|
||||
target = targetMethod,
|
||||
type = targetMethod.owner.returnType
|
||||
)
|
||||
|
||||
if (isRemovingBody) {
|
||||
body = context.irFactory.createBlockBody(
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET
|
||||
)
|
||||
}
|
||||
|
||||
body?.prependFunctionCall(call)
|
||||
}
|
||||
|
||||
private fun IrField.processFieldWithDiagnostic() {
|
||||
if (initializer != null && isKotlinPackage()) {
|
||||
initializer = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrField.isKotlinPackage() =
|
||||
fqNameWhenAvailable?.asString()?.startsWith("kotlin") == true
|
||||
|
||||
// TODO refactor it, the function became too big. Please contact me (Zalim) before doing it.
|
||||
fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendContext): Set<IrDeclaration> {
|
||||
val printReachabilityInfo =
|
||||
|
||||
@@ -162,6 +162,9 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
|
||||
|
||||
val jsImul = getInternalFunction("imul")
|
||||
|
||||
val jsUnreachableDeclarationLog = getInternalFunction("unreachableDeclarationLog")
|
||||
val jsUnreachableDeclarationException = getInternalFunction("unreachableDeclarationException")
|
||||
|
||||
// Coroutines
|
||||
|
||||
val jsCoroutineContext
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmen
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.types.impl.IrDynamicTypeImpl
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -44,6 +45,7 @@ class JsIrBackendContext(
|
||||
override val configuration: CompilerConfiguration, // TODO: remove configuration from backend context
|
||||
override val scriptMode: Boolean = false,
|
||||
override val es6mode: Boolean = false,
|
||||
val dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null,
|
||||
val propertyLazyInitialization: Boolean = false,
|
||||
override val irFactory: IrFactory = IrFactoryImpl
|
||||
) : JsCommonBackendContext {
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory
|
||||
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
||||
import org.jetbrains.kotlin.ir.util.noUnboundLeft
|
||||
import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic
|
||||
import org.jetbrains.kotlin.library.KotlinLibrary
|
||||
import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -45,6 +46,7 @@ fun compile(
|
||||
generateFullJs: Boolean = true,
|
||||
generateDceJs: Boolean = false,
|
||||
dceDriven: Boolean = false,
|
||||
dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null,
|
||||
es6mode: Boolean = false,
|
||||
multiModule: Boolean = false,
|
||||
relativeRequirePath: Boolean = false,
|
||||
@@ -70,6 +72,7 @@ fun compile(
|
||||
exportedDeclarations,
|
||||
configuration,
|
||||
es6mode = es6mode,
|
||||
dceRuntimeDiagnostic = dceRuntimeDiagnostic,
|
||||
propertyLazyInitialization = propertyLazyInitialization,
|
||||
irFactory = irFactory
|
||||
)
|
||||
|
||||
+3
-25
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrArithBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.prependFunctionCall
|
||||
import org.jetbrains.kotlin.ir.util.isPure
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildField
|
||||
@@ -74,7 +75,7 @@ class PropertyLazyInitLowering(
|
||||
|
||||
when (container) {
|
||||
is IrSimpleFunction ->
|
||||
irBody.addInitialization(initializationCall, container)
|
||||
irBody.prependFunctionCall(initializationCall)
|
||||
is IrField -> {
|
||||
container
|
||||
.correspondingProperty
|
||||
@@ -83,7 +84,7 @@ class PropertyLazyInitLowering(
|
||||
?.let { listOf(it.getter, it.setter) }
|
||||
?.filterNotNull()
|
||||
?.forEach {
|
||||
irBody.addInitialization(initializationCall, it)
|
||||
irBody.prependFunctionCall(initializationCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,29 +173,6 @@ class PropertyLazyInitLowering(
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrBody.addInitialization(
|
||||
initCall: IrCall,
|
||||
container: IrSimpleFunction
|
||||
) {
|
||||
when (this) {
|
||||
is IrExpressionBody -> {
|
||||
expression = JsIrBuilder.buildComposite(
|
||||
type = container.returnType,
|
||||
statements = listOf(
|
||||
initCall,
|
||||
expression
|
||||
)
|
||||
)
|
||||
}
|
||||
is IrBlockBody -> {
|
||||
statements.add(
|
||||
0,
|
||||
initCall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIrGetField(field: IrField): IrGetField {
|
||||
return JsIrBuilder.buildGetField(
|
||||
symbol = field.symbol,
|
||||
|
||||
@@ -11,8 +11,9 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.ir.backend.js.export.isExported
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
@@ -88,4 +89,26 @@ val IrValueDeclaration.isDispatchReceiver: Boolean
|
||||
if (parent is IrFunction && parent.dispatchReceiverParameter == this)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun IrBody.prependFunctionCall(
|
||||
call: IrCall
|
||||
) {
|
||||
when (this) {
|
||||
is IrExpressionBody -> {
|
||||
expression = JsIrBuilder.buildComposite(
|
||||
type = expression.type,
|
||||
statements = listOf(
|
||||
call,
|
||||
expression
|
||||
)
|
||||
)
|
||||
}
|
||||
is IrBlockBody -> {
|
||||
statements.add(
|
||||
0,
|
||||
call
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -11,6 +11,8 @@ where advanced options include:
|
||||
-Xir-dce-driven Perform a more experimental faster dead code elimination
|
||||
-Xir-dce-print-reachability-info
|
||||
Print declarations' reachability info to stdout during performing DCE
|
||||
-Xir-dce-runtime-diagnostic={log|exception}
|
||||
Enable runtime diagnostics when performing DCE instead of removing declarations
|
||||
-Xir-module-name=<name> Specify a compilation module name for IR backend
|
||||
-Xir-only Disables pre-IR backend
|
||||
-Xir-per-module Splits generated .js per-module
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.config
|
||||
|
||||
enum class DceRuntimeDiagnostic {
|
||||
LOG,
|
||||
EXCEPTION;
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
fun DceRuntimeDiagnostic.removingBody(): Boolean {
|
||||
return this != DceRuntimeDiagnostic.LOG
|
||||
}
|
||||
|
||||
fun DceRuntimeDiagnostic.dceRuntimeDiagnosticToArgumentOfUnreachableMethod(): Int {
|
||||
return when (this) {
|
||||
DceRuntimeDiagnostic.LOG -> 0
|
||||
DceRuntimeDiagnostic.EXCEPTION -> 1
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,6 @@ val jsMainSources by task<Sync> {
|
||||
"libraries/stdlib/js/src/kotlin/console.kt",
|
||||
"libraries/stdlib/js/src/kotlin/coreDeprecated.kt",
|
||||
"libraries/stdlib/js/src/kotlin/date.kt",
|
||||
"libraries/stdlib/js/src/kotlin/debug.kt",
|
||||
"libraries/stdlib/js/src/kotlin/grouping.kt",
|
||||
"libraries/stdlib/js/src/kotlin/json.kt",
|
||||
"libraries/stdlib/js/src/kotlin/promise.kt",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@JsName("Error")
|
||||
internal open external class JsError(message: String) : Throwable
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.js
|
||||
|
||||
import JsError
|
||||
|
||||
internal fun unreachableDeclarationLog() {
|
||||
console.asDynamic().trace("Unreachable declaration")
|
||||
}
|
||||
|
||||
internal fun unreachableDeclarationException() {
|
||||
throw JsError("Unreachable declaration")
|
||||
}
|
||||
Reference in New Issue
Block a user