Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Condition="'$(ExportsFile)' != ''" Include="--exportsfile:$(ExportsFile)" />
<IlcArg Condition="'$(_targetOS)' == 'win' and '$(DebuggerSupport)' != 'false'" Include="--export-dynamic-symbol:DotNetRuntimeDebugHeader,DATA" />
<IlcArg Condition="'$(_targetOS)' != 'win' and '$(DebuggerSupport)' != 'false'" Include="--export-dynamic-symbol:DotNetRuntimeDebugHeader" />
<IlcArg Condition="'$(_targetOS)' == 'win' and '$(DebuggerSupport)' != 'false'" Include="--export-dynamic-symbol:DotNetRuntimeContractDescriptor,DATA" />
<IlcArg Condition="'$(_targetOS)' != 'win' and '$(DebuggerSupport)' != 'false'" Include="--export-dynamic-symbol:DotNetRuntimeContractDescriptor" />
Comment on lines 237 to +240
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the relationship between DotNetRuntimeDebugHeader and DotNetRuntimeContractDescriptor?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DotNetRuntimeDebugHeader export exposes a set of offsets that the current NativeAOT SOS uses to implement some commands ad-hoc.

The DotNetRuntimeContractDescriptor is the cDAC contract blob, similar to CoreCLR builds. Once the cDAC NAOT infrastructure is in place and working, the legacy DotNetRuntimeDebugHeader export and blob will be removed.

<IlcArg Condition="'$(_targetOS)' == 'freebsd' and '$(IsNativeExecutable)' == 'true'" Include="--export-dynamic-symbol:__progname;--export-dynamic-symbol:environ" />
<IlcArg Condition="'$(IlcExportUnmanagedEntrypoints)' == 'true'" Include="--export-unmanaged-entrypoints" />
<IlcArg Include="@(AutoInitializedAssemblies->'--initassembly:%(Identity)')" />
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/nativeaot/Runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ convert_to_absolute_path(VXSORT_SOURCES ${VXSORT_SOURCES})
convert_to_absolute_path(DUMMY_VXSORT_SOURCES ${DUMMY_VXSORT_SOURCES})

if(NOT CLR_CMAKE_TARGET_ARCH_WASM)
add_subdirectory(datadescriptor)
add_subdirectory(Full)
else()
add_subdirectory(Portable)
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ endif (CLR_CMAKE_TARGET_WIN32)

add_library(Runtime.WorkstationGC STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOURCES} ${RUNTIME_ARCH_ASM_OBJECTS})
add_dependencies(Runtime.WorkstationGC aot_eventing_headers)
target_link_libraries(Runtime.WorkstationGC PRIVATE aotminipal)
target_link_libraries(Runtime.WorkstationGC PRIVATE aotminipal nativeaot_cdac_contract_descriptor nativeaot_gc_wks_descriptor)

add_library(Runtime.ServerGC STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOURCES} ${SERVER_GC_SOURCES} ${RUNTIME_ARCH_ASM_OBJECTS})
add_dependencies(Runtime.ServerGC aot_eventing_headers)
target_link_libraries(Runtime.ServerGC PRIVATE aotminipal)
target_link_libraries(Runtime.ServerGC PRIVATE aotminipal nativeaot_cdac_contract_descriptor nativeaot_gc_svr_descriptor)

add_library(standalonegc-disabled STATIC ${STANDALONEGC_DISABLED_SOURCES})
add_dependencies(standalonegc-disabled aot_eventing_headers)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ bool RuntimeInstance::Initialize(HANDLE hPalInstance)

ASSERT_MSG(g_pTheRuntimeInstance == NULL, "multi-instances are not supported");
g_pTheRuntimeInstance = pRuntimeInstance;
ThreadStore::s_pThreadStore = pThreadStore;

return true;
}
Expand Down
46 changes: 46 additions & 0 deletions src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
set(CMAKE_INCLUDE_CURRENT_DIR OFF)

# cDAC contract descriptor for NativeAOT
#
# This uses the shared datadescriptor infrastructure from
# src/coreclr/debug/datadescriptor-shared/ to generate a
# DotNetRuntimeContractDescriptor symbol in the NativeAOT runtime.
#
# Include directories and compile definitions are inherited from the parent
# Runtime/CMakeLists.txt directory scope. The interface targets below only
# need to add the datadescriptor-specific include path.

include(${CLR_DIR}/clrdatadescriptors.cmake)

add_library(nativeaot_descriptor_interface INTERFACE)
target_include_directories(nativeaot_descriptor_interface INTERFACE
${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(nativeaot_descriptor_interface INTERFACE SKIP_TRACING_DEFINITIONS)
generate_data_descriptors(
LIBRARY_NAME nativeaot_cdac_contract_descriptor
CONTRACT_NAME "DotNetRuntimeContractDescriptor"
INTERFACE_TARGET nativeaot_descriptor_interface
EXPORT_VISIBLE)

# GC contract descriptors (workstation + server).
# The GC has its own data descriptor exposed as a sub-descriptor via gc_descriptor in GcDacVars.
set(GC_DESCRIPTOR_DIR "${CLR_DIR}/gc/datadescriptor")

add_library(nativeaot_gc_wks_descriptor_interface INTERFACE)
target_include_directories(nativeaot_gc_wks_descriptor_interface INTERFACE
${GC_DESCRIPTOR_DIR}
${GC_DIR})
generate_data_descriptors(
Comment thread
max-charlamb marked this conversation as resolved.
LIBRARY_NAME nativeaot_gc_wks_descriptor
CONTRACT_NAME "GCContractDescriptorWKS"
INTERFACE_TARGET nativeaot_gc_wks_descriptor_interface)

add_library(nativeaot_gc_svr_descriptor_interface INTERFACE)
target_include_directories(nativeaot_gc_svr_descriptor_interface INTERFACE
${GC_DESCRIPTOR_DIR}
${GC_DIR})
target_compile_definitions(nativeaot_gc_svr_descriptor_interface INTERFACE -DSERVER_GC)
Comment thread
max-charlamb marked this conversation as resolved.
generate_data_descriptors(
LIBRARY_NAME nativeaot_gc_svr_descriptor
CONTRACT_NAME "GCContractDescriptorSVR"
INTERFACE_TARGET nativeaot_gc_svr_descriptor_interface)
36 changes: 36 additions & 0 deletions src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// This header provides the includes needed for datadescriptor.inc to use
// offsetof() on NativeAOT runtime data structures.
//
// Note: Some NativeAOT types have private members that offsetof() cannot access
// from this compilation unit. For those types, we use known offset constants
// validated at build time by AsmOffsetsVerify.cpp and DebugHeader.cpp static_asserts.

#include "common.h"
#include "gcenv.h"
#include "gcheaputilities.h"
#include "gcinterface.dac.h"
#include "rhassert.h"
#include "TargetPtrs.h"
#include "PalLimitedContext.h"
#include "Pal.h"
#include "holder.h"
#include "RuntimeInstance.h"
#include "regdisplay.h"
#include "StackFrameIterator.h"
#include "thread.h"
#include "threadstore.h"

#include <stdint.h>
#include <stddef.h>

GPTR_DECL(MethodTable, g_pFreeObjectEEType);

// ILC emits a ContractDescriptor named "DotNetManagedContractDescriptor" with
// managed type layouts. We take its address so datadescriptor.inc can reference
// it as a sub-descriptor via CDAC_GLOBAL_SUB_DESCRIPTOR.
struct ContractDescriptor;
extern "C" ContractDescriptor DotNetManagedContractDescriptor;
static const void* g_pManagedContractDescriptor = &DotNetManagedContractDescriptor;
137 changes: 137 additions & 0 deletions src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//
// No include guards. This file is included multiple times.
//
// NativeAOT data descriptor declarations for the cDAC contract system.
// This file defines the types, fields, and globals that diagnostic tools
// need to inspect NativeAOT runtime state.
//
// When modifying this file (adding/removing types, fields, or globals), you must also:
// 1. Update the corresponding contract doc in docs/design/datacontracts/<contract>.md
// 2. Update the managed data class in src/native/managed/cdac/.../Data/<Type>.cs
// 3. Update the contract implementation in src/native/managed/cdac/.../Contracts/<Contract>.cs
// 4. Update the mock descriptors and tests in src/native/managed/cdac/tests/.

CDAC_BASELINE("empty")
CDAC_TYPES_BEGIN()

// ========================
// Thread and ThreadStore
// ========================

CDAC_TYPE_BEGIN(Thread)
CDAC_TYPE_INDETERMINATE(Thread)
CDAC_TYPE_FIELD(Thread, T_UINT64, OSId, offsetof(RuntimeThreadLocals, m_threadId))
CDAC_TYPE_FIELD(Thread, T_UINT32, State, offsetof(RuntimeThreadLocals, m_ThreadStateFlags))
CDAC_TYPE_FIELD(Thread, T_POINTER, LinkNext, offsetof(RuntimeThreadLocals, m_pNext))
CDAC_TYPE_FIELD(Thread, T_POINTER, ExceptionTracker, offsetof(RuntimeThreadLocals, m_pExInfoStackHead))
CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackBase, offsetof(RuntimeThreadLocals, m_pStackHigh))
CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackLimit, offsetof(RuntimeThreadLocals, m_pStackLow))
CDAC_TYPE_FIELD(Thread, TYPE(RuntimeThreadLocals), RuntimeThreadLocals, offsetof(RuntimeThreadLocals, m_eeAllocContext))
CDAC_TYPE_FIELD(Thread, T_POINTER, TransitionFrame, offsetof(RuntimeThreadLocals, m_pTransitionFrame))
CDAC_TYPE_END(Thread)

CDAC_TYPE_BEGIN(ThreadStore)
CDAC_TYPE_INDETERMINATE(ThreadStore)
CDAC_TYPE_FIELD(ThreadStore, T_POINTER, FirstThreadLink, cdac_data<ThreadStore>::FirstThreadLink)
CDAC_TYPE_END(ThreadStore)

CDAC_TYPE_BEGIN(RuntimeThreadLocals)
CDAC_TYPE_INDETERMINATE(RuntimeThreadLocals)
CDAC_TYPE_FIELD(RuntimeThreadLocals, TYPE(EEAllocContext), AllocContext, offsetof(RuntimeThreadLocals, m_eeAllocContext))
CDAC_TYPE_END(RuntimeThreadLocals)

// ========================
// Allocation Context
// ========================

CDAC_TYPE_BEGIN(EEAllocContext)
CDAC_TYPE_INDETERMINATE(EEAllocContext)
CDAC_TYPE_FIELD(EEAllocContext, TYPE(GCAllocContext), GCAllocationContext, offsetof(ee_alloc_context, m_rgbAllocContextBuffer))
CDAC_TYPE_END(EEAllocContext)

CDAC_TYPE_BEGIN(GCAllocContext)
CDAC_TYPE_INDETERMINATE(GCAllocContext)
CDAC_TYPE_FIELD(GCAllocContext, T_POINTER, Pointer, offsetof(gc_alloc_context, alloc_ptr))
CDAC_TYPE_FIELD(GCAllocContext, T_POINTER, Limit, offsetof(gc_alloc_context, alloc_limit))
CDAC_TYPE_FIELD(GCAllocContext, T_INT64, AllocBytes, offsetof(gc_alloc_context, alloc_bytes))
CDAC_TYPE_FIELD(GCAllocContext, T_INT64, AllocBytesLoh, offsetof(gc_alloc_context, alloc_bytes_uoh))
CDAC_TYPE_END(GCAllocContext)

// ========================
// MethodTable (EEType)
// ========================

CDAC_TYPE_BEGIN(MethodTable)
CDAC_TYPE_INDETERMINATE(MethodTable)
CDAC_TYPE_FIELD(MethodTable, T_UINT32, Flags, cdac_data<MethodTable>::Flags)
CDAC_TYPE_FIELD(MethodTable, T_UINT32, BaseSize, cdac_data<MethodTable>::BaseSize)
CDAC_TYPE_FIELD(MethodTable, T_POINTER, RelatedType, cdac_data<MethodTable>::RelatedType)
CDAC_TYPE_FIELD(MethodTable, T_UINT16, NumVtableSlots, cdac_data<MethodTable>::NumVtableSlots)
CDAC_TYPE_FIELD(MethodTable, T_UINT16, NumInterfaces, cdac_data<MethodTable>::NumInterfaces)
CDAC_TYPE_FIELD(MethodTable, T_UINT32, HashCode, cdac_data<MethodTable>::HashCode)
CDAC_TYPE_FIELD(MethodTable, T_POINTER, VTable, cdac_data<MethodTable>::VTable)
CDAC_TYPE_END(MethodTable)

// ========================
// Exception Info
// ========================

CDAC_TYPE_BEGIN(ExInfo)
CDAC_TYPE_INDETERMINATE(ExInfo)
CDAC_TYPE_FIELD(ExInfo, T_POINTER, PreviousNestedInfo, offsetof(ExInfo, m_pPrevExInfo))
CDAC_TYPE_FIELD(ExInfo, T_POINTER, ThrownObject, offsetof(ExInfo, m_exception))
CDAC_TYPE_END(ExInfo)

CDAC_TYPES_END()

// ========================
// Globals
// ========================

CDAC_GLOBALS_BEGIN()

CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore)

CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &g_pFreeObjectEEType)

CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address)
CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address)

// NativeAOT MethodTable flag constants (accessed via cdac_data friend)
CDAC_GLOBAL(MethodTableEETypeKindMask, uint32, cdac_data<MethodTable>::EETypeKindMask)
CDAC_GLOBAL(MethodTableHasComponentSizeFlag, uint32, cdac_data<MethodTable>::HasComponentSizeFlag)
CDAC_GLOBAL(MethodTableHasFinalizerFlag, uint32, cdac_data<MethodTable>::HasFinalizerFlag)
CDAC_GLOBAL(MethodTableHasPointersFlag, uint32, cdac_data<MethodTable>::HasPointersFlag)
CDAC_GLOBAL(MethodTableIsGenericFlag, uint32, cdac_data<MethodTable>::IsGenericFlag)
CDAC_GLOBAL(MethodTableElementTypeMask, uint32, cdac_data<MethodTable>::ElementTypeMask)
CDAC_GLOBAL(MethodTableElementTypeShift, uint32, cdac_data<MethodTable>::ElementTypeShift)

// Thread state flag constants
CDAC_GLOBAL(ThreadStateFlagAttached, uint32, 0x00000001)
CDAC_GLOBAL(ThreadStateFlagDetached, uint32, 0x00000002)

// Object contract globals
#ifdef TARGET_64BIT
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 7)
#else
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 3)
#endif

// Contracts: declare which contracts this runtime supports
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add stresslog here as well (without the module table). It can use the same contract version as CoreCLR.

CDAC_GLOBAL_CONTRACT(Thread, 1001)
CDAC_GLOBAL_CONTRACT(Exception, 1)
CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1001)

// Managed type sub-descriptor: ILC emits a ContractDescriptor with managed type layouts
// that the cDAC reader merges as a sub-descriptor. This provides field offsets for managed
// types (e.g., ConditionalWeakTable internals, IdDispenser) that are not exposed through
// native C++ data descriptors.
CDAC_GLOBAL_SUB_DESCRIPTOR(ManagedTypes, &g_pManagedContractDescriptor)

Comment thread
max-charlamb marked this conversation as resolved.
// GC sub-descriptor: the GC populates gc_descriptor during GC_Initialize.
// It is important for subdescriptor pointers to be the last pointers.
CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor))

CDAC_GLOBALS_END()
23 changes: 23 additions & 0 deletions src/coreclr/nativeaot/Runtime/inc/MethodTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class MethodTable;
class TypeManager;
struct TypeManagerHandle;

// cdac_data<T> template for exposing private members to the cDAC data descriptor.
#include "cdacdata.h"

//-------------------------------------------------------------------------------------------------
// The subset of TypeFlags that NativeAOT knows about at runtime
// This should match the TypeFlags enum in the managed type system.
Expand Down Expand Up @@ -86,6 +89,7 @@ class MethodTable
{
friend class AsmOffsets;
friend void PopulateDebugHeaders();
friend struct ::cdac_data<MethodTable>;

private:
struct RelatedTypeUnion
Expand Down Expand Up @@ -346,4 +350,23 @@ class MethodTable
UInt32_BOOL SanityCheck() { return Validate(); }
};

template<> struct cdac_data<MethodTable>
{
static constexpr size_t Flags = offsetof(MethodTable, m_uFlags);
static constexpr size_t BaseSize = offsetof(MethodTable, m_uBaseSize);
static constexpr size_t RelatedType = offsetof(MethodTable, m_RelatedType);
static constexpr size_t NumVtableSlots = offsetof(MethodTable, m_usNumVtableSlots);
static constexpr size_t NumInterfaces = offsetof(MethodTable, m_usNumInterfaces);
static constexpr size_t HashCode = offsetof(MethodTable, m_uHashCode);
static constexpr size_t VTable = offsetof(MethodTable, m_VTable);

static constexpr uint32_t EETypeKindMask = MethodTable::EETypeKindMask;
static constexpr uint32_t HasComponentSizeFlag = MethodTable::HasComponentSizeFlag;
static constexpr uint32_t HasFinalizerFlag = MethodTable::HasFinalizerFlag;
static constexpr uint32_t HasPointersFlag = MethodTable::HasPointersFlag;
static constexpr uint32_t IsGenericFlag = MethodTable::IsGenericFlag;
static constexpr uint32_t ElementTypeMask = MethodTable::ElementTypeMask;
static constexpr uint32_t ElementTypeShift = MethodTable::ElementTypeShift;
};

#pragma warning(pop)
2 changes: 2 additions & 0 deletions src/coreclr/nativeaot/Runtime/threadstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ volatile uint32_t RhpTrapThreads = (uint32_t)TrapThreadsFlags::None;

GVAL_IMPL_INIT(PTR_Thread, RhpSuspendingThread, 0);

SPTR_IMPL(ThreadStore, ThreadStore, s_pThreadStore);

ThreadStore * GetThreadStore()
{
return GetRuntimeInstance()->GetThreadStore();
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/nativeaot/Runtime/threadstore.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

#include "Crst.h"
#include "cdacdata.h"

class Thread;
class CLREventStatic;
Expand All @@ -19,7 +20,9 @@ extern "C" void PopulateDebugHeaders();

class ThreadStore
{
friend class RuntimeInstance;
friend void PopulateDebugHeaders();
friend struct ::cdac_data<ThreadStore>;

SList<Thread> m_ThreadList;
PTR_RuntimeInstance m_pRuntimeInstance;
Expand All @@ -29,6 +32,7 @@ class ThreadStore
ThreadStore();

public:
SPTR_DECL(ThreadStore, s_pThreadStore);
void LockThreadStore();
void UnlockThreadStore();

Expand Down Expand Up @@ -68,6 +72,11 @@ class ThreadStore
};
typedef DPTR(ThreadStore) PTR_ThreadStore;

template<> struct cdac_data<ThreadStore>
{
static constexpr size_t FirstThreadLink = offsetof(ThreadStore, m_ThreadList);
};

ThreadStore * GetThreadStore();

#define FOREACH_THREAD(p_thread_name) \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@
<Compile Include="System\RuntimeType.NativeAot.cs" />
<Compile Include="System\Runtime\ControlledExecution.NativeAot.cs" />
<Compile Include="System\Runtime\DependentHandle.cs" />
<Compile Include="System\Runtime\CompilerServices\CdacFieldAttribute.cs" />
<Compile Include="System\Runtime\CompilerServices\CdacTypeAttribute.cs" />
<Compile Include="System\Runtime\CompilerServices\EagerStaticClassConstructionAttribute.cs" />
<Compile Include="System\Runtime\CompilerServices\RuntimeFeature.NativeAot.cs" />
<Compile Include="System\Runtime\CompilerServices\StaticClassConstructionContext.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>
/// When applied to an instance field of a type annotated with <see cref="CdacTypeAttribute"/>,
/// indicates that ILC should include this field in the managed cDAC data descriptor.
/// The field's actual declared name is used in the descriptor.
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
internal sealed class CdacFieldAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>
/// When applied to a type, indicates that ILC should include its field layout in the
/// managed cDAC data descriptor. The cDAC reader merges this information as a
/// sub-descriptor so diagnostic tools can inspect managed type instances without
/// runtime metadata (critical for NativeAOT where metadata may be stripped).
/// </summary>
/// <remarks>
/// Fields to include must be individually annotated with <see cref="CdacFieldAttribute"/>.
/// The type and field names used in the descriptor match the actual managed names.
/// </remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
internal sealed class CdacTypeAttribute : Attribute
{
}
}
Loading
Loading