Commit ca07b7f0 authored by fenglei.tian's avatar fenglei.tian

fix compiling bugs

parent 1a91c924
...@@ -26,38 +26,40 @@ using namespace std; ...@@ -26,38 +26,40 @@ using namespace std;
using namespace ngraph; using namespace ngraph;
#define NVRTC_SAFE_CALL(x) \ #define NVRTC_SAFE_CALL(x) \
do { \ do \
{ \
nvrtcResult result = x; \ nvrtcResult result = x; \
if (result != NVRTC_SUCCESS) { \ if (result != NVRTC_SUCCESS) \
std::cerr << "\nerror: " #x " failed with error " \ { \
<< nvrtcGetErrorString(result) << '\n'; \ std::cerr << "\nerror: " #x " failed with error " << nvrtcGetErrorString(result) \
<< '\n'; \
exit(1); \ exit(1); \
} \ } \
} while(0) } while (0)
#define CUDA_SAFE_CALL(x) \ #define CUDA_SAFE_CALL(x) \
do { \ do \
{ \
CUresult result = x; \ CUresult result = x; \
if (result != CUDA_SUCCESS) { \ if (result != CUDA_SUCCESS) \
const char *msg; \ { \
const char* msg; \
cuGetErrorName(result, &msg); \ cuGetErrorName(result, &msg); \
std::cerr << "\nerror: " #x " failed with error " \ std::cerr << "\nerror: " #x " failed with error " << msg << '\n'; \
<< msg << '\n'; \
exit(1); \ exit(1); \
} \ } \
} while(0) } while (0)
runtime::gpu::GPU_CallFrame::GPU_CallFrame(std::shared_ptr<GPU_ExternalFunction> external_function, runtime::gpu::GPU_CallFrame::GPU_CallFrame(std::shared_ptr<GPU_ExternalFunction> external_function,
EntryPoint compiled_function) EntryPoint compiled_function)
: m_external_function(external_function) : m_external_function(external_function)
, m_compiled_function(compiled_function) , m_compiled_function(compiled_function)
{ {
CUdevice cuDevice; CUdevice cuDevice;
CUcontext context; CUcontext context;
CUmodule module; CUmodule module;
CUfunction cuda_op_abs_kernel; CUfunction cuda_op_abs_kernel;
CUDA_SAFE_CALL( cuInit(0)); CUDA_SAFE_CALL(cuInit(0));
CUDA_SAFE_CALL(cuDeviceGet(&cuDevice, 0)); CUDA_SAFE_CALL(cuDeviceGet(&cuDevice, 0));
CUDA_SAFE_CALL(cuCtxCreate(&context, 0, cuDevice)); CUDA_SAFE_CALL(cuCtxCreate(&context, 0, cuDevice));
cublasStatus_t cublasStatus = cublasCreate(&m_cublas_handle); cublasStatus_t cublasStatus = cublasCreate(&m_cublas_handle);
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include "ngraph/runtime/gpu/gpu_util.hpp"
namespace ngraph namespace ngraph
{ {
...@@ -30,19 +31,15 @@ namespace ngraph ...@@ -30,19 +31,15 @@ namespace ngraph
static Cuda_context_manager& Instance() static Cuda_context_manager& Instance()
{ {
static Cuda_context_manager manager; static Cuda_context_manager manager;
return pool; return manager;
} }
Cuda_context_manager(Cuda_context_manager const&) = delete; Cuda_context_manager(Cuda_context_manager const&) = delete;
Cuda_context_manager(Cuda_context_manager&&) = delete; Cuda_context_manager(Cuda_context_manager&&) = delete;
Cuda_context_manager& operator=(Cuda_context_manager const&) = delete; Cuda_context_manager& operator=(Cuda_context_manager const&) = delete;
Cuda_context_manager& operator=(Cuda_context_manager &&) = delete; Cuda_context_manager& operator=(Cuda_context_manager&&) = delete;
std::shared_ptr<CUcontext> GetContext()
{
return context_ptr;
}
std::shared_ptr<CUcontext> GetContext() { return context_ptr; }
protected: protected:
Cuda_context_manager() Cuda_context_manager()
{ {
...@@ -51,11 +48,11 @@ namespace ngraph ...@@ -51,11 +48,11 @@ namespace ngraph
CUDA_SAFE_CALL(cuCtxCreate(&context, 0, cuDevice)); CUDA_SAFE_CALL(cuCtxCreate(&context, 0, cuDevice));
context_ptr = std::make_shared<CUcontext>(context); context_ptr = std::make_shared<CUcontext>(context);
} }
~Cuda_context_manager(){} ~Cuda_context_manager() {}
CUdevice cuDevice; CUdevice cuDevice;
CUcontext context; CUcontext context;
std::shared_ptr<CUcontext> context_ptr; std::shared_ptr<CUcontext> context_ptr;
} };
} }
} }
} }
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
#include <string> #include <string>
#include "ngraph/runtime/gpu/gpu_util.hpp"
#include "ngraph/runtime/gpu/gpu_cuda_context_manager.hpp" #include "ngraph/runtime/gpu/gpu_cuda_context_manager.hpp"
#include "ngraph/runtime/gpu/gpu_util.hpp"
namespace ngraph namespace ngraph
{ {
...@@ -30,7 +30,10 @@ namespace ngraph ...@@ -30,7 +30,10 @@ namespace ngraph
class Cuda_function_builder class Cuda_function_builder
{ {
public: public:
static std::shared_ptr<CUfuction> get(std::string& kernel, std::string& name, int number_of_options, std::string options) static std::shared_ptr<CUfunction> Get(std::string& kernel,
std::string& name,
int number_of_options,
const char** options)
{ {
nvrtcProgram prog; nvrtcProgram prog;
NVRTC_SAFE_CALL(nvrtcCreateProgram(&prog, NVRTC_SAFE_CALL(nvrtcCreateProgram(&prog,
...@@ -40,33 +43,35 @@ namespace ngraph ...@@ -40,33 +43,35 @@ namespace ngraph
NULL, // headers NULL, // headers
NULL)); // includeNames NULL)); // includeNames
nvrtcResult compileResult = nvrtcCompileProgram(prog, nvrtcResult compileResult =
number_of_options, nvrtcCompileProgram(prog, number_of_options, options);
options);
if (compileResult != NVRTC_SUCCESS) { if (compileResult != NVRTC_SUCCESS)
{
// size_t logSize; // size_t logSize;
// NVRTC_SAFE_CALL(nvrtcGetProgramLogSize(prog, &logSize)); // NVRTC_SAFE_CALL(nvrtcGetProgramLogSize(prog, &logSize));
// char *log = new char[logSize]; // char *log = new char[logSize];
// NVRTC_SAFE_CALL(nvrtcGetProgramLog(prog, log)); // NVRTC_SAFE_CALL(nvrtcGetProgramLog(prog, log));
// std::cout << log << '\n'; // std::cout << log << '\n';
// delete[] log; // delete[] log;
throw std::runtime_error("compile error: \n" + kernel + "\n options" + options); throw std::runtime_error("compile error: \n" + kernel + "\n options");
} }
size_t ptxSize; size_t ptxSize;
NVRTC_SAFE_CALL(nvrtcGetPTXSize(prog, &ptxSize)); NVRTC_SAFE_CALL(nvrtcGetPTXSize(prog, &ptxSize));
char *ptx = new char[ptxSize]; char* ptx = new char[ptxSize];
NVRTC_SAFE_CALL(nvrtcGetPTX(prog, ptx)); // Load the generated PTX and get a handle to the parent kernel. NVRTC_SAFE_CALL(nvrtcGetPTX(
prog,
ptx)); // Load the generated PTX and get a handle to the parent kernel.
NVRTC_SAFE_CALL(nvrtcDestroyProgram(&prog)); // Destroy the program. NVRTC_SAFE_CALL(nvrtcDestroyProgram(&prog)); // Destroy the program.
CUmodule module; CUmodule module;
CUfunction function; CUfunction function;
CUDA_SAFE_CALL(cuModuleLoadDataEx(&module, ptx, 0, 0, 0)); CUDA_SAFE_CALL(cuModuleLoadDataEx(&module, ptx, 0, 0, 0));
CUDA_SAFE_CALL(cuModuleGetFunction(&function, module, name)); CUDA_SAFE_CALL(cuModuleGetFunction(&function, module, name.c_str()));
return std::make_shared<CUfunction>(function); return std::make_shared<CUfunction>(function);
} }
} };
} }
} }
} }
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
#pragma once #pragma once
#include <string> #include <string>
#include <unordered_map>
#include "ngraph/runtime/gpu/gpu_util.hpp"
namespace ngraph namespace ngraph
{ {
...@@ -36,17 +39,17 @@ namespace ngraph ...@@ -36,17 +39,17 @@ namespace ngraph
Cuda_function_pool(Cuda_function_pool const&) = delete; Cuda_function_pool(Cuda_function_pool const&) = delete;
Cuda_function_pool(Cuda_function_pool&&) = delete; Cuda_function_pool(Cuda_function_pool&&) = delete;
Cuda_function_pool& operator=(Cuda_function_pool const&) = delete; Cuda_function_pool& operator=(Cuda_function_pool const&) = delete;
Cuda_function_pool& operator=(Cuda_function_pool &&) = delete; Cuda_function_pool& operator=(Cuda_function_pool&&) = delete;
void Set(std::string& name, std::shared_ptr<CUfunction> function) void Set(std::string& name, std::shared_ptr<CUfunction> function)
{ {
CUfunction_map.insert({name,function}); CUfunction_map.insert({name, function});
} }
std::shared_ptr<CUfunction> Get(std::string& name) std::shared_ptr<CUfunction> Get(std::string& name)
{ {
auto it = CUfunction_map.find(name); auto it = CUfunction_map.find(name);
if(it != CUfunction_map.end()) if (it != CUfunction_map.end())
{ {
return (*it).second; return (*it).second;
} }
...@@ -54,11 +57,10 @@ namespace ngraph ...@@ -54,11 +57,10 @@ namespace ngraph
} }
protected: protected:
Cuda_function_pool(){} Cuda_function_pool() {}
~Cuda_function_pool(){} ~Cuda_function_pool() {}
std::unordered_map<std::string, std::shared_ptr<CUfunction>> CUfunction_map; std::unordered_map<std::string, std::shared_ptr<CUfunction>> CUfunction_map;
} };
} }
} }
} }
...@@ -26,12 +26,16 @@ namespace ngraph ...@@ -26,12 +26,16 @@ namespace ngraph
{ {
class Cuda_kernel_builder class Cuda_kernel_builder
{ {
static std::string get_1_element_op(std::string& name, std::string& data_type, std::string& op, std::string& kernel) public:
static void Get_1_element_op(const std::string& name,
const std::string& data_type,
const std::string& op,
std::string& kernel)
{ {
kernel = R"( kernel = R"(
extern "C" __global__ extern "C" __global__
void cuda_op_)" + name + "(" + data_type + "* in, " + data_type + "* out, size_t n)\n" void cuda_op_)" + name + "(" +
+R"({ data_type + "* in, " + data_type + "* out, size_t n)\n" + R"({
size_t tid = blockIdx.x * blockDim.x + threadIdx.x; size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
if(tid < n) if(tid < n)
{ {
...@@ -41,27 +45,34 @@ out[tid] = " + op + "(in[tid]);\n" ...@@ -41,27 +45,34 @@ out[tid] = " + op + "(in[tid]);\n"
return; return;
} }
static std::string get_2_element_op(std::string& name, std::string& data_type, std::string op, std::string& kernel) static void Get_2_element_op(const std::string& name,
const std::string& data_type,
const std::string op,
std::string& kernel)
{ {
kernel = R"( kernel = R"(
extern "C" __global__ extern "C" __global__
void cuda_op_)" + name + "(" + data_type + "* in1, " + data_type + "* in2, " + data_type + "* out, size_t n)\n" void cuda_op_)" + name + "(" +
+R"({ data_type + "* in1, " + data_type + "* in2, " + data_type +
"* out, size_t n)\n" + R"({
size_t tid = blockIdx.x * blockDim.x + threadIdx.x; size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
if(tid < n) if(tid < n)
{ {
out[tid] = in1[tid] )" + op + "in2[tid]\n" out[tid] = in1[tid] )" + op + "in2[tid]\n" +
+R"(} R"(}
})"; })";
return; return;
} }
static std::string get_n_element_op(std::string& name, std::string& data_type, std::vector<std::string> ops, std::string& kernel) static void Get_n_element_op(const std::string& name,
const std::string& data_type,
const std::vector<std::string> ops,
std::string& kernel)
{ {
kernel = ""; kernel = "";
return; return;
} }
} };
} }
} }
} }
...@@ -17,18 +17,16 @@ ...@@ -17,18 +17,16 @@
#include <algorithm> #include <algorithm>
#include <map> #include <map>
#include <cublas_v2.h>
#include <nvrtc.h>
#include <cuda.h> #include <cuda.h>
#include <cuda_runtime.h> #include <cuda_runtime.h>
#include <cublas_v2.h>
#include <cudnn_v7.h> #include <cudnn_v7.h>
#include <nvrtc.h>
#include "ngraph/runtime/gpu/gpu_cuda_kernel_emitters.hpp" #include "ngraph/runtime/gpu/gpu_cuda_kernel_emitters.hpp"
#include "ngraph/runtime/gpu/gpu_cude_kernel_builder.hpp" #include "ngraph/runtime/gpu/gpu_cuda_function_builder.hpp"
#include "ngraph/runtime/gpu/gpu_cude_function_builder.hpp" #include "ngraph/runtime/gpu/gpu_cuda_function_pool.hpp"
#include "ngraph/runtime/gpu/gpu_cude_function_pool.hpp" #include "ngraph/runtime/gpu/gpu_cuda_kernel_builder.hpp"
namespace ngraph namespace ngraph
{ {
...@@ -40,18 +38,18 @@ namespace ngraph ...@@ -40,18 +38,18 @@ namespace ngraph
{ {
namespace kernel namespace kernel
{ {
void emit_abs(void* in, void* out, size_t count) void emit_abs(void* in, void* out, size_t count)
{ {
std::string name = "abs"; std::string name = "abs";
// Create an instance of nvrtcProgram with the code string. // Create an instance of nvrtcProgram with the code string.
if(Cuda_function_pool::Instance().get(name) == nullptr) if (Cuda_function_pool::Instance().Get(name) == nullptr)
{ {
const char *opts[] = {"--gpu-architecture=compute_35", const char* opts[] = {"--gpu-architecture=compute_35",
"--relocatable-device-code=true"}; "--relocatable-device-code=true"};
std::string kernel; std::string kernel;
Cuda_kernel_builder::get_1_element_op(name, "float", "fabsf",kernel); Cuda_kernel_builder::Get_1_element_op(name, "float", "fabsf", kernel);
Cuda_function_pool::Instance().set(name, Cuda_function_builder(name, kernel, 2, opts)); Cuda_function_pool::Instance().Set(
name, Cuda_function_builder::Get(name, kernel, 2, opts));
} }
//convert runtime ptr to driver api ptr //convert runtime ptr to driver api ptr
...@@ -59,13 +57,19 @@ namespace ngraph ...@@ -59,13 +57,19 @@ namespace ngraph
dPtrIn = (CUdeviceptr)in; dPtrIn = (CUdeviceptr)in;
dPtrOut = (CUdeviceptr)out; dPtrOut = (CUdeviceptr)out;
void *argsList[] = {&dPtrIn, &dPtrOut, &count}; void* argsList[] = {&dPtrIn, &dPtrOut, &count};
CUDA_SAFE_CALL( CUDA_SAFE_CALL(
cuLaunchKernel(cudCuda_function_pool::Instance().get(name).get(), cuLaunchKernel(*Cuda_function_pool::Instance().Get(name).get(),
count ,1, 1, // grid dim count,
1, 1, 1, // block dim 1,
0, NULL, // shared mem and stream 1, // grid dim
argsList, 0)); // arguments 1,
1,
1, // block dim
0,
NULL, // shared mem and stream
argsList,
0)); // arguments
CUDA_SAFE_CALL(cuCtxSynchronize()); // Retrieve and print output. CUDA_SAFE_CALL(cuCtxSynchronize()); // Retrieve and print output.
} }
...@@ -92,8 +96,7 @@ namespace ngraph ...@@ -92,8 +96,7 @@ namespace ngraph
{ {
} }
void emit_replace_slice( void emit_replace_slice(codegen::CodeWriter& writer,
codegen::CodeWriter& writer,
const std::string& element_type, const std::string& element_type,
const std::string& arg0, // replacement context const std::string& arg0, // replacement context
const std::string& arg1, // replacement value const std::string& arg1, // replacement value
...@@ -137,7 +140,6 @@ namespace ngraph ...@@ -137,7 +140,6 @@ namespace ngraph
const AxisSet& reduction_axes) const AxisSet& reduction_axes)
{ {
} }
} }
} }
} }
......
...@@ -17,20 +17,20 @@ ...@@ -17,20 +17,20 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cublas_v2.h> #include <cublas_v2.h>
#include <cublas_v2.h>
#include <cuda.h>
#include <cuda.h> #include <cuda.h>
#include <cuda_runtime.h> #include <cuda_runtime.h>
#include <cuda_runtime.h>
#include <cudnn_v7.h>
#include <cudnn_v7.h> #include <cudnn_v7.h>
#include <iostream> #include <iostream>
#include <nvrtc.h> #include <nvrtc.h>
#include <nvrtc.h>
#include <string> #include <string>
#include <typeindex> #include <typeindex>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <nvrtc.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <cudnn_v7.h>
#include "ngraph/node.hpp" #include "ngraph/node.hpp"
#include "ngraph/ops/broadcast.hpp" #include "ngraph/ops/broadcast.hpp"
...@@ -48,9 +48,9 @@ ...@@ -48,9 +48,9 @@
#include "ngraph/ops/reverse.hpp" #include "ngraph/ops/reverse.hpp"
#include "ngraph/ops/slice.hpp" #include "ngraph/ops/slice.hpp"
#include "ngraph/ops/sum.hpp" #include "ngraph/ops/sum.hpp"
#include "ngraph/runtime/gpu/gpu_cuda_kernel_emitters.hpp"
#include "ngraph/runtime/gpu/gpu_emitter.hpp" #include "ngraph/runtime/gpu/gpu_emitter.hpp"
#include "ngraph/runtime/gpu/gpu_kernel_emitters.hpp" #include "ngraph/runtime/gpu/gpu_kernel_emitters.hpp"
#include "ngraph/runtime/gpu/gpu_cuda_kernel_emitters.hpp"
#include "ngraph/util.hpp" #include "ngraph/util.hpp"
using namespace std; using namespace std;
...@@ -95,7 +95,8 @@ void runtime::gpu::GPU_Emitter::EmitAbs(codegen::CodeWriter& writer, ...@@ -95,7 +95,8 @@ void runtime::gpu::GPU_Emitter::EmitAbs(codegen::CodeWriter& writer,
writer.indent++; writer.indent++;
writer << "int count = " << out[0].get_size() << ";\n"; writer << "int count = " << out[0].get_size() << ";\n";
writer << "if(count == 0) return;\n"; writer << "if(count == 0) return;\n";
writer << "ngraph::runtime::gpu::cuda::kernel::emit_abs((void*) " << args[0].get_name() << ", (void*) " << out[0].get_name() << ", count);\n"; writer << "ngraph::runtime::gpu::cuda::kernel::emit_abs((void*) " << args[0].get_name()
<< ", (void*) " << out[0].get_name() << ", count);\n";
writer.indent--; writer.indent--;
writer << "}\n"; writer << "}\n";
} }
...@@ -205,7 +206,7 @@ void runtime::gpu::GPU_Emitter::EmitDot(codegen::CodeWriter& writer, ...@@ -205,7 +206,7 @@ void runtime::gpu::GPU_Emitter::EmitDot(codegen::CodeWriter& writer,
else if ((arg0_shape.size() == 2) && (arg1_shape.size() == 2)) else if ((arg0_shape.size() == 2) && (arg1_shape.size() == 2))
{ {
// GEMM Call // GEMM Call
if(arg0_shape[0] != out[0].get_shape()[0] || // m if (arg0_shape[0] != out[0].get_shape()[0] || // m
arg1_shape[1] != out[0].get_shape()[1] || // n arg1_shape[1] != out[0].get_shape()[1] || // n
arg0_shape[1] != arg1_shape[0]) // k arg0_shape[1] != arg1_shape[0]) // k
{ {
......
...@@ -94,10 +94,10 @@ ...@@ -94,10 +94,10 @@
#include "ngraph/pass/memory_layout.hpp" #include "ngraph/pass/memory_layout.hpp"
#include "ngraph/runtime/gpu/gpu_backend.hpp" #include "ngraph/runtime/gpu/gpu_backend.hpp"
#include "ngraph/runtime/gpu/gpu_call_frame.hpp" #include "ngraph/runtime/gpu/gpu_call_frame.hpp"
#include "ngraph/runtime/gpu/gpu_cuda_kernel_emitters.hpp"
#include "ngraph/runtime/gpu/gpu_emitter.hpp" #include "ngraph/runtime/gpu/gpu_emitter.hpp"
#include "ngraph/runtime/gpu/gpu_external_function.hpp" #include "ngraph/runtime/gpu/gpu_external_function.hpp"
#include "ngraph/runtime/gpu/gpu_kernel_emitters.hpp" #include "ngraph/runtime/gpu/gpu_kernel_emitters.hpp"
#include "ngraph/runtime/gpu/gpu_cuda_kernel_emitters.hpp"
using namespace std; using namespace std;
using namespace ngraph; using namespace ngraph;
...@@ -249,17 +249,17 @@ void runtime::gpu::GPU_ExternalFunction::compile() ...@@ -249,17 +249,17 @@ void runtime::gpu::GPU_ExternalFunction::compile()
#include "ngraph/pass/manager.hpp" #include "ngraph/pass/manager.hpp"
#include "ngraph/pass/memory_layout.hpp" #include "ngraph/pass/memory_layout.hpp"
#include "ngraph/runtime/aligned_buffer.hpp" #include "ngraph/runtime/aligned_buffer.hpp"
#include "ngraph/runtime/gpu/gpu_cuda_kernel_emitters.hpp"
#include "ngraph/runtime/gpu/gpu_util.hpp" #include "ngraph/runtime/gpu/gpu_util.hpp"
#include "ngraph/util.hpp" #include "ngraph/util.hpp"
#include "ngraph/runtime/gpu/gpu_cuda_kernel_emitters.hpp"
)"; )";
string pch_header_source = writer.get_code(); string pch_header_source = writer.get_code();
// writer += R"( // writer += R"(
// using namespace ngraph; // using namespace ngraph;
// using namespace std; // using namespace std;
//)"; //)";
if (m_emit_timing) if (m_emit_timing)
{ {
......
...@@ -17,26 +17,27 @@ ...@@ -17,26 +17,27 @@
#pragma once #pragma once
#define NVRTC_SAFE_CALL(x) \ #define NVRTC_SAFE_CALL(x) \
do { \ do \
{ \
nvrtcResult result = x; \ nvrtcResult result = x; \
if (result != NVRTC_SUCCESS) { \ if (result != NVRTC_SUCCESS) \
std::cerr << "\nerror: " #x " failed with error " \ { \
<< nvrtcGetErrorString(result) << '\n'; \ throw std::runtime_error("\nerror: " #x " failed with error " + \
exit(1); \ std::string(nvrtcGetErrorString(result))); \
} \ } \
} while(0) } while (0)
#define CUDA_SAFE_CALL(x) \ #define CUDA_SAFE_CALL(x) \
do { \ do \
{ \
CUresult result = x; \ CUresult result = x; \
if (result != CUDA_SUCCESS) { \ if (result != CUDA_SUCCESS) \
const char *msg; \ { \
const char* msg; \
cuGetErrorName(result, &msg); \ cuGetErrorName(result, &msg); \
std::cerr << "\nerror: " #x " failed with error " \ throw std::runtime_error("\nerror: " #x " failed with error " + std::string(msg)); \
<< msg << '\n'; \
exit(1); \
} \ } \
} while(0) } while (0)
namespace ngraph namespace ngraph
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment