mirror of
https://github.com/KarolS/millfork.git
synced 2024-12-26 12:29:26 +00:00
Interrupt handler optimization fixes
This commit is contained in:
parent
516d7eae26
commit
2548822b8b
@ -228,41 +228,50 @@ object AlwaysGoodOptimizations {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private def operationPairBuilder(op1: Opcode.Value, op2: Opcode.Value, middle: AssemblyLinePattern) = {
|
private def operationPairBuilder(op1: Opcode.Value, op2: Opcode.Value, middle: AssemblyLinePattern, discardToRemove: Option[Opcode.Value]) = {
|
||||||
(HasOpcode(op1) & Elidable) ~
|
(HasOpcode(op1) & Elidable) ~
|
||||||
(Linear & middle).*.capture(1) ~
|
(Linear & middle).*.capture(1) ~
|
||||||
(HasOpcode(op2) & Elidable) ~
|
(HasOpcode(op2) & Elidable) ~
|
||||||
((LinearOrLabel & Not(ReadsNOrZ) & Not(ChangesNAndZ)).* ~ ChangesNAndZ).capture(2) ~~> { (_, ctx) =>
|
((LinearOrLabel & Not(ReadsNOrZ) & Not(ChangesNAndZ)).* ~ ChangesNAndZ).capture(2) ~~> { (_, ctx) =>
|
||||||
ctx.get[List[AssemblyLine]](1) ++ ctx.get[List[AssemblyLine]](2)
|
ctx.get[List[AssemblyLine]](1).filter(l => !discardToRemove.contains(l.opcode)) ++ ctx.get[List[AssemblyLine]](2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val PointlessOperationPairRemoval = new RuleBasedAssemblyOptimization("Pointless operation pair",
|
val PointlessOperationPairRemoval = new RuleBasedAssemblyOptimization("Pointless operation pair",
|
||||||
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
operationPairBuilder(PHA, PLA, Not(ChangesA) & Not(ConcernsStack)),
|
operationPairBuilder(PHA, PLA, Not(ChangesA) & Not(ConcernsStack), Some(DISCARD_AF)),
|
||||||
operationPairBuilder(PHX, PLX, Not(ChangesX) & Not(ConcernsStack)),
|
operationPairBuilder(PHX, PLX, Not(ChangesX) & Not(ConcernsStack), Some(DISCARD_XF)),
|
||||||
operationPairBuilder(PHY, PLY, Not(ChangesY) & Not(ConcernsStack)),
|
operationPairBuilder(PHY, PLY, Not(ChangesY) & Not(ConcernsStack), Some(DISCARD_YF)),
|
||||||
operationPairBuilder(PHZ, PLZ, Not(ChangesIZ) & Not(ConcernsStack)),
|
operationPairBuilder(PHZ, PLZ, Not(ChangesIZ) & Not(ConcernsStack), Some(DISCARD_YF)),
|
||||||
operationPairBuilder(INX, DEX, Not(ConcernsX) & Not(ReadsNOrZ)),
|
operationPairBuilder(INX, DEX, Not(ConcernsX) & Not(ReadsNOrZ), None),
|
||||||
operationPairBuilder(DEX, INX, Not(ConcernsX) & Not(ReadsNOrZ)),
|
operationPairBuilder(DEX, INX, Not(ConcernsX) & Not(ReadsNOrZ), None),
|
||||||
operationPairBuilder(INY, DEY, Not(ConcernsX) & Not(ReadsNOrZ)),
|
operationPairBuilder(INY, DEY, Not(ConcernsX) & Not(ReadsNOrZ), None),
|
||||||
operationPairBuilder(DEY, INY, Not(ConcernsX) & Not(ReadsNOrZ)),
|
operationPairBuilder(DEY, INY, Not(ConcernsX) & Not(ReadsNOrZ), None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
private def operationPairBuilder2(op1: Opcode.Value, op1extra: AssemblyLinePattern, middle: AssemblyLinePattern,op2: Opcode.Value, op2extra: AssemblyLinePattern) = {
|
private def operationPairBuilder2(op1: Opcode.Value, op1extra: AssemblyLinePattern, middle: AssemblyLinePattern,op2: Opcode.Value, op2extra: AssemblyLinePattern, discardToRemove: Option[Opcode.Value]) = {
|
||||||
(HasOpcode(op1) & Elidable & op1extra) ~
|
(HasOpcode(op1) & Elidable & op1extra) ~
|
||||||
(Linear & middle).*.capture(1) ~
|
(Linear & middle).*.capture(1) ~
|
||||||
(HasOpcode(op2) & Elidable & op2extra) ~~> { (_, ctx) =>
|
(HasOpcode(op2) & Elidable & op2extra) ~~> { (_, ctx) =>
|
||||||
ctx.get[List[AssemblyLine]](1)
|
ctx.get[List[AssemblyLine]](1).filter(l => !discardToRemove.contains(l.opcode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def operationPairBuilder3(op1: Opcode.Value, op1extra: AssemblyLinePattern, op2: Opcode.Value, middle: AssemblyLinePattern) = {
|
private def operationPairBuilder3(op1: Opcode.Value, op1extra: AssemblyLinePattern, op2: Opcode.Value, middle: AssemblyLinePattern, discardToRemove: Option[Opcode.Value]) = {
|
||||||
(HasOpcode(op1) & Elidable & op1extra) ~
|
(HasOpcode(op1) & Elidable & op1extra) ~
|
||||||
middle.*.capture(1) ~
|
middle.*.capture(1) ~
|
||||||
Where(_.isExternallyLinearBlock(1)) ~
|
Where(_.isExternallyLinearBlock(1)) ~
|
||||||
(HasOpcode(op2) & Elidable) ~~> { (_, ctx) =>
|
(HasOpcode(op2) & Elidable) ~~> { (_, ctx) =>
|
||||||
|
ctx.get[List[AssemblyLine]](1).filter(l => !discardToRemove.contains(l.opcode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def operationPairBuilder4(op1: Opcode.Value, op1extra: AssemblyLinePattern, middle: AssemblyLinePattern, op2: Opcode.Value, op2extra: AssemblyLinePattern) = {
|
||||||
|
(HasOpcode(op1) & op1extra & Elidable & HasAddrModeIn(Set(Absolute, ZeroPage, LongAbsolute)) & MatchParameter(3)) ~
|
||||||
|
middle.*.capture(1) ~
|
||||||
|
Where(_.isExternallyLinearBlock(1)) ~
|
||||||
|
(HasOpcode(op2) & op2extra & Elidable & HasAddrModeIn(Set(Absolute, ZeroPage, LongAbsolute)) & MatchParameter(3)) ~~> { (_, ctx) =>
|
||||||
ctx.get[List[AssemblyLine]](1)
|
ctx.get[List[AssemblyLine]](1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,41 +281,65 @@ object AlwaysGoodOptimizations {
|
|||||||
operationPairBuilder2(
|
operationPairBuilder2(
|
||||||
PHA, Anything,
|
PHA, Anything,
|
||||||
Not(ConcernsStack),
|
Not(ConcernsStack),
|
||||||
PLA, DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)),
|
PLA, DoesntMatterWhatItDoesWith(State.A, State.N, State.Z), Some(DISCARD_AF)),
|
||||||
operationPairBuilder2(
|
operationPairBuilder2(
|
||||||
PHX, Anything,
|
PHX, Anything,
|
||||||
Not(ConcernsStack),
|
Not(ConcernsStack),
|
||||||
PLX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)),
|
PLX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z), Some(DISCARD_XF)),
|
||||||
operationPairBuilder2(
|
operationPairBuilder2(
|
||||||
PHY, Anything,
|
PHY, Anything,
|
||||||
Not(ConcernsStack),
|
Not(ConcernsStack),
|
||||||
PLY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)),
|
PLY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z), Some(DISCARD_YF)),
|
||||||
operationPairBuilder2(
|
operationPairBuilder2(
|
||||||
INX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z),
|
INX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z),
|
||||||
Anything,
|
Anything,
|
||||||
DEX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)),
|
DEX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z), None),
|
||||||
operationPairBuilder2(
|
operationPairBuilder2(
|
||||||
DEX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z),
|
DEX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z),
|
||||||
Anything,
|
Anything,
|
||||||
INX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)),
|
INX, DoesntMatterWhatItDoesWith(State.X, State.N, State.Z), None),
|
||||||
operationPairBuilder2(
|
operationPairBuilder2(
|
||||||
INY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z),
|
INY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z),
|
||||||
Anything,
|
Anything,
|
||||||
DEY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)),
|
DEY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z), None),
|
||||||
operationPairBuilder2(
|
operationPairBuilder2(
|
||||||
DEY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z),
|
DEY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z),
|
||||||
Anything,
|
Anything,
|
||||||
INY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)),
|
INY, DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z), None),
|
||||||
operationPairBuilder3(PHA, Anything, PLA, Not(ChangesA) & Not(ConcernsStack)),
|
operationPairBuilder3(PHA, Anything, PLA, Not(ChangesA) & Not(ConcernsStack), Some(DISCARD_AF)),
|
||||||
operationPairBuilder3(PHX, Anything, PLX, Not(ChangesX) & Not(ConcernsStack)),
|
operationPairBuilder3(PHX, Anything, PLX, Not(ChangesX) & Not(ConcernsStack), Some(DISCARD_XF)),
|
||||||
operationPairBuilder3(PHY, Anything, PLY, Not(ChangesY) & Not(ConcernsStack)),
|
operationPairBuilder3(PHY, Anything, PLY, Not(ChangesY) & Not(ConcernsStack), Some(DISCARD_YF)),
|
||||||
operationPairBuilder3(PHZ, Anything, PLZ, Not(ChangesIZ) & Not(ConcernsStack)),
|
operationPairBuilder3(PHZ, Anything, PLZ, Not(ChangesIZ) & Not(ConcernsStack), Some(DISCARD_YF)),
|
||||||
operationPairBuilder3(PHD, Anything, PLD, Not(ChangesDirectPageRegister)),
|
operationPairBuilder3(PHD, Anything, PLD, Not(ChangesDirectPageRegister), None),
|
||||||
operationPairBuilder3(PHB, Anything, PLB, Not(ChangesDataBankRegister)),
|
operationPairBuilder3(PHB, Anything, PLB, Not(ChangesDataBankRegister), None),
|
||||||
operationPairBuilder3(INX, DoesntMatterWhatItDoesWith(State.N, State.Z), DEX, Not(ConcernsX) & Not(ReadsNOrZ)),
|
operationPairBuilder3(INX, DoesntMatterWhatItDoesWith(State.N, State.Z), DEX, Not(ConcernsX) & Not(ReadsNOrZ), None),
|
||||||
operationPairBuilder3(DEX, DoesntMatterWhatItDoesWith(State.N, State.Z), INX, Not(ConcernsX) & Not(ReadsNOrZ)),
|
operationPairBuilder3(DEX, DoesntMatterWhatItDoesWith(State.N, State.Z), INX, Not(ConcernsX) & Not(ReadsNOrZ), None),
|
||||||
operationPairBuilder3(INY, DoesntMatterWhatItDoesWith(State.N, State.Z), DEY, Not(ConcernsX) & Not(ReadsNOrZ)),
|
operationPairBuilder3(INY, DoesntMatterWhatItDoesWith(State.N, State.Z), DEY, Not(ConcernsX) & Not(ReadsNOrZ), None),
|
||||||
operationPairBuilder3(DEY, DoesntMatterWhatItDoesWith(State.N, State.Z), INY, Not(ConcernsX) & Not(ReadsNOrZ)),
|
operationPairBuilder3(DEY, DoesntMatterWhatItDoesWith(State.N, State.Z), INY, Not(ConcernsX) & Not(ReadsNOrZ), None),
|
||||||
|
operationPairBuilder4(
|
||||||
|
LDA, DoesntMatterWhatItDoesWith(State.N, State.Z),
|
||||||
|
Not(ConcernsA),
|
||||||
|
STA, DoesntMatterWhatItDoesWith(State.A)),
|
||||||
|
operationPairBuilder4(
|
||||||
|
LDX, DoesntMatterWhatItDoesWith(State.N, State.Z),
|
||||||
|
Not(ConcernsX),
|
||||||
|
STX, DoesntMatterWhatItDoesWith(State.X)),
|
||||||
|
operationPairBuilder4(
|
||||||
|
LDY, DoesntMatterWhatItDoesWith(State.N, State.Z),
|
||||||
|
Not(ConcernsY),
|
||||||
|
STY, DoesntMatterWhatItDoesWith(State.Y)),
|
||||||
|
operationPairBuilder4(
|
||||||
|
LAX, DoesntMatterWhatItDoesWith(State.N, State.Z),
|
||||||
|
Not(ConcernsX) & Not(ConcernsA),
|
||||||
|
STA, DoesntMatterWhatItDoesWith(State.A, State.X)),
|
||||||
|
operationPairBuilder4(
|
||||||
|
LAX, DoesntMatterWhatItDoesWith(State.N, State.Z),
|
||||||
|
Not(ConcernsX) & Not(ConcernsA),
|
||||||
|
STX, DoesntMatterWhatItDoesWith(State.A, State.X)),
|
||||||
|
operationPairBuilder4(
|
||||||
|
LAX, DoesntMatterWhatItDoesWith(State.N, State.Z),
|
||||||
|
Not(ConcernsX) & Not(ConcernsA),
|
||||||
|
SAX, DoesntMatterWhatItDoesWith(State.A, State.X)),
|
||||||
)
|
)
|
||||||
|
|
||||||
val PointlessStackStashing = new RuleBasedAssemblyOptimization("Pointless stack stashing",
|
val PointlessStackStashing = new RuleBasedAssemblyOptimization("Pointless stack stashing",
|
||||||
@ -762,27 +795,27 @@ object AlwaysGoodOptimizations {
|
|||||||
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
|
|
||||||
(HasOpcodeIn(Set(LDA, STA)) & HasAddrMode(Immediate) & MatchParameter(1)) ~
|
(HasOpcodeIn(Set(LDA, STA)) & HasAddrMode(Immediate) & MatchParameter(1)) ~
|
||||||
(Linear & Not(ChangesA)).* ~
|
(Linear & Not(ChangesA) & Not(HasOpcode(DISCARD_AF))).* ~
|
||||||
(Elidable & HasOpcode(LDA) & HasAddrMode(Immediate) & MatchParameter(1)) ~~> (_.init),
|
(Elidable & HasOpcode(LDA) & HasAddrMode(Immediate) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
(HasOpcodeIn(Set(LDX, STX)) & HasAddrMode(Immediate) & MatchParameter(1)) ~
|
(HasOpcodeIn(Set(LDX, STX)) & HasAddrMode(Immediate) & MatchParameter(1)) ~
|
||||||
(Linear & Not(ChangesX)).* ~
|
(Linear & Not(ChangesX) & Not(HasOpcode(DISCARD_XF))).* ~
|
||||||
(Elidable & HasOpcode(LDX) & HasAddrMode(Immediate) & MatchParameter(1)) ~~> (_.init),
|
(Elidable & HasOpcode(LDX) & HasAddrMode(Immediate) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
(HasOpcodeIn(Set(LDY, STY)) & HasAddrMode(Immediate) & MatchParameter(1)) ~
|
(HasOpcodeIn(Set(LDY, STY)) & HasAddrMode(Immediate) & MatchParameter(1)) ~
|
||||||
(Linear & Not(ChangesY)).* ~
|
(Linear & Not(ChangesY) & Not(HasOpcode(DISCARD_YF))).* ~
|
||||||
(Elidable & HasOpcode(LDY) & HasAddrMode(Immediate) & MatchParameter(1)) ~~> (_.init),
|
(Elidable & HasOpcode(LDY) & HasAddrMode(Immediate) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
(HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
(HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
(Linear & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
(Linear & Not(ChangesA) & Not(HasOpcode(DISCARD_AF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
||||||
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
(HasOpcodeIn(Set(LDX, STX)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
(HasOpcodeIn(Set(LDX, STX)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
(Linear & Not(ChangesX) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
(Linear & Not(ChangesX) & Not(HasOpcode(DISCARD_XF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
||||||
(Elidable & HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
(Elidable & HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
|
|
||||||
(HasOpcodeIn(Set(LDY, STY)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
(HasOpcodeIn(Set(LDY, STY)) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||||
(Linear & Not(ChangesY) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
(Linear & Not(ChangesY) & Not(HasOpcode(DISCARD_YF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
|
||||||
(Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
(Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -158,17 +158,17 @@ object LaterOptimizations {
|
|||||||
|
|
||||||
val DoubleLoadToTheSameRegister = new RuleBasedAssemblyOptimization("Double load to the same register",
|
val DoubleLoadToTheSameRegister = new RuleBasedAssemblyOptimization("Double load to the same register",
|
||||||
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
needsFlowInfo = FlowInfoRequirement.NoRequirement,
|
||||||
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDA, Not(ChangesA)),
|
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDA, Not(ChangesA) & Not(HasOpcode(DISCARD_AF))),
|
||||||
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDX, Not(ChangesX)),
|
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDX, Not(ChangesX) & Not(HasOpcode(DISCARD_XF))),
|
||||||
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDY, Not(ChangesY)),
|
TwoIdenticalLoadsWithNoFlagChangeInBetween(LDY, Not(ChangesY) & Not(HasOpcode(DISCARD_YF))),
|
||||||
TwoIdenticalLoadsWithNoFlagChangeInBetween(LAX, Not(ChangesA) & Not(ChangesX)),
|
TwoIdenticalLoadsWithNoFlagChangeInBetween(LAX, Not(ChangesA) & Not(ChangesX) & Not(HasOpcode(DISCARD_AF)) & Not(HasOpcode(DISCARD_XF))),
|
||||||
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDA, Not(ChangesA)),
|
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDA, Not(ChangesA) & Not(HasOpcode(DISCARD_AF))),
|
||||||
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDX, Not(ChangesX)),
|
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDX, Not(ChangesX) & Not(HasOpcode(DISCARD_XF))),
|
||||||
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDY, Not(ChangesY)),
|
TwoIdenticalImmediateLoadsWithNoFlagChangeInBetween(LDY, Not(ChangesY) & Not(HasOpcode(DISCARD_YF))),
|
||||||
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDA, Not(ChangesA)),
|
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDA, Not(ChangesA) & Not(HasOpcode(DISCARD_AF))),
|
||||||
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDX, Not(ChangesX)),
|
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDX, Not(ChangesX) & Not(HasOpcode(DISCARD_XF))),
|
||||||
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDY, Not(ChangesY)),
|
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LDY, Not(ChangesY) & Not(HasOpcode(DISCARD_YF))),
|
||||||
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LAX, Not(ChangesA) & Not(ChangesX)),
|
TwoIdenticalLoadsWhoseFlagsWillNotBeChecked(LAX, Not(ChangesA) & Not(ChangesX) & Not(HasOpcode(DISCARD_AF)) & Not(HasOpcode(DISCARD_XF))),
|
||||||
InterleavedLoads(LDA, STA),
|
InterleavedLoads(LDA, STA),
|
||||||
InterleavedLoads(LDX, STX),
|
InterleavedLoads(LDX, STX),
|
||||||
InterleavedLoads(LDY, STY),
|
InterleavedLoads(LDY, STY),
|
||||||
|
Loading…
Reference in New Issue
Block a user