*Greatly* reduced the amount of excess IR related to string literals
This commit is contained in:
parent
1f53fc5214
commit
83aefbae07
@ -16,6 +16,9 @@ func (this *generator) generateAssignment (assignment *entity.Assignment) (llvm.
|
|||||||
destination)
|
destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO possibly break generateAssignmentToDestination into several routines,
|
||||||
|
// each handling one destination type
|
||||||
|
|
||||||
// generateAssignmentToDestination performs type coercions if necessary, mainly
|
// generateAssignmentToDestination performs type coercions if necessary, mainly
|
||||||
// interface assignment. This should be called when the user is assigning a
|
// interface assignment. This should be called when the user is assigning a
|
||||||
// value to something via an assignment statement, a function/method call, or a
|
// value to something via an assignment statement, a function/method call, or a
|
||||||
@ -126,8 +129,28 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
|
|||||||
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
|
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// conversion from any type to pointer
|
||||||
|
case *entity.TypePointer:
|
||||||
|
// assignments
|
||||||
|
switch source := source.(type) {
|
||||||
|
// assignment from array literal to pointer
|
||||||
|
case *entity.LiteralArray:
|
||||||
|
if destinationSpecified {
|
||||||
|
_, err := this.generateLiteralArrayLoc(source, irDestLoc)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignment from string literal to pointer
|
||||||
|
case *entity.LiteralString:
|
||||||
|
if destinationSpecified {
|
||||||
|
_, err := this.generateLiteralStringLoc(source, irDestLoc)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// conversion from any type to slice
|
// conversion from any type to slice
|
||||||
case *entity.TypeSlice:
|
case *entity.TypeSlice:
|
||||||
|
// conversions
|
||||||
switch sourceTypeBase := analyzer.ReduceToBase(source.Type()).(type) {
|
switch sourceTypeBase := analyzer.ReduceToBase(source.Type()).(type) {
|
||||||
// conversion from array to slice
|
// conversion from array to slice
|
||||||
case *entity.TypeArray:
|
case *entity.TypeArray:
|
||||||
@ -150,9 +173,27 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
|
|||||||
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
|
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assignments
|
||||||
|
switch source := source.(type) {
|
||||||
|
// assignment from array literal to slice
|
||||||
|
case *entity.LiteralArray:
|
||||||
|
if destinationSpecified {
|
||||||
|
_, err := this.generateLiteralArrayLoc(source, irDestLoc)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignment from string literal to slice
|
||||||
|
case *entity.LiteralString:
|
||||||
|
if destinationSpecified {
|
||||||
|
_, err := this.generateLiteralStringLoc(source, irDestLoc)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// conversion from any type to array
|
// conversion from any type to array
|
||||||
case *entity.TypeArray:
|
case *entity.TypeArray:
|
||||||
|
// assignments
|
||||||
switch source := source.(type) {
|
switch source := source.(type) {
|
||||||
// assignment from array literal to array
|
// assignment from array literal to array
|
||||||
case *entity.LiteralArray:
|
case *entity.LiteralArray:
|
||||||
@ -160,10 +201,18 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
|
|||||||
_, err := this.generateLiteralArrayLoc(source, irDestLoc)
|
_, err := this.generateLiteralArrayLoc(source, irDestLoc)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assignment from string literal to array
|
||||||
|
case *entity.LiteralString:
|
||||||
|
if destinationSpecified {
|
||||||
|
_, err := this.generateLiteralStringLoc(source, irDestLoc)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// conversion from any type to struct
|
// conversion from any type to struct
|
||||||
case *entity.TypeStruct:
|
case *entity.TypeStruct:
|
||||||
|
// assignments
|
||||||
switch source := source.(type) {
|
switch source := source.(type) {
|
||||||
// assignment from struct literal to struct
|
// assignment from struct literal to struct
|
||||||
case *entity.LiteralStruct:
|
case *entity.LiteralStruct:
|
||||||
|
@ -223,7 +223,7 @@ func (this *generator) generateExpressionLoc (expression entity.Expression) (llv
|
|||||||
case *entity.LiteralArray:
|
case *entity.LiteralArray:
|
||||||
return this.generateLiteralArrayLoc(expression, nil)
|
return this.generateLiteralArrayLoc(expression, nil)
|
||||||
case *entity.LiteralString:
|
case *entity.LiteralString:
|
||||||
return this.generateLiteralStringLoc(expression)
|
return this.generateLiteralStringLoc(expression, nil)
|
||||||
case *entity.LiteralStruct:
|
case *entity.LiteralStruct:
|
||||||
return this.generateLiteralStructLoc(expression, nil)
|
return this.generateLiteralStructLoc(expression, nil)
|
||||||
|
|
||||||
|
@ -81,55 +81,74 @@ func (this *generator) generateLiteralArrayLoc (literal *entity.LiteralArray, ir
|
|||||||
case *entity.TypeSlice:
|
case *entity.TypeSlice:
|
||||||
destDataFieldLoc := this.getSliceDataFieldLoc(irDestLoc, irDestType)
|
destDataFieldLoc := this.getSliceDataFieldLoc(irDestLoc, irDestType)
|
||||||
destLengthFieldLoc := this.getSliceLengthFieldLoc(irDestLoc, irDestType)
|
destLengthFieldLoc := this.getSliceLengthFieldLoc(irDestLoc, irDestType)
|
||||||
destData, err := makeData(base.Element)
|
destDataLoc, err := makeData(base.Element)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
err = populateData(base.Element, destData, -1)
|
err = populateData(base.Element, destDataLoc, -1)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
this.blockManager.NewStore(destData, destDataFieldLoc)
|
this.blockManager.NewStore(destDataLoc, destDataFieldLoc)
|
||||||
this.blockManager.NewStore (
|
this.blockManager.NewStore (
|
||||||
llvm.NewConstInt(llvm.I32, int64(len(literal.Elements))),
|
llvm.NewConstInt(llvm.I32, int64(len(literal.Elements))),
|
||||||
destLengthFieldLoc)
|
destLengthFieldLoc)
|
||||||
|
|
||||||
case *entity.TypePointer:
|
case *entity.TypePointer:
|
||||||
destData, err := makeData(base.Referenced)
|
destDataLoc, err := makeData(base.Referenced)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
err = populateData(base.Referenced, destData, -1)
|
err = populateData(base.Referenced, destDataLoc, -1)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
this.blockManager.NewStore(destData, irDestLoc)
|
this.blockManager.NewStore(destDataLoc, irDestLoc)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(fmt.Sprintln("array can't be used as ", base))
|
return nil, errors.New(fmt.Sprintln("array can't be used as ", base))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !destinationSpecified {
|
if destinationSpecified {
|
||||||
return irDestLoc, nil
|
|
||||||
} else {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return irDestLoc, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString) (llvm.Value, error) {
|
func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString, irDestLoc llvm.Value) (llvm.Value, error) {
|
||||||
// TODO support irDestLoc here, make use of it in generateAssignmentToDestination
|
// TODO support irDestLoc here, make use of it in generateAssignmentToDestination
|
||||||
base := analyzer.ReduceToBase(literal.Type())
|
destinationSpecified := irDestLoc != nil
|
||||||
|
|
||||||
makeData := func (anyElementType entity.Type, cstring bool) (llvm.Value, int64, error) {
|
makeDataType := func (anyElementType entity.Type, cstring bool) (llvm.Type, int, error) {
|
||||||
elementType, ok := analyzer.ReduceToBase(anyElementType).(*entity.TypeInt)
|
elementType, ok := analyzer.ReduceToBase(anyElementType).(*entity.TypeInt)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, 0, errors.New(fmt.Sprintln("string can't be used as ", base))
|
return nil, 0, errors.New(fmt.Sprintln (
|
||||||
|
"string can't be used with element type",
|
||||||
|
anyElementType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var length int; switch {
|
||||||
|
case elementType.Width >= 32: length = len(literal.ValueUTF32)
|
||||||
|
case elementType.Width >= 16: length = len(literal.ValueUTF16)
|
||||||
|
default: length = len(literal.ValueUTF8)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cstring { length += 1 }
|
||||||
|
irDataType, err := this.generateType(&entity.TypeArray {
|
||||||
|
Element: elementType,
|
||||||
|
Length: length,
|
||||||
|
})
|
||||||
|
return irDataType, length, err
|
||||||
|
}
|
||||||
|
populateData := func (anyElementType entity.Type, irDestLoc llvm.Value, max int, cstring bool) (error) {
|
||||||
|
elementType, ok := analyzer.ReduceToBase(anyElementType).(*entity.TypeInt)
|
||||||
|
if !ok {
|
||||||
|
return errors.New(fmt.Sprintln (
|
||||||
|
"string can't be used with element type",
|
||||||
|
anyElementType))
|
||||||
|
}
|
||||||
|
|
||||||
|
irArrayType, length, err := makeDataType(elementType, cstring)
|
||||||
|
if err != nil { return err }
|
||||||
|
|
||||||
|
// TODO if the string literal is undersized, populate the rest with
|
||||||
|
// zeros
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case elementType.Width >= 32:
|
case elementType.Width >= 32:
|
||||||
length := len(literal.ValueUTF32)
|
|
||||||
if cstring { length += 1 }
|
|
||||||
|
|
||||||
irArrayType, err := this.generateType(&entity.TypeArray {
|
|
||||||
Element: elementType,
|
|
||||||
Length: length,
|
|
||||||
})
|
|
||||||
if err != nil { return nil, 0, err }
|
|
||||||
array := this.blockManager.newAllocaFront(irArrayType)
|
|
||||||
|
|
||||||
for index := 0; index < length; index ++ {
|
for index := 0; index < length; index ++ {
|
||||||
var element int64
|
var element int64
|
||||||
if index >= len(literal.ValueUTF32) {
|
if index >= len(literal.ValueUTF32) {
|
||||||
@ -139,26 +158,16 @@ func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString)
|
|||||||
}
|
}
|
||||||
|
|
||||||
elementPointer := this.blockManager.NewGetElementPtr (
|
elementPointer := this.blockManager.NewGetElementPtr (
|
||||||
irArrayType, array,
|
irArrayType, irDestLoc,
|
||||||
llvm.NewConstInt(llvm.I32, 0),
|
llvm.NewConstInt(llvm.I32, 0),
|
||||||
llvm.NewConstInt(llvm.I32, int64(index)))
|
llvm.NewConstInt(llvm.I32, int64(index)))
|
||||||
this.blockManager.NewStore (
|
this.blockManager.NewStore (
|
||||||
llvm.NewConstInt(llvm.I32, element),
|
llvm.NewConstInt(llvm.I32, element),
|
||||||
elementPointer)
|
elementPointer)
|
||||||
}
|
}
|
||||||
return array, int64(length), nil
|
return nil
|
||||||
|
|
||||||
case elementType.Width >= 16:
|
case elementType.Width >= 16:
|
||||||
length := len(literal.ValueUTF16)
|
|
||||||
if cstring { length += 1 }
|
|
||||||
|
|
||||||
irArrayType, err := this.generateType(&entity.TypeArray {
|
|
||||||
Element: elementType,
|
|
||||||
Length: length,
|
|
||||||
})
|
|
||||||
if err != nil { return nil, 0, err }
|
|
||||||
array := this.blockManager.newAllocaFront(irArrayType)
|
|
||||||
|
|
||||||
for index := 0; index < length; index ++ {
|
for index := 0; index < length; index ++ {
|
||||||
var element int64
|
var element int64
|
||||||
if index >= len(literal.ValueUTF16) {
|
if index >= len(literal.ValueUTF16) {
|
||||||
@ -168,26 +177,16 @@ func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString)
|
|||||||
}
|
}
|
||||||
|
|
||||||
elementPointer := this.blockManager.NewGetElementPtr (
|
elementPointer := this.blockManager.NewGetElementPtr (
|
||||||
irArrayType, array,
|
irArrayType, irDestLoc,
|
||||||
llvm.NewConstInt(llvm.I32, 0),
|
llvm.NewConstInt(llvm.I32, 0),
|
||||||
llvm.NewConstInt(llvm.I32, int64(index)))
|
llvm.NewConstInt(llvm.I32, int64(index)))
|
||||||
this.blockManager.NewStore (
|
this.blockManager.NewStore (
|
||||||
llvm.NewConstInt(llvm.I16, element),
|
llvm.NewConstInt(llvm.I16, element),
|
||||||
elementPointer)
|
elementPointer)
|
||||||
}
|
}
|
||||||
return array, int64(length), nil
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
length := len(literal.ValueUTF8)
|
|
||||||
if cstring { length += 1 }
|
|
||||||
|
|
||||||
irArrayType, err := this.generateType(&entity.TypeArray {
|
|
||||||
Element: elementType,
|
|
||||||
Length: length,
|
|
||||||
})
|
|
||||||
if err != nil { return nil, 0, err }
|
|
||||||
array := this.blockManager.newAllocaFront(irArrayType)
|
|
||||||
|
|
||||||
for index := 0; index < length; index ++ {
|
for index := 0; index < length; index ++ {
|
||||||
var element int64
|
var element int64
|
||||||
if index >= len(literal.ValueUTF8) {
|
if index >= len(literal.ValueUTF8) {
|
||||||
@ -197,23 +196,27 @@ func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString)
|
|||||||
}
|
}
|
||||||
|
|
||||||
elementPointer := this.blockManager.NewGetElementPtr (
|
elementPointer := this.blockManager.NewGetElementPtr (
|
||||||
irArrayType, array,
|
irArrayType, irDestLoc,
|
||||||
llvm.NewConstInt(llvm.I32, 0),
|
llvm.NewConstInt(llvm.I32, 0),
|
||||||
llvm.NewConstInt(llvm.I32, int64(index)))
|
llvm.NewConstInt(llvm.I32, int64(index)))
|
||||||
this.blockManager.NewStore (
|
this.blockManager.NewStore (
|
||||||
llvm.NewConstInt(llvm.I8, element),
|
llvm.NewConstInt(llvm.I8, element),
|
||||||
elementPointer)
|
elementPointer)
|
||||||
}
|
}
|
||||||
return array, int64(length), nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
irDestType, err := this.generateType(literal.Type())
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
if !destinationSpecified {
|
||||||
|
irDestLoc = this.blockManager.newAllocaFront(irDestType)
|
||||||
|
}
|
||||||
|
|
||||||
|
base := analyzer.ReduceToBase(literal.Type())
|
||||||
switch base := base.(type) {
|
switch base := base.(type) {
|
||||||
case *entity.TypeInt:
|
case *entity.TypeInt:
|
||||||
var value llvm.Value
|
var value llvm.Value; switch {
|
||||||
irType, err := this.generateType(literal.Type())
|
|
||||||
if err != nil { return nil, err }
|
|
||||||
switch {
|
|
||||||
case base.Width >= 32:
|
case base.Width >= 32:
|
||||||
value = llvm.NewConstInt(llvm.I32, int64(literal.ValueUTF32[0]))
|
value = llvm.NewConstInt(llvm.I32, int64(literal.ValueUTF32[0]))
|
||||||
case base.Width >= 16:
|
case base.Width >= 16:
|
||||||
@ -221,31 +224,46 @@ func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString)
|
|||||||
default:
|
default:
|
||||||
value = llvm.NewConstInt(llvm.I8, int64(literal.ValueUTF8[0]))
|
value = llvm.NewConstInt(llvm.I8, int64(literal.ValueUTF8[0]))
|
||||||
}
|
}
|
||||||
char := this.blockManager.newAllocaFront(irType)
|
this.blockManager.NewStore(value, irDestLoc)
|
||||||
this.blockManager.NewStore(value, char)
|
|
||||||
return char, nil
|
|
||||||
|
|
||||||
case *entity.TypeArray:
|
case *entity.TypeArray:
|
||||||
value, _, err := makeData(base.Element, false)
|
err := populateData(base.Element, irDestLoc, base.Length, false)
|
||||||
return value, err
|
if err != nil { return nil, err }
|
||||||
|
|
||||||
case *entity.TypeSlice:
|
case *entity.TypeSlice:
|
||||||
array, length, err := makeData(base.Element, false)
|
destDataFieldLoc := this.getSliceDataFieldLoc(irDestLoc, irDestType)
|
||||||
|
destLengthFieldLoc := this.getSliceLengthFieldLoc(irDestLoc, irDestType)
|
||||||
|
|
||||||
|
irDataType, length, err := makeDataType(base.Element, true)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
return this.generateSliceDefinedLength(literal.Type(), array, length)
|
destDataLoc := this.blockManager.newAllocaFront(irDataType)
|
||||||
|
|
||||||
|
err = populateData(base.Element, destDataLoc, -1, false)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
this.blockManager.NewStore(destDataLoc, destDataFieldLoc)
|
||||||
|
this.blockManager.NewStore (
|
||||||
|
llvm.NewConstInt(llvm.I32, int64(length)),
|
||||||
|
destLengthFieldLoc)
|
||||||
|
|
||||||
case *entity.TypePointer:
|
case *entity.TypePointer:
|
||||||
array, _, err := makeData(base.Referenced, true)
|
irDataType, _, err := makeDataType(base.Referenced, true)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
destDataLoc := this.blockManager.newAllocaFront(irDataType)
|
||||||
|
err = populateData(base.Referenced, destDataLoc, -1, false)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
return this.valueToLocation(array), nil
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(fmt.Sprintln("string can't be used as ", base))
|
return nil, errors.New(fmt.Sprintln("string can't be used as ", base))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if destinationSpecified {
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return irDestLoc, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *generator) generateLiteralStructLoc (literal *entity.LiteralStruct, irDestLoc llvm.Value) (llvm.Value, error) {
|
func (this *generator) generateLiteralStructLoc (literal *entity.LiteralStruct, irDestLoc llvm.Value) (llvm.Value, error) {
|
||||||
// TODO support irDestLoc here, make use of it in generateAssignmentToDestination
|
|
||||||
destinationSpecified := irDestLoc != nil
|
destinationSpecified := irDestLoc != nil
|
||||||
|
|
||||||
base, ok := analyzer.ReduceToBase(literal.Type()).(*entity.TypeStruct)
|
base, ok := analyzer.ReduceToBase(literal.Type()).(*entity.TypeStruct)
|
||||||
@ -278,10 +296,10 @@ func (this *generator) generateLiteralStructLoc (literal *entity.LiteralStruct,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !destinationSpecified {
|
if destinationSpecified {
|
||||||
return irDestLoc, nil
|
|
||||||
} else {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return irDestLoc, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +245,46 @@ testString (test,
|
|||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLiteralString (test *testing.T) {
|
||||||
|
testString (test,
|
||||||
|
`%Index = type i64
|
||||||
|
%String = type { ptr, %Index }
|
||||||
|
%Byte = type i8
|
||||||
|
define void @main() {
|
||||||
|
0:
|
||||||
|
%1 = alloca %String
|
||||||
|
%2 = getelementptr %String, ptr %1, i32 0, i32 0
|
||||||
|
%3 = getelementptr %String, ptr %1, i32 0, i32 1
|
||||||
|
%4 = alloca [3 x i8]
|
||||||
|
%5 = getelementptr [2 x i8], ptr %4, i32 0, i32 0
|
||||||
|
store i8 115, ptr %5
|
||||||
|
%6 = getelementptr [2 x i8], ptr %4, i32 0, i32 1
|
||||||
|
store i8 65, ptr %6
|
||||||
|
store ptr %4, ptr %2
|
||||||
|
store i32 3, ptr %3
|
||||||
|
%7 = alloca ptr
|
||||||
|
%8 = alloca [3 x i8]
|
||||||
|
%9 = getelementptr [2 x i8], ptr %8, i32 0, i32 0
|
||||||
|
store i8 115, ptr %9
|
||||||
|
%10 = getelementptr [2 x i8], ptr %8, i32 0, i32 1
|
||||||
|
store i8 66, ptr %10
|
||||||
|
%11 = alloca [5 x %Byte]
|
||||||
|
%12 = getelementptr [2 x i8], ptr %11, i32 0, i32 0
|
||||||
|
store i8 115, ptr %12
|
||||||
|
%13 = getelementptr [2 x i8], ptr %11, i32 0, i32 1
|
||||||
|
store i8 67, ptr %13
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
`
|
||||||
|
[main] 'main' = {
|
||||||
|
a: String = 'sA'
|
||||||
|
b: *Byte = 'sB'
|
||||||
|
c: 5:Byte = 'sC'
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTypedStructInstantiation (test *testing.T) {
|
func TestTypedStructInstantiation (test *testing.T) {
|
||||||
testString (test,
|
testString (test,
|
||||||
`%A = type { i64 }
|
`%A = type { i64 }
|
||||||
|
Loading…
Reference in New Issue
Block a user