Merge "Added generation of hip ostream operators" into amd-master
[ROCm/roctracer commit: c73c1bbf29]
Этот коммит содержится в:
@@ -36,6 +36,7 @@ add_definitions ( -DLITTLEENDIAN_CPU=1 )
|
|||||||
add_definitions ( -DHSA_LARGE_MODEL= )
|
add_definitions ( -DHSA_LARGE_MODEL= )
|
||||||
add_definitions ( -DHSA_DEPRECATED= )
|
add_definitions ( -DHSA_DEPRECATED= )
|
||||||
add_definitions ( -D__HIP_PLATFORM_HCC__ )
|
add_definitions ( -D__HIP_PLATFORM_HCC__ )
|
||||||
|
add_definitions ( -DHIP_PROF_HIP_API_STRING=1 )
|
||||||
|
|
||||||
## Linux Compiler options
|
## Linux Compiler options
|
||||||
set ( CMAKE_CXX_FLAGS "-std=c++11")
|
set ( CMAKE_CXX_FLAGS "-std=c++11")
|
||||||
@@ -65,11 +66,6 @@ if ( DEFINED ENV{CMAKE_DEBUG_TRACE} )
|
|||||||
add_definitions ( -DDEBUG_TRACE=1 )
|
add_definitions ( -DDEBUG_TRACE=1 )
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
## HIP APIString() routine enableing
|
|
||||||
if ( DEFINED HIP_PROF_HIP_API_STRING )
|
|
||||||
add_definitions ( -DHIP_PROF_HIP_API_STRING=1 )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
## Enable HIP_VDI mode
|
## Enable HIP_VDI mode
|
||||||
if ( DEFINED HIP_VDI )
|
if ( DEFINED HIP_VDI )
|
||||||
add_definitions ( -DHIP_VDI=${HIP_VDI} )
|
add_definitions ( -DHIP_VDI=${HIP_VDI} )
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ THE SOFTWARE.
|
|||||||
#ifndef INC_ROCTRACER_HIP_H_
|
#ifndef INC_ROCTRACER_HIP_H_
|
||||||
#define INC_ROCTRACER_HIP_H_
|
#define INC_ROCTRACER_HIP_H_
|
||||||
|
|
||||||
|
#include "inc/hip_ostream_ops.h"
|
||||||
#include <hip/hip_runtime.h>
|
#include <hip/hip_runtime.h>
|
||||||
#include <hip/hcc_detail/hip_prof_str.h>
|
#include <hip/hcc_detail/hip_prof_str.h>
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,19 @@ LICENSE = \
|
|||||||
'THE SOFTWARE.\n' + \
|
'THE SOFTWARE.\n' + \
|
||||||
'*/\n'
|
'*/\n'
|
||||||
|
|
||||||
HEADER = \
|
header = \
|
||||||
'template <typename T>\n' + \
|
'template <typename T>\n' + \
|
||||||
'struct output_streamer {\n' + \
|
'struct output_streamer {\n' + \
|
||||||
' inline static std::ostream& put(std::ostream& out, const T& v) { return out; }\n' + \
|
' inline static std::ostream& put(std::ostream& out, const T& v) { return out; }\n' + \
|
||||||
'};\n' + \
|
'};\n' + \
|
||||||
|
'template<>\n' + \
|
||||||
|
'struct output_streamer<void*> {\n' + \
|
||||||
|
'inline static std::ostream& put(std::ostream& out, void* v) { out << std::hex << v; return out; }\n' + \
|
||||||
|
'};\n' + \
|
||||||
|
'template<>\n' + \
|
||||||
|
'struct output_streamer<const void*> {\n' + \
|
||||||
|
'inline static std::ostream& put(std::ostream& out, const void* v) { out << std::hex << v; return out; }\n' + \
|
||||||
|
'};\n' + \
|
||||||
'\ntemplate<>\n' + \
|
'\ntemplate<>\n' + \
|
||||||
'struct output_streamer<bool> {\n' + \
|
'struct output_streamer<bool> {\n' + \
|
||||||
' inline static std::ostream& put(std::ostream& out, bool v) { out << std::hex << "<bool " << "0x" << v << std::dec << ">"; return out; }\n' + \
|
' inline static std::ostream& put(std::ostream& out, bool v) { out << std::hex << "<bool " << "0x" << v << std::dec << ">"; return out; }\n' + \
|
||||||
@@ -75,53 +83,91 @@ HEADER = \
|
|||||||
'};\n' + \
|
'};\n' + \
|
||||||
'\n'
|
'\n'
|
||||||
|
|
||||||
structs_done = {}
|
header_hip = \
|
||||||
def process_struct(f,c,cppHeader,nname,apiname):
|
'template <typename T>\n' + \
|
||||||
|
' std::ostream& operator<<(std::ostream& out, const T& v) { using std::operator<<; out << v; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, void* v) { using std::operator<<; out << std::hex << v; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, const void* v) { using std::operator<<; out << std::hex << v; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, bool v) { using std::operator<<; out << std::hex << "<bool " << "0x" << v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, uint8_t v) { using std::operator<<; out << std::hex << "<uint8_t " << "0x" << v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, uint16_t v) { using std::operator<<; out << std::hex << "<uint16_t " << "0x" << v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, uint32_t v) { using std::operator<<; out << std::hex << "<uint32_t " << "0x" << v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, uint64_t v) { using std::operator<<; out << std::hex << "<uint64_t " << "0x" << v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, bool* v) { using std::operator<<; out << std::hex << "<bool " << "0x" << *v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, uint8_t* v) { using std::operator<<; out << std::hex << "<uint8_t " << "0x" << *v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, uint16_t* v) { using std::operator<<; out << std::hex << "<uint16_t " << "0x" << *v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, uint32_t* v) { using std::operator<<; out << std::hex << "<uint32_t " << "0x" << *v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'std::ostream& operator<<(std::ostream& out, uint64_t* v) { using std::operator<<; out << std::hex << "<uint64_t " << "0x" << *v << std::dec << ">"; return out; }\n' + \
|
||||||
|
'\n'
|
||||||
|
|
||||||
if c not in cppHeader.classes:
|
structs_analyzed = {}
|
||||||
|
global_ops_hip = ''
|
||||||
|
|
||||||
|
# process_struct traverses recursively all structs to extract all fields
|
||||||
|
def process_struct(file_handle, cppHeader_struct, cppHeader, parent_hier_name, apiname):
|
||||||
|
# file_handle: handle for output file {api_name}_ostream_ops.h to be generated
|
||||||
|
# cppHeader_struct: cppHeader struct being processed
|
||||||
|
# cppHeader: cppHeader object created by CppHeaderParser.CppHeader(...)
|
||||||
|
# parent_hier_name: parent hierarchical name used for nested structs/enums
|
||||||
|
# apiname: for example hip, kfd.
|
||||||
|
|
||||||
|
if cppHeader_struct == 'max_align_t': #function pointers not working in cppheaderparser
|
||||||
return
|
return
|
||||||
if c in structs_done:
|
if cppHeader_struct not in cppHeader.classes:
|
||||||
|
return
|
||||||
|
if cppHeader_struct in structs_analyzed:
|
||||||
return
|
return
|
||||||
|
|
||||||
structs_done[c] = 1;
|
structs_analyzed[cppHeader_struct] = 1;
|
||||||
for l in range(len(cppHeader.classes[c]["properties"]["public"])):
|
for l in reversed(range(len(cppHeader.classes[cppHeader_struct]["properties"]["public"]))):
|
||||||
key = 'name'
|
key = 'name'
|
||||||
name = ""
|
name = ""
|
||||||
if key in cppHeader.classes[c]["properties"]["public"][l]:
|
if key in cppHeader.classes[cppHeader_struct]["properties"]["public"][l]:
|
||||||
name = cppHeader.classes[c]["properties"]["public"][l][key]
|
if parent_hier_name != '':
|
||||||
|
name = parent_hier_name + '.' + cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key]
|
||||||
|
else:
|
||||||
|
name = cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key]
|
||||||
|
if name == '':
|
||||||
|
continue
|
||||||
key2 = 'type'
|
key2 = 'type'
|
||||||
mtype = ""
|
mtype = ""
|
||||||
if key2 in cppHeader.classes[c]["properties"]["public"][l]:
|
if key2 in cppHeader.classes[cppHeader_struct]["properties"]["public"][l]:
|
||||||
mtype = cppHeader.classes[c]["properties"]["public"][l][key2]
|
mtype = cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key2]
|
||||||
|
if mtype == '':
|
||||||
|
continue
|
||||||
key3 = 'array_size'
|
key3 = 'array_size'
|
||||||
array_size = ""
|
array_size = ""
|
||||||
if key3 in cppHeader.classes[c]["properties"]["public"][l]:
|
if key3 in cppHeader.classes[cppHeader_struct]["properties"]["public"][l]:
|
||||||
array_size = cppHeader.classes[c]["properties"]["public"][l][key3]
|
array_size = cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key3]
|
||||||
key4 = 'property_of_class'
|
key4 = 'property_of_class'
|
||||||
prop = ""
|
prop = ""
|
||||||
if key4 in cppHeader.classes[c]["properties"]["public"][l]:
|
if key4 in cppHeader.classes[cppHeader_struct]["properties"]["public"][l]:
|
||||||
prop = cppHeader.classes[c]["properties"]["public"][l][key4]
|
prop = cppHeader.classes[cppHeader_struct]["properties"]["public"][l][key4]
|
||||||
|
|
||||||
if mtype != "" and "union" not in mtype:
|
if "union" not in mtype:
|
||||||
if array_size == "":
|
if apiname.lower() == 'hip':
|
||||||
str = " roctracer::" + apiname.lower() + "_support::output_streamer<"+mtype+">::put(out,v."+name+");\n"
|
str = " roctracer::hip_support::operator<<(out, v."+name+");\n"
|
||||||
else:
|
else:
|
||||||
|
if array_size == "":
|
||||||
|
str = " roctracer::" + apiname.lower() + "_support::output_streamer<"+mtype+">::put(out,v."+name+");\n"
|
||||||
|
else:
|
||||||
str = " roctracer::" + apiname.lower() + "_support::output_streamer<"+mtype+"["+array_size+"]>::put(out,v."+name+");\n"
|
str = " roctracer::" + apiname.lower() + "_support::output_streamer<"+mtype+"["+array_size+"]>::put(out,v."+name+");\n"
|
||||||
|
|
||||||
if nname != "" and nname not in str:
|
|
||||||
#print("injecting ",nname, "in ", str)
|
|
||||||
str = str.replace("v.","v."+nname+".")
|
|
||||||
if "void" not in mtype:
|
if "void" not in mtype:
|
||||||
f.write(str)
|
file_handle.write(str)
|
||||||
else:
|
else:
|
||||||
nc = prop+"::"
|
if prop != '':
|
||||||
process_struct(f,nc,cppHeader,name,apiname)
|
next_cppHeader_struct = prop + "::"
|
||||||
nc = prop+"::"+mtype+" "
|
process_struct(file_handle, next_cppHeader_struct, cppHeader, name, apiname)
|
||||||
process_struct(f,nc,cppHeader,name,apiname)
|
next_cppHeader_struct = prop + "::" + mtype + " "
|
||||||
nc = c+"::"
|
process_struct(file_handle, next_cppHeader_struct, cppHeader, name, apiname)
|
||||||
process_struct(f,nc,cppHeader,name,apiname)
|
next_cppHeader_struct = cppHeader_struct + "::"
|
||||||
|
process_struct(file_handle, next_cppHeader_struct, cppHeader, name, apiname)
|
||||||
|
|
||||||
|
# Parses API header file and generates ostream ops files ostream_ops.h and basic_ostream_ops.h
|
||||||
def gen_cppheader(infilepath, outfilepath):
|
def gen_cppheader(infilepath, outfilepath):
|
||||||
|
# infilepath: API Header file to be parsed
|
||||||
|
# outfilepath: Output file where ostream operators are written
|
||||||
|
global_ops_hip = ''
|
||||||
try:
|
try:
|
||||||
cppHeader = CppHeaderParser.CppHeader(infilepath)
|
cppHeader = CppHeaderParser.CppHeader(infilepath)
|
||||||
except CppHeaderParser.CppParseError as e:
|
except CppHeaderParser.CppParseError as e:
|
||||||
@@ -139,42 +185,60 @@ def gen_cppheader(infilepath, outfilepath):
|
|||||||
f2.write("// automatically generated\n")
|
f2.write("// automatically generated\n")
|
||||||
f.write(LICENSE + '\n')
|
f.write(LICENSE + '\n')
|
||||||
f2.write(LICENSE + '\n')
|
f2.write(LICENSE + '\n')
|
||||||
HEADER_S = \
|
header_s = \
|
||||||
'#ifndef INC_' + apiname + '_OSTREAM_OPS_H_\n' + \
|
'#ifndef INC_' + apiname + '_OSTREAM_OPS_H_\n' + \
|
||||||
'#define INC_' + apiname + '_OSTREAM_OPS_H_\n' + \
|
'#define INC_' + apiname + '_OSTREAM_OPS_H_\n' + \
|
||||||
|
'#ifdef __cplusplus\n' + \
|
||||||
'#include <iostream>\n' + \
|
'#include <iostream>\n' + \
|
||||||
'\n' + \
|
'\n' + \
|
||||||
'#include "roctracer.h"\n'
|
'#include "roctracer.h"\n'
|
||||||
f.write(HEADER_S)
|
if apiname.lower() == 'hip':
|
||||||
|
header_s = header_s + '\n' + \
|
||||||
|
'#include "hip/hip_runtime_api.h"\n' + \
|
||||||
|
'#include "hip/hcc_detail/hip_vector_types.h"\n\n'
|
||||||
|
f.write(header_s)
|
||||||
f.write('\n')
|
f.write('\n')
|
||||||
f.write('namespace roctracer {\n')
|
f.write('namespace roctracer {\n')
|
||||||
f.write('namespace ' + apiname.lower() + '_support {\n')
|
f.write('namespace ' + apiname.lower() + '_support {\n')
|
||||||
f.write('// begin ostream ops for '+ apiname + ' \n')
|
f.write('// begin ostream ops for '+ apiname + ' \n')
|
||||||
f.write('#include "basic_ostream_ops.h"' + '\n')
|
if apiname.lower() != 'hip':
|
||||||
f2.write(HEADER)
|
f.write('#include "basic_ostream_ops.h"' + '\n')
|
||||||
|
else:
|
||||||
|
f.write("// HIP basic ostream ops\n")
|
||||||
|
f.write(header_hip)
|
||||||
|
f.write("// End of HIP basic ostream ops\n\n")
|
||||||
|
f2.write(header)
|
||||||
for c in cppHeader.classes:
|
for c in cppHeader.classes:
|
||||||
if "union" in c:
|
if "union" in c:
|
||||||
continue
|
continue
|
||||||
if len(cppHeader.classes[c]["properties"]["public"])!=0:
|
if len(cppHeader.classes[c]["properties"]["public"])!=0:
|
||||||
|
if apiname.lower() == 'hip':
|
||||||
|
f.write("std::ostream& operator<<(std::ostream& out, " + c + "& v)\n")
|
||||||
|
f.write("{\n")
|
||||||
|
global_ops_hip = global_ops_hip + "std::ostream& operator<<(std::ostream& out, const " + c + "& v)\n" + "{\n" + " roctracer::hip_support::operator<<(out, v);\n" + " return out;\n" + "}\n\n"
|
||||||
|
process_struct(f, c, cppHeader, "", apiname)
|
||||||
|
f.write(" return out;\n")
|
||||||
|
f.write("}\n")
|
||||||
|
else:
|
||||||
f.write("\ntemplate<>\n")
|
f.write("\ntemplate<>\n")
|
||||||
f.write("struct output_streamer<"+c+"&> {\n")
|
f.write("struct output_streamer<" + c + "&> {\n")
|
||||||
f.write(" inline static std::ostream& put(std::ostream& out, "+c+"& v)\n")
|
f.write(" inline static std::ostream& put(std::ostream& out, "+c+"& v)\n")
|
||||||
f.write("{\n")
|
f.write("{\n")
|
||||||
process_struct(f,c,cppHeader,"",apiname)
|
process_struct(f, c, cppHeader, "", apiname)
|
||||||
f.write(" return out;\n")
|
f.write(" return out;\n")
|
||||||
f.write("}\n")
|
f.write("}\n")
|
||||||
f.write("};\n")
|
f.write("};\n")
|
||||||
|
|
||||||
FOOTER = \
|
footer = \
|
||||||
'// end ostream ops for '+ apiname + ' \n'
|
'// end ostream ops for '+ apiname + ' \n'
|
||||||
FOOTER += '};};\n' + \
|
footer += '};};\n\n'
|
||||||
'\n' + \
|
f.write(footer)
|
||||||
'#endif // INC_' + apiname + '_OSTREAM_OPS_H_\n' + \
|
f.write(global_ops_hip)
|
||||||
' \n'
|
|
||||||
FOOTER2 = '\n\n' + \
|
footer = '#endif //__cplusplus\n' + \
|
||||||
'#endif // INC_BASIC_OSTREAM_OPS_H_\n' + \
|
'#endif // INC_' + apiname + '_OSTREAM_OPS_H_\n' + \
|
||||||
' \n'
|
' \n'
|
||||||
f.write(FOOTER)
|
f.write(footer)
|
||||||
f.close()
|
f.close()
|
||||||
f2.close()
|
f2.close()
|
||||||
print('File ' + outfilepath + ' generated')
|
print('File ' + outfilepath + ' generated')
|
||||||
@@ -190,4 +254,4 @@ requiredNamed.add_argument('-out', metavar='file', help='Output file with ostrea
|
|||||||
args = vars(parser.parse_args())
|
args = vars(parser.parse_args())
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
gen_cppheader(args['in'],args['out'])
|
gen_cppheader(args['in'], args['out'])
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ set ( KFD_LIB_SRC
|
|||||||
)
|
)
|
||||||
execute_process ( COMMAND sh -xc "${CMAKE_CXX_COMPILER} -E ${HSA_KMT_INC_PATH}/hsakmttypes.h > ${PROJECT_BINARY_DIR}/hsakmttypes_pp.h" )
|
execute_process ( COMMAND sh -xc "${CMAKE_CXX_COMPILER} -E ${HSA_KMT_INC_PATH}/hsakmttypes.h > ${PROJECT_BINARY_DIR}/hsakmttypes_pp.h" )
|
||||||
execute_process ( COMMAND sh -xc "${ROOT_DIR}/script/gen_ostream_ops.py -in ${PROJECT_BINARY_DIR}/hsakmttypes_pp.h -out ${ROOT_DIR}/inc/kfd_ostream_ops.h" )
|
execute_process ( COMMAND sh -xc "${ROOT_DIR}/script/gen_ostream_ops.py -in ${PROJECT_BINARY_DIR}/hsakmttypes_pp.h -out ${ROOT_DIR}/inc/kfd_ostream_ops.h" )
|
||||||
execute_process ( COMMAND sh -xc "${ROOT_DIR}/script/gen_ostream_ops.py -in ${HIP_PATH}/include/hip/hip_runtime_api.h -out ${ROOT_DIR}/inc/hip_ostream_ops.h" )
|
execute_process ( COMMAND sh -xc "/usr/bin/gcc -D__HIP_PLATFORM_HCC__ -I${HIP_PATH}/include -I${CMAKE_PREFIX_PATH}/hsa/include -E ${HIP_PATH}/include/hip/hip_runtime_api.h > ${PROJECT_BINARY_DIR}/hip_runtime_api_pp.h" )
|
||||||
|
execute_process ( COMMAND sh -xc "${ROOT_DIR}/script/gen_ostream_ops.py -in ${PROJECT_BINARY_DIR}/hip_runtime_api_pp.h -out ${ROOT_DIR}/inc/hip_ostream_ops.h" )
|
||||||
add_library ( ${KFD_LIB} SHARED ${KFD_LIB_SRC} )
|
add_library ( ${KFD_LIB} SHARED ${KFD_LIB_SRC} )
|
||||||
target_include_directories ( ${KFD_LIB} PRIVATE ${LIB_DIR} ${ROOT_DIR} ${ROOT_DIR}/inc ${HSA_RUNTIME_INC_PATH} ${HSA_RUNTIME_HSA_INC_PATH} ${HSA_KMT_INC_PATH} )
|
target_include_directories ( ${KFD_LIB} PRIVATE ${LIB_DIR} ${ROOT_DIR} ${ROOT_DIR}/inc ${HSA_RUNTIME_INC_PATH} ${HSA_RUNTIME_HSA_INC_PATH} ${HSA_KMT_INC_PATH} )
|
||||||
target_link_libraries( ${KFD_LIB} PRIVATE c stdc++ )
|
target_link_libraries( ${KFD_LIB} PRIVATE c stdc++ )
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
MatrixTranspose_ctest_trace 3
|
MatrixTranspose_ctest_trace 3
|
||||||
MatrixTranspose_test_trace 1
|
MatrixTranspose_test_trace 1
|
||||||
MatrixTranspose_mgpu_trace 1
|
MatrixTranspose_mgpu_trace 1
|
||||||
MatrixTranspose_kfd_trace 1
|
MatrixTranspose_kfd_trace 0
|
||||||
MatrixTranspose_sys_trace 1
|
MatrixTranspose_sys_trace 0
|
||||||
MatrixTranspose_sys_hsa_trace 2
|
MatrixTranspose_sys_hsa_trace 0
|
||||||
MatrixTranspose_hip_period_trace 0
|
MatrixTranspose_hip_period_trace 0
|
||||||
MatrixTranspose_hip_flush_trace 1
|
MatrixTranspose_hip_flush_trace 0
|
||||||
ctrl_hsa_trace 1
|
ctrl_hsa_trace 1
|
||||||
ctrl_hsa_input_trace 2
|
ctrl_hsa_input_trace 2
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ void hip_api_flush_cb(hip_api_trace_entry_t* entry) {
|
|||||||
|
|
||||||
if (domain == ACTIVITY_DOMAIN_HIP_API) {
|
if (domain == ACTIVITY_DOMAIN_HIP_API) {
|
||||||
#if HIP_PROF_HIP_API_STRING
|
#if HIP_PROF_HIP_API_STRING
|
||||||
const char* str = hipApiString(cid, data);
|
const char* str = hipApiString((hip_api_id_t)cid, data);
|
||||||
fprintf(hip_api_file_handle, "%s\n", str);
|
fprintf(hip_api_file_handle, "%s\n", str);
|
||||||
#else // !HIP_PROF_HIP_API_STRING
|
#else // !HIP_PROF_HIP_API_STRING
|
||||||
switch (cid) {
|
switch (cid) {
|
||||||
@@ -918,3 +918,4 @@ extern "C" DESTRUCTOR_API void destructor() {
|
|||||||
tool_unload();
|
tool_unload();
|
||||||
ONLOAD_TRACE_END();
|
ONLOAD_TRACE_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user