Improve code related to object layout (#2446)
This commit is contained in:
committed by
Nikolay Igotti
parent
ca9c4cde51
commit
328413337b
@@ -44,16 +44,16 @@ inline fun <T : Any, R> T.usePinned(block: (Pinned<T>) -> R): R {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Pinned<ByteArray>.addressOf(index: Int): CPointer<ByteVar> = this.addressOfElement(index)
|
fun Pinned<ByteArray>.addressOf(index: Int): CPointer<ByteVar> = this.get().addressOfElement(index)
|
||||||
fun ByteArray.refTo(index: Int): CValuesRef<ByteVar> = this.usingPinned { addressOf(index) }
|
fun ByteArray.refTo(index: Int): CValuesRef<ByteVar> = this.usingPinned { addressOf(index) }
|
||||||
|
|
||||||
fun Pinned<ShortArray>.addressOf(index: Int): CPointer<ShortVar> = this.addressOfElement(index)
|
fun Pinned<ShortArray>.addressOf(index: Int): CPointer<ShortVar> = this.get().addressOfElement(index)
|
||||||
fun ShortArray.refTo(index: Int): CValuesRef<ShortVar> = this.usingPinned { addressOf(index) }
|
fun ShortArray.refTo(index: Int): CValuesRef<ShortVar> = this.usingPinned { addressOf(index) }
|
||||||
|
|
||||||
fun Pinned<IntArray>.addressOf(index: Int): CPointer<IntVar> = this.addressOfElement(index)
|
fun Pinned<IntArray>.addressOf(index: Int): CPointer<IntVar> = this.get().addressOfElement(index)
|
||||||
fun IntArray.refTo(index: Int): CValuesRef<IntVar> = this.usingPinned { addressOf(index) }
|
fun IntArray.refTo(index: Int): CValuesRef<IntVar> = this.usingPinned { addressOf(index) }
|
||||||
|
|
||||||
fun Pinned<LongArray>.addressOf(index: Int): CPointer<LongVar> = this.addressOfElement(index)
|
fun Pinned<LongArray>.addressOf(index: Int): CPointer<LongVar> = this.get().addressOfElement(index)
|
||||||
fun LongArray.refTo(index: Int): CValuesRef<LongVar> = this.usingPinned { addressOf(index) }
|
fun LongArray.refTo(index: Int): CValuesRef<LongVar> = this.usingPinned { addressOf(index) }
|
||||||
|
|
||||||
// TODO: pinning of unsigned arrays involves boxing as they are inline classes wrapping signed arrays.
|
// TODO: pinning of unsigned arrays involves boxing as they are inline classes wrapping signed arrays.
|
||||||
@@ -69,10 +69,10 @@ fun UIntArray.refTo(index: Int): CValuesRef<UIntVar> = this.usingPinned { addres
|
|||||||
fun Pinned<ULongArray>.addressOf(index: Int): CPointer<ULongVar> = this.get().addressOfElement(index)
|
fun Pinned<ULongArray>.addressOf(index: Int): CPointer<ULongVar> = this.get().addressOfElement(index)
|
||||||
fun ULongArray.refTo(index: Int): CValuesRef<ULongVar> = this.usingPinned { addressOf(index) }
|
fun ULongArray.refTo(index: Int): CValuesRef<ULongVar> = this.usingPinned { addressOf(index) }
|
||||||
|
|
||||||
fun Pinned<FloatArray>.addressOf(index: Int): CPointer<FloatVar> = this.addressOfElement(index)
|
fun Pinned<FloatArray>.addressOf(index: Int): CPointer<FloatVar> = this.get().addressOfElement(index)
|
||||||
fun FloatArray.refTo(index: Int): CValuesRef<FloatVar> = this.usingPinned { addressOf(index) }
|
fun FloatArray.refTo(index: Int): CValuesRef<FloatVar> = this.usingPinned { addressOf(index) }
|
||||||
|
|
||||||
fun Pinned<DoubleArray>.addressOf(index: Int): CPointer<DoubleVar> = this.addressOfElement(index)
|
fun Pinned<DoubleArray>.addressOf(index: Int): CPointer<DoubleVar> = this.get().addressOfElement(index)
|
||||||
fun DoubleArray.refTo(index: Int): CValuesRef<DoubleVar> = this.usingPinned { addressOf(index) }
|
fun DoubleArray.refTo(index: Int): CValuesRef<DoubleVar> = this.usingPinned { addressOf(index) }
|
||||||
|
|
||||||
private inline fun <T : Any, P : CPointed> T.usingPinned(
|
private inline fun <T : Any, P : CPointed> T.usingPinned(
|
||||||
@@ -86,21 +86,32 @@ private inline fun <T : Any, P : CPointed> T.usingPinned(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SymbolName("Kotlin_Arrays_getAddressOfElement")
|
@SymbolName("Kotlin_Arrays_getByteArrayAddressOfElement")
|
||||||
private external fun getAddressOfElement(array: Any, index: Int): COpaquePointer
|
private external fun ByteArray.addressOfElement(index: Int): CPointer<ByteVar>
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@SymbolName("Kotlin_Arrays_getShortArrayAddressOfElement")
|
||||||
private inline fun <P : CVariable> Pinned<*>.addressOfElement(index: Int): CPointer<P> =
|
private external fun ShortArray.addressOfElement(index: Int): CPointer<ShortVar>
|
||||||
getAddressOfElement(this.get(), index).reinterpret()
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_Arrays_getAddressOfElement")
|
@SymbolName("Kotlin_Arrays_getIntArrayAddressOfElement")
|
||||||
|
private external fun IntArray.addressOfElement(index: Int): CPointer<IntVar>
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Arrays_getLongArrayAddressOfElement")
|
||||||
|
private external fun LongArray.addressOfElement(index: Int): CPointer<LongVar>
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Arrays_getByteArrayAddressOfElement")
|
||||||
private external fun UByteArray.addressOfElement(index: Int): CPointer<UByteVar>
|
private external fun UByteArray.addressOfElement(index: Int): CPointer<UByteVar>
|
||||||
|
|
||||||
@SymbolName("Kotlin_Arrays_getAddressOfElement")
|
@SymbolName("Kotlin_Arrays_getShortArrayAddressOfElement")
|
||||||
private external fun UShortArray.addressOfElement(index: Int): CPointer<UShortVar>
|
private external fun UShortArray.addressOfElement(index: Int): CPointer<UShortVar>
|
||||||
|
|
||||||
@SymbolName("Kotlin_Arrays_getAddressOfElement")
|
@SymbolName("Kotlin_Arrays_getIntArrayAddressOfElement")
|
||||||
private external fun UIntArray.addressOfElement(index: Int): CPointer<UIntVar>
|
private external fun UIntArray.addressOfElement(index: Int): CPointer<UIntVar>
|
||||||
|
|
||||||
@SymbolName("Kotlin_Arrays_getAddressOfElement")
|
@SymbolName("Kotlin_Arrays_getLongArrayAddressOfElement")
|
||||||
private external fun ULongArray.addressOfElement(index: Int): CPointer<ULongVar>
|
private external fun ULongArray.addressOfElement(index: Int): CPointer<ULongVar>
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Arrays_getFloatArrayAddressOfElement")
|
||||||
|
private external fun FloatArray.addressOfElement(index: Int): CPointer<FloatVar>
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Arrays_getDoubleArrayAddressOfElement")
|
||||||
|
private external fun DoubleArray.addressOfElement(index: Int): CPointer<DoubleVar>
|
||||||
|
|||||||
+1
-1
@@ -1592,7 +1592,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
|
|
||||||
functionGenerationContext.gep(objCPtr, bodyOffset)
|
functionGenerationContext.gep(objCPtr, bodyOffset)
|
||||||
} else {
|
} else {
|
||||||
LLVMBuildGEP(functionGenerationContext.builder, objectPtr, cValuesOf(kImmOne), 1, "")!!
|
objectPtr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -133,7 +133,8 @@ private fun Context.getDeclaredFields(classDescriptor: ClassDescriptor): List<Ir
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun ContextUtils.createClassBodyType(name: String, fields: List<IrField>): LLVMTypeRef {
|
private fun ContextUtils.createClassBodyType(name: String, fields: List<IrField>): LLVMTypeRef {
|
||||||
val fieldTypes = fields.map { getLLVMType(it.type) }
|
val fieldTypes = listOf(runtime.objHeaderType) + fields.map { getLLVMType(it.type) }
|
||||||
|
// TODO: consider adding synthetic ObjHeader field to Any.
|
||||||
|
|
||||||
val classType = LLVMStructCreateNamed(LLVMGetModuleContext(context.llvmModule), name)!!
|
val classType = LLVMStructCreateNamed(LLVMGetModuleContext(context.llvmModule), name)!!
|
||||||
|
|
||||||
@@ -369,7 +370,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
|||||||
error(containingClass.descriptor.toString())
|
error(containingClass.descriptor.toString())
|
||||||
val allFields = classDeclarations.fields
|
val allFields = classDeclarations.fields
|
||||||
this.fields[descriptor] = FieldLlvmDeclarations(
|
this.fields[descriptor] = FieldLlvmDeclarations(
|
||||||
allFields.indexOf(descriptor),
|
allFields.indexOf(descriptor) + 1, // First field is ObjHeader.
|
||||||
classDeclarations.bodyType
|
classDeclarations.bodyType
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+3
-3
@@ -289,9 +289,9 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
NullPointer(int32Type), NullPointer(int8Type), NullPointer(kInt8Ptr))
|
NullPointer(int32Type), NullPointer(int8Type), NullPointer(kInt8Ptr))
|
||||||
} else {
|
} else {
|
||||||
data class FieldRecord(val offset: Int, val type: Int, val name: String)
|
data class FieldRecord(val offset: Int, val type: Int, val name: String)
|
||||||
val fields = getStructElements(bodyType).mapIndexedNotNull { index, type ->
|
val fields = getStructElements(bodyType).drop(1).mapIndexedNotNull { index, type ->
|
||||||
FieldRecord(
|
FieldRecord(
|
||||||
LLVMOffsetOfElement(llvmTargetData, bodyType, index).toInt(),
|
LLVMOffsetOfElement(llvmTargetData, bodyType, index + 1).toInt(),
|
||||||
mapRuntimeType(type),
|
mapRuntimeType(type),
|
||||||
llvmDeclarations.fields[index].name.asString())
|
llvmDeclarations.fields[index].name.asString())
|
||||||
}
|
}
|
||||||
@@ -316,7 +316,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
): ConstPointer {
|
): ConstPointer {
|
||||||
assert(descriptor.isInterface)
|
assert(descriptor.isInterface)
|
||||||
|
|
||||||
val size = 0
|
val size = LLVMStoreSizeOfType(llvmTargetData, kObjHeader).toInt()
|
||||||
|
|
||||||
val superClass = context.ir.symbols.any.owner
|
val superClass = context.ir.symbols.any.owner
|
||||||
|
|
||||||
|
|||||||
+8
-14
@@ -71,24 +71,20 @@ internal fun StaticData.createConstKotlinArray(arrayClass: IrClass, elements: Li
|
|||||||
return createRef(objHeaderPtr)
|
return createRef(objHeaderPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun StaticData.createConstKotlinObject(type: IrClass, body: ConstValue): ConstPointer {
|
internal fun StaticData.createConstKotlinObject(type: IrClass, vararg fields: ConstValue): ConstPointer {
|
||||||
val typeInfo = type.typeInfoPtr
|
val typeInfo = type.typeInfoPtr
|
||||||
|
|
||||||
val compositeType = structType(runtime.objHeaderType, body.llvmType)
|
|
||||||
|
|
||||||
val global = this.createGlobal(compositeType, "")
|
|
||||||
|
|
||||||
val objHeaderPtr = global.pointer.getElementPtr(0)
|
|
||||||
val objHeader = objHeader(typeInfo)
|
val objHeader = objHeader(typeInfo)
|
||||||
|
|
||||||
global.setInitializer(Struct(compositeType, objHeader, body))
|
val global = this.placeGlobal("", Struct(objHeader, *fields))
|
||||||
global.setConstant(true)
|
global.setConstant(true)
|
||||||
|
|
||||||
|
val objHeaderPtr = global.pointer.getElementPtr(0)
|
||||||
|
|
||||||
return createRef(objHeaderPtr)
|
return createRef(objHeaderPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun StaticData.createInitializer(type: IrClass, body: ConstValue): ConstValue =
|
internal fun StaticData.createInitializer(type: IrClass, vararg fields: ConstValue): ConstValue =
|
||||||
Struct(objHeader(type.typeInfoPtr), body)
|
Struct(objHeader(type.typeInfoPtr), *fields)
|
||||||
|
|
||||||
private fun StaticData.getArrayListClass(): ClassDescriptor {
|
private fun StaticData.getArrayListClass(): ClassDescriptor {
|
||||||
val module = context.irModule!!.descriptor
|
val module = context.irModule!!.descriptor
|
||||||
@@ -123,14 +119,12 @@ internal fun StaticData.createConstArrayList(array: ConstPointer, length: Int):
|
|||||||
sorted.put(fqName, arrayListFields[fqName]!!)
|
sorted.put(fqName, arrayListFields[fqName]!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
val body = Struct(*(sorted.values.toTypedArray()))
|
return createConstKotlinObject(arrayListClass, *sorted.values.toTypedArray())
|
||||||
|
|
||||||
return createConstKotlinObject(arrayListClass, body)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun StaticData.createUniqueInstance(
|
internal fun StaticData.createUniqueInstance(
|
||||||
kind: UniqueKind, bodyType: LLVMTypeRef, typeInfo: ConstPointer): ConstPointer {
|
kind: UniqueKind, bodyType: LLVMTypeRef, typeInfo: ConstPointer): ConstPointer {
|
||||||
assert (getStructElements(bodyType).isEmpty())
|
assert (getStructElements(bodyType).size == 1) // ObjHeader only.
|
||||||
val objHeader = when (kind) {
|
val objHeader = when (kind) {
|
||||||
UniqueKind.UNIT -> objHeader(typeInfo)
|
UniqueKind.UNIT -> objHeader(typeInfo)
|
||||||
UniqueKind.EMPTY_ARRAY -> arrayHeader(typeInfo, 0)
|
UniqueKind.EMPTY_ARRAY -> arrayHeader(typeInfo, 0)
|
||||||
|
|||||||
@@ -489,13 +489,58 @@ KNativePtr Kotlin_ImmutableBlob_asCPointerImpl(KRef thiz, KInt offset) {
|
|||||||
return PrimitiveArrayAddressOfElementAt<KByte>(array, offset);
|
return PrimitiveArrayAddressOfElementAt<KByte>(array, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
KNativePtr Kotlin_Arrays_getAddressOfElement(KRef thiz, KInt index) {
|
KNativePtr Kotlin_Arrays_getByteArrayAddressOfElement(KRef thiz, KInt index) {
|
||||||
ArrayHeader* array = thiz->array();
|
ArrayHeader* array = thiz->array();
|
||||||
if (index < 0 || index >= array->count_) {
|
if (index < 0 || index >= array->count_) {
|
||||||
ThrowArrayIndexOutOfBoundsException();
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return AddressOfElementAt(array, index);
|
return AddressOfElementAt<KByte>(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
KNativePtr Kotlin_Arrays_getShortArrayAddressOfElement(KRef thiz, KInt index) {
|
||||||
|
ArrayHeader* array = thiz->array();
|
||||||
|
if (index < 0 || index >= array->count_) {
|
||||||
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddressOfElementAt<KShort>(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
KNativePtr Kotlin_Arrays_getIntArrayAddressOfElement(KRef thiz, KInt index) {
|
||||||
|
ArrayHeader* array = thiz->array();
|
||||||
|
if (index < 0 || index >= array->count_) {
|
||||||
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddressOfElementAt<KInt>(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
KNativePtr Kotlin_Arrays_getLongArrayAddressOfElement(KRef thiz, KInt index) {
|
||||||
|
ArrayHeader* array = thiz->array();
|
||||||
|
if (index < 0 || index >= array->count_) {
|
||||||
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddressOfElementAt<KLong>(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
KNativePtr Kotlin_Arrays_getFloatArrayAddressOfElement(KRef thiz, KInt index) {
|
||||||
|
ArrayHeader* array = thiz->array();
|
||||||
|
if (index < 0 || index >= array->count_) {
|
||||||
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddressOfElementAt<KFloat>(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
KNativePtr Kotlin_Arrays_getDoubleArrayAddressOfElement(KRef thiz, KInt index) {
|
||||||
|
ArrayHeader* array = thiz->array();
|
||||||
|
if (index < 0 || index >= array->count_) {
|
||||||
|
ThrowArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddressOfElementAt<KDouble>(array, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -23,37 +23,48 @@
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct AtomicReferenceLayout {
|
struct AtomicReferenceLayout {
|
||||||
|
ObjHeader header;
|
||||||
KRef value_;
|
KRef value_;
|
||||||
KInt lock_;
|
KInt lock_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T> struct AtomicPrimitive {
|
||||||
|
ObjHeader header;
|
||||||
|
volatile T value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> inline volatile T* getValueLocation(KRef thiz) {
|
||||||
|
AtomicPrimitive<T>* atomic = reinterpret_cast<AtomicPrimitive<T>*>(thiz);
|
||||||
|
return &atomic->value_;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> void setImpl(KRef thiz, T value) {
|
template <typename T> void setImpl(KRef thiz, T value) {
|
||||||
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
volatile T* location = getValueLocation<T>(thiz);
|
||||||
atomicSet(location, value);
|
atomicSet(location, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> T getImpl(KRef thiz) {
|
template <typename T> T getImpl(KRef thiz) {
|
||||||
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
volatile T* location = getValueLocation<T>(thiz);
|
||||||
return atomicGet(location);
|
return atomicGet(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> T addAndGetImpl(KRef thiz, T delta) {
|
template <typename T> T addAndGetImpl(KRef thiz, T delta) {
|
||||||
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
volatile T* location = getValueLocation<T>(thiz);
|
||||||
return atomicAdd(location, delta);
|
return atomicAdd(location, delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> T compareAndSwapImpl(KRef thiz, T expectedValue, T newValue) {
|
template <typename T> T compareAndSwapImpl(KRef thiz, T expectedValue, T newValue) {
|
||||||
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
volatile T* location = getValueLocation<T>(thiz);
|
||||||
return compareAndSwap(location, expectedValue, newValue);
|
return compareAndSwap(location, expectedValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> KBoolean compareAndSetImpl(KRef thiz, T expectedValue, T newValue) {
|
template <typename T> KBoolean compareAndSetImpl(KRef thiz, T expectedValue, T newValue) {
|
||||||
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
volatile T* location = getValueLocation<T>(thiz);
|
||||||
return compareAndSet(location, expectedValue, newValue);
|
return compareAndSet(location, expectedValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline AtomicReferenceLayout* asAtomicReference(KRef thiz) {
|
inline AtomicReferenceLayout* asAtomicReference(KRef thiz) {
|
||||||
return reinterpret_cast<AtomicReferenceLayout*>(thiz + 1);
|
return reinterpret_cast<AtomicReferenceLayout*>(thiz);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -93,7 +104,7 @@ KLong Kotlin_AtomicLong_compareAndSwap(KRef thiz, KLong expectedValue, KLong new
|
|||||||
// Potentially huge performance penalty, but correct.
|
// Potentially huge performance penalty, but correct.
|
||||||
// TODO: reconsider, once target MIPS can do proper 64-bit CAS.
|
// TODO: reconsider, once target MIPS can do proper 64-bit CAS.
|
||||||
while (compareAndSwap(&lock64, 0, 1) != 0);
|
while (compareAndSwap(&lock64, 0, 1) != 0);
|
||||||
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
volatile KLong* address = getValueLocation<KLong>(thiz);
|
||||||
KLong old = *address;
|
KLong old = *address;
|
||||||
if (old == expectedValue) {
|
if (old == expectedValue) {
|
||||||
*address = newValue;
|
*address = newValue;
|
||||||
@@ -111,7 +122,7 @@ KBoolean Kotlin_AtomicLong_compareAndSet(KRef thiz, KLong expectedValue, KLong n
|
|||||||
// TODO: reconsider, once target MIPS can do proper 64-bit CAS.
|
// TODO: reconsider, once target MIPS can do proper 64-bit CAS.
|
||||||
KBoolean result = false;
|
KBoolean result = false;
|
||||||
while (compareAndSwap(&lock64, 0, 1) != 0);
|
while (compareAndSwap(&lock64, 0, 1) != 0);
|
||||||
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
volatile KLong* address = getValueLocation<KLong>(thiz);
|
||||||
KLong old = *address;
|
KLong old = *address;
|
||||||
if (old == expectedValue) {
|
if (old == expectedValue) {
|
||||||
result = true;
|
result = true;
|
||||||
@@ -129,7 +140,7 @@ void Kotlin_AtomicLong_set(KRef thiz, KLong newValue) {
|
|||||||
// Potentially huge performance penalty, but correct.
|
// Potentially huge performance penalty, but correct.
|
||||||
// TODO: reconsider, once target MIPS can do proper 64-bit atomic store.
|
// TODO: reconsider, once target MIPS can do proper 64-bit atomic store.
|
||||||
while (compareAndSwap(&lock64, 0, 1) != 0);
|
while (compareAndSwap(&lock64, 0, 1) != 0);
|
||||||
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
volatile KLong* address = getValueLocation<KLong>(thiz);
|
||||||
*address = newValue;
|
*address = newValue;
|
||||||
compareAndSwap(&lock64, 1, 0);
|
compareAndSwap(&lock64, 1, 0);
|
||||||
#else
|
#else
|
||||||
@@ -142,7 +153,7 @@ KLong Kotlin_AtomicLong_get(KRef thiz) {
|
|||||||
// Potentially huge performance penalty, but correct.
|
// Potentially huge performance penalty, but correct.
|
||||||
// TODO: reconsider, once target MIPS can do proper 64-bit atomic store.
|
// TODO: reconsider, once target MIPS can do proper 64-bit atomic store.
|
||||||
while (compareAndSwap(&lock64, 0, 1) != 0);
|
while (compareAndSwap(&lock64, 0, 1) != 0);
|
||||||
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
volatile KLong* address = getValueLocation<KLong>(thiz);
|
||||||
KLong value = *address;
|
KLong value = *address;
|
||||||
compareAndSwap(&lock64, 1, 0);
|
compareAndSwap(&lock64, 1, 0);
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -45,6 +45,19 @@ constexpr int runtimeTypeSize[] = {
|
|||||||
1 // BOOLEAN
|
1 // BOOLEAN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr int runtimeTypeAlignment[] = {
|
||||||
|
-1, // INVALID
|
||||||
|
alignof(ObjHeader*), // OBJECT
|
||||||
|
alignof(int8_t), // INT8
|
||||||
|
alignof(int16_t), // INT16
|
||||||
|
alignof(int32_t), // INT32
|
||||||
|
alignof(int64_t), // INT64
|
||||||
|
alignof(float), // FLOAT32
|
||||||
|
alignof(double), // FLOAT64
|
||||||
|
alignof(void*), // NATIVE_PTR
|
||||||
|
1 // BOOLEAN
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -130,13 +143,16 @@ RUNTIME_USED void* Konan_DebugGetFieldAddress(KRef obj, int index) {
|
|||||||
if (index > obj->array()->count_)
|
if (index > obj->array()->count_)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return reinterpret_cast<uint8_t*>(obj->array() + 1) + index * runtimeTypeSize[-extendedTypeInfo->fieldsCount_];
|
int32_t typeIndex = -extendedTypeInfo->fieldsCount_;
|
||||||
|
return reinterpret_cast<uint8_t*>(obj->array())
|
||||||
|
+ alignUp(sizeof(struct ArrayHeader), runtimeTypeAlignment[typeIndex])
|
||||||
|
+ index * runtimeTypeSize[typeIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index >= extendedTypeInfo->fieldsCount_)
|
if (index >= extendedTypeInfo->fieldsCount_)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return reinterpret_cast<uint8_t*>(obj + 1) + extendedTypeInfo->fieldOffsets_[index];
|
return reinterpret_cast<uint8_t*>(obj) + extendedTypeInfo->fieldOffsets_[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute address of field or an array element at the index, or null, if incorrect.
|
// Compute address of field or an array element at the index, or null, if incorrect.
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ constexpr container_size_t kContainerAlignment = 1024;
|
|||||||
// Single object alignment.
|
// Single object alignment.
|
||||||
constexpr container_size_t kObjectAlignment = 8;
|
constexpr container_size_t kObjectAlignment = 8;
|
||||||
|
|
||||||
|
// Required e.g. for object size computations to be correct.
|
||||||
|
static_assert(sizeof(ContainerHeader) % kObjectAlignment == 0, "sizeof(ContainerHeader) is not aligned");
|
||||||
|
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
#define MEMORY_LOG(...) konan::consolePrintf(__VA_ARGS__);
|
#define MEMORY_LOG(...) konan::consolePrintf(__VA_ARGS__);
|
||||||
#else
|
#else
|
||||||
@@ -396,14 +399,25 @@ inline container_size_t alignUp(container_size_t size, int alignment) {
|
|||||||
return (size + alignment - 1) & ~(alignment - 1);
|
return (size + alignment - 1) & ~(alignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32_t arrayObjectSize(const TypeInfo* typeInfo, uint32_t count) {
|
||||||
|
// Note: array body is aligned, but for size computation it is enough to align the sum.
|
||||||
|
static_assert(kObjectAlignment % alignof(KLong) == 0, "");
|
||||||
|
static_assert(kObjectAlignment % alignof(KDouble) == 0, "");
|
||||||
|
return alignUp(sizeof(ArrayHeader) - typeInfo->instanceSize_ * count, kObjectAlignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t arrayObjectSize(const ArrayHeader* obj) {
|
||||||
|
return arrayObjectSize(obj->type_info(), obj->count_);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: shall we do padding for alignment?
|
// TODO: shall we do padding for alignment?
|
||||||
inline container_size_t objectSize(const ObjHeader* obj) {
|
inline container_size_t objectSize(const ObjHeader* obj) {
|
||||||
const TypeInfo* type_info = obj->type_info();
|
const TypeInfo* type_info = obj->type_info();
|
||||||
container_size_t size = (type_info->instanceSize_ < 0 ?
|
container_size_t size = (type_info->instanceSize_ < 0 ?
|
||||||
// An array.
|
// An array.
|
||||||
ArrayDataSizeBytes(obj->array()) + sizeof(ArrayHeader)
|
arrayObjectSize(obj->array())
|
||||||
:
|
:
|
||||||
type_info->instanceSize_ + sizeof(ObjHeader));
|
type_info->instanceSize_);
|
||||||
return alignUp(size, kObjectAlignment);
|
return alignUp(size, kObjectAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +508,7 @@ inline void traverseContainerObjectFields(ContainerHeader* container, func proce
|
|||||||
if (typeInfo != theArrayTypeInfo) {
|
if (typeInfo != theArrayTypeInfo) {
|
||||||
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
|
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
|
||||||
ObjHeader** location = reinterpret_cast<ObjHeader**>(
|
ObjHeader** location = reinterpret_cast<ObjHeader**>(
|
||||||
reinterpret_cast<uintptr_t>(obj + 1) + typeInfo->objOffsets_[index]);
|
reinterpret_cast<uintptr_t>(obj) + typeInfo->objOffsets_[index]);
|
||||||
process(location);
|
process(location);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1003,7 +1017,7 @@ void FreeContainer(ContainerHeader* container) {
|
|||||||
void ObjectContainer::Init(const TypeInfo* typeInfo) {
|
void ObjectContainer::Init(const TypeInfo* typeInfo) {
|
||||||
RuntimeAssert(typeInfo->instanceSize_ >= 0, "Must be an object");
|
RuntimeAssert(typeInfo->instanceSize_ >= 0, "Must be an object");
|
||||||
uint32_t alloc_size =
|
uint32_t alloc_size =
|
||||||
sizeof(ContainerHeader) + sizeof(ObjHeader) + typeInfo->instanceSize_;
|
sizeof(ContainerHeader) + typeInfo->instanceSize_;
|
||||||
header_ = AllocContainer(alloc_size);
|
header_ = AllocContainer(alloc_size);
|
||||||
if (header_) {
|
if (header_) {
|
||||||
// One object in this container.
|
// One object in this container.
|
||||||
@@ -1018,8 +1032,7 @@ void ObjectContainer::Init(const TypeInfo* typeInfo) {
|
|||||||
void ArrayContainer::Init(const TypeInfo* typeInfo, uint32_t elements) {
|
void ArrayContainer::Init(const TypeInfo* typeInfo, uint32_t elements) {
|
||||||
RuntimeAssert(typeInfo->instanceSize_ < 0, "Must be an array");
|
RuntimeAssert(typeInfo->instanceSize_ < 0, "Must be an array");
|
||||||
uint32_t alloc_size =
|
uint32_t alloc_size =
|
||||||
sizeof(ContainerHeader) + sizeof(ArrayHeader) -
|
sizeof(ContainerHeader) + arrayObjectSize(typeInfo, elements);
|
||||||
typeInfo->instanceSize_ * elements;
|
|
||||||
header_ = AllocContainer(alloc_size);
|
header_ = AllocContainer(alloc_size);
|
||||||
RuntimeAssert(header_ != nullptr, "Cannot alloc memory");
|
RuntimeAssert(header_ != nullptr, "Cannot alloc memory");
|
||||||
if (header_) {
|
if (header_) {
|
||||||
@@ -1030,7 +1043,7 @@ void ArrayContainer::Init(const TypeInfo* typeInfo, uint32_t elements) {
|
|||||||
SetHeader(GetPlace()->obj(), typeInfo);
|
SetHeader(GetPlace()->obj(), typeInfo);
|
||||||
MEMORY_LOG("array at %p\n", GetPlace())
|
MEMORY_LOG("array at %p\n", GetPlace())
|
||||||
OBJECT_ALLOC_EVENT(
|
OBJECT_ALLOC_EVENT(
|
||||||
memoryState, -typeInfo->instanceSize_ * elements, GetPlace()->obj())
|
memoryState, arrayObjectSize(typeInfo, elements), GetPlace()->obj())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,7 +1115,7 @@ ObjHeader** ArenaContainer::getSlot() {
|
|||||||
|
|
||||||
ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
||||||
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
|
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
|
||||||
uint32_t size = type_info->instanceSize_ + sizeof(ObjHeader);
|
uint32_t size = type_info->instanceSize_;
|
||||||
ObjHeader* result = reinterpret_cast<ObjHeader*>(place(size));
|
ObjHeader* result = reinterpret_cast<ObjHeader*>(place(size));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -1115,12 +1128,12 @@ ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
|||||||
|
|
||||||
ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t count) {
|
ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t count) {
|
||||||
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
|
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
|
||||||
container_size_t size = sizeof(ArrayHeader) - type_info->instanceSize_ * count;
|
container_size_t size = arrayObjectSize(type_info, count);
|
||||||
ArrayHeader* result = reinterpret_cast<ArrayHeader*>(place(size));
|
ArrayHeader* result = reinterpret_cast<ArrayHeader*>(place(size));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
OBJECT_ALLOC_EVENT(memoryState, -type_info->instanceSize_ * count, result->obj())
|
OBJECT_ALLOC_EVENT(memoryState, arrayObjectSize(type_info, count), result->obj())
|
||||||
currentChunk_->asHeader()->incObjectCount();
|
currentChunk_->asHeader()->incObjectCount();
|
||||||
setHeader(result->obj(), type_info);
|
setHeader(result->obj(), type_info);
|
||||||
result->count_ = count;
|
result->count_ = count;
|
||||||
@@ -1923,7 +1936,7 @@ KBoolean Konan_ensureAcyclicAndSet(ObjHeader* where, KInt index, ObjHeader* what
|
|||||||
if (!acyclic) return false;
|
if (!acyclic) return false;
|
||||||
}
|
}
|
||||||
UpdateRef(reinterpret_cast<ObjHeader**>(
|
UpdateRef(reinterpret_cast<ObjHeader**>(
|
||||||
reinterpret_cast<uintptr_t>(where + 1) + where->type_info()->objOffsets_[index]), what);
|
reinterpret_cast<uintptr_t>(where) + where->type_info()->objOffsets_[index]), what);
|
||||||
// Fence on updated location?
|
// Fence on updated location?
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,11 +334,6 @@ inline bool PermanentOrFrozen(ObjHeader* obj) {
|
|||||||
return container == nullptr || container->frozen();
|
return container == nullptr || container->frozen();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t ArrayDataSizeBytes(const ArrayHeader* obj) {
|
|
||||||
// Instance size is negative.
|
|
||||||
return -obj->type_info()->instanceSize_ * obj->count_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Class representing arbitrary placement container.
|
// Class representing arbitrary placement container.
|
||||||
class Container {
|
class Container {
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -20,60 +20,64 @@
|
|||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Exceptions.h"
|
#include "Exceptions.h"
|
||||||
|
|
||||||
inline void* AddressOfElementAt(ArrayHeader* obj, int32_t index) {
|
constexpr size_t alignUp(size_t size, size_t alignment) {
|
||||||
// Instance size is negative.
|
return (size + alignment - 1) & ~(alignment - 1);
|
||||||
return reinterpret_cast<uint8_t*>(obj + 1) -
|
|
||||||
obj->type_info()->instanceSize_ * index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const void* AddressOfElementAt(const ArrayHeader* obj, int32_t index) {
|
template <typename T>
|
||||||
// Instance size is negative.
|
inline T* AddressOfElementAt(ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<const uint8_t*>(obj + 1) -
|
int8_t* body = reinterpret_cast<int8_t*>(obj) + alignUp(sizeof(ArrayHeader), alignof(T));
|
||||||
obj->type_info()->instanceSize_ * index;
|
return reinterpret_cast<T*>(body) + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline const T* AddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
||||||
|
const int8_t* body = reinterpret_cast<const int8_t*>(obj) + alignUp(sizeof(ArrayHeader), alignof(T));
|
||||||
|
return reinterpret_cast<const T*>(body) + index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimized versions not accessing type info.
|
// Optimized versions not accessing type info.
|
||||||
inline KByte* ByteArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
inline KByte* ByteArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<KByte*>(obj + 1) + index;
|
return AddressOfElementAt<KByte>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const KByte* ByteArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
inline const KByte* ByteArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<const KByte*>(obj + 1) + index;
|
return AddressOfElementAt<KByte>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline KChar* CharArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
inline KChar* CharArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<KChar*>(obj + 1) + index;
|
return AddressOfElementAt<KChar>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const KChar* CharArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
inline const KChar* CharArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<const KChar*>(obj + 1) + index;
|
return AddressOfElementAt<KChar>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline KInt* IntArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
inline KInt* IntArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<KInt*>(obj + 1) + index;
|
return AddressOfElementAt<KInt>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const KInt* IntArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
inline const KInt* IntArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<const KInt*>(obj + 1) + index;
|
return AddressOfElementAt<KInt>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consider aligning of base to sizeof(T).
|
// Consider aligning of base to sizeof(T).
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline T* PrimitiveArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
inline T* PrimitiveArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<T*>(obj + 1) + index;
|
return AddressOfElementAt<T>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline const T* PrimitiveArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
inline const T* PrimitiveArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<const T*>(obj + 1) + index;
|
return AddressOfElementAt<T>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline KRef* ArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
inline KRef* ArrayAddressOfElementAt(ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<KRef*>(obj + 1) + index;
|
return AddressOfElementAt<KRef>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const KRef* ArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
inline const KRef* ArrayAddressOfElementAt(const ArrayHeader* obj, KInt index) {
|
||||||
return reinterpret_cast<const KRef*>(obj + 1) + index;
|
return AddressOfElementAt<KRef>(obj, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -18,7 +18,17 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// TODO: an ugly hack with fixed offsets.
|
// TODO: an ugly hack with fixed layout.
|
||||||
|
struct WeakReferenceCounter {
|
||||||
|
ObjHeader header;
|
||||||
|
KRef referred;
|
||||||
|
KInt lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline WeakReferenceCounter* asWeakReferenceCounter(ObjHeader* obj) {
|
||||||
|
return reinterpret_cast<WeakReferenceCounter*>(obj);
|
||||||
|
}
|
||||||
|
|
||||||
constexpr int referredOffset = 0;
|
constexpr int referredOffset = 0;
|
||||||
constexpr int lockOffset = sizeof(void*);
|
constexpr int lockOffset = sizeof(void*);
|
||||||
|
|
||||||
@@ -64,11 +74,11 @@ OBJ_GETTER(Konan_getWeakReferenceImpl, ObjHeader* referred) {
|
|||||||
|
|
||||||
// Materialize a weak reference to either null or the real reference.
|
// Materialize a weak reference to either null or the real reference.
|
||||||
OBJ_GETTER(Konan_WeakReferenceCounter_get, ObjHeader* counter) {
|
OBJ_GETTER(Konan_WeakReferenceCounter_get, ObjHeader* counter) {
|
||||||
ObjHeader** referredAddress = reinterpret_cast<ObjHeader**>(reinterpret_cast<char*>(counter + 1) + referredOffset);
|
ObjHeader** referredAddress = &asWeakReferenceCounter(counter)->referred;
|
||||||
#if KONAN_NO_THREADS
|
#if KONAN_NO_THREADS
|
||||||
RETURN_OBJ(*referredAddress);
|
RETURN_OBJ(*referredAddress);
|
||||||
#else
|
#else
|
||||||
int32_t* lockAddress = reinterpret_cast<int32_t*>(reinterpret_cast<char*>(counter + 1) + lockOffset);
|
int32_t* lockAddress = &asWeakReferenceCounter(counter)->lock;
|
||||||
// Spinlock.
|
// Spinlock.
|
||||||
lock(lockAddress);
|
lock(lockAddress);
|
||||||
ObjHolder holder(*referredAddress);
|
ObjHolder holder(*referredAddress);
|
||||||
@@ -78,12 +88,12 @@ OBJ_GETTER(Konan_WeakReferenceCounter_get, ObjHeader* counter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WeakReferenceCounterClear(ObjHeader* counter) {
|
void WeakReferenceCounterClear(ObjHeader* counter) {
|
||||||
ObjHeader** referredAddress = reinterpret_cast<ObjHeader**>(reinterpret_cast<char*>(counter + 1) + referredOffset);
|
ObjHeader** referredAddress = &asWeakReferenceCounter(counter)->referred;
|
||||||
// Note, that we don't do UpdateRef here, as reference is weak.
|
// Note, that we don't do UpdateRef here, as reference is weak.
|
||||||
#if KONAN_NO_THREADS
|
#if KONAN_NO_THREADS
|
||||||
*referredAddress = nullptr;
|
*referredAddress = nullptr;
|
||||||
#else
|
#else
|
||||||
int32_t* lockAddress = reinterpret_cast<int32_t*>(reinterpret_cast<char*>(counter + 1) + lockOffset);
|
int32_t* lockAddress = &asWeakReferenceCounter(counter)->lock;
|
||||||
// Spinlock.
|
// Spinlock.
|
||||||
lock(lockAddress);
|
lock(lockAddress);
|
||||||
*referredAddress = nullptr;
|
*referredAddress = nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user