Move common static helpers from codegen to the macro-assembler files.

Review URL: http://codereview.chromium.org/4654002

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5807 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 87834f8c
......@@ -557,7 +557,7 @@ void CodeGenerator::Load(Expression* expr) {
void CodeGenerator::LoadGlobal() {
Register reg = frame_->GetTOSRegister();
__ ldr(reg, GlobalObject());
__ ldr(reg, GlobalObjectOperand());
frame_->EmitPush(reg);
}
......@@ -5135,11 +5135,11 @@ class DeferredIsStringWrapperSafeForDefaultValueOf : public DeferredCode {
__ b(eq, &false_result);
__ ldr(scratch1_, FieldMemOperand(scratch1_, HeapObject::kMapOffset));
__ ldr(scratch2_,
CodeGenerator::ContextOperand(cp, Context::GLOBAL_INDEX));
ContextOperand(cp, Context::GLOBAL_INDEX));
__ ldr(scratch2_,
FieldMemOperand(scratch2_, GlobalObject::kGlobalContextOffset));
__ ldr(scratch2_,
CodeGenerator::ContextOperand(
ContextOperand(
scratch2_, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX));
__ cmp(scratch1_, scratch2_);
__ b(ne, &false_result);
......@@ -5825,7 +5825,7 @@ void CodeGenerator::VisitCallRuntime(CallRuntime* node) {
// Prepare stack for calling JS runtime function.
// Push the builtins object found in the current global object.
Register scratch = VirtualFrame::scratch0();
__ ldr(scratch, GlobalObject());
__ ldr(scratch, GlobalObjectOperand());
Register builtins = frame_->GetTOSRegister();
__ ldr(builtins, FieldMemOperand(scratch, GlobalObject::kBuiltinsOffset));
frame_->EmitPush(builtins);
......
......@@ -279,10 +279,6 @@ class CodeGenerator: public AstVisitor {
return inlined_write_barrier_size_ + 4;
}
static MemOperand ContextOperand(Register context, int index) {
return MemOperand(context, Context::SlotOffset(index));
}
private:
// Type of a member function that generates inline code for a native function.
typedef void (CodeGenerator::*InlineFunctionGenerator)
......@@ -349,10 +345,6 @@ class CodeGenerator: public AstVisitor {
JumpTarget* slow);
// Expressions
static MemOperand GlobalObject() {
return ContextOperand(cp, Context::GLOBAL_INDEX);
}
void LoadCondition(Expression* x,
JumpTarget* true_target,
JumpTarget* false_target,
......
......@@ -1019,7 +1019,7 @@ void FullCodeGenerator::EmitLoadGlobalSlotCheckExtensions(
__ bind(&fast);
}
__ ldr(r0, CodeGenerator::GlobalObject());
__ ldr(r0, GlobalObjectOperand());
__ mov(r2, Operand(slot->var()->name()));
RelocInfo::Mode mode = (typeof_state == INSIDE_TYPEOF)
? RelocInfo::CODE_TARGET
......@@ -1040,7 +1040,7 @@ void FullCodeGenerator::EmitVariableLoad(Variable* var) {
Comment cmnt(masm_, "Global variable");
// Use inline caching. Variable name is passed in r2 and the global
// object (receiver) in r0.
__ ldr(r0, CodeGenerator::GlobalObject());
__ ldr(r0, GlobalObjectOperand());
__ mov(r2, Operand(var->name()));
Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
EmitCallIC(ic, RelocInfo::CODE_TARGET_CONTEXT);
......@@ -1514,7 +1514,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
// assignment. Right-hand-side value is passed in r0, variable name in
// r2, and the global object in r1.
__ mov(r2, Operand(var->name()));
__ ldr(r1, CodeGenerator::GlobalObject());
__ ldr(r1, GlobalObjectOperand());
Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize));
EmitCallIC(ic, RelocInfo::CODE_TARGET);
......@@ -1809,7 +1809,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
context()->DropAndPlug(1, r0);
} else if (var != NULL && !var->is_this() && var->is_global()) {
// Push global object as receiver for the call IC.
__ ldr(r0, CodeGenerator::GlobalObject());
__ ldr(r0, GlobalObjectOperand());
__ push(r0);
EmitCallWithIC(expr, var->name(), RelocInfo::CODE_TARGET_CONTEXT);
} else if (var != NULL && var->AsSlot() != NULL &&
......@@ -1845,7 +1845,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
// Push function.
__ push(r0);
// Push global receiver.
__ ldr(r1, CodeGenerator::GlobalObject());
__ ldr(r1, GlobalObjectOperand());
__ ldr(r1, FieldMemOperand(r1, GlobalObject::kGlobalReceiverOffset));
__ push(r1);
__ bind(&call);
......@@ -1879,7 +1879,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize));
EmitCallIC(ic, RelocInfo::CODE_TARGET);
__ ldr(r1, CodeGenerator::GlobalObject());
__ ldr(r1, GlobalObjectOperand());
__ ldr(r1, FieldMemOperand(r1, GlobalObject::kGlobalReceiverOffset));
__ Push(r0, r1); // Function, receiver.
EmitCallWithStub(expr);
......@@ -1902,7 +1902,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
VisitForStackValue(fun);
}
// Load global receiver object.
__ ldr(r1, CodeGenerator::GlobalObject());
__ ldr(r1, GlobalObjectOperand());
__ ldr(r1, FieldMemOperand(r1, GlobalObject::kGlobalReceiverOffset));
__ push(r1);
// Emit function call.
......@@ -2780,7 +2780,7 @@ void FullCodeGenerator::VisitCallRuntime(CallRuntime* expr) {
if (expr->is_jsruntime()) {
// Prepare for calling JS runtime function.
__ ldr(r0, CodeGenerator::GlobalObject());
__ ldr(r0, GlobalObjectOperand());
__ ldr(r0, FieldMemOperand(r0, GlobalObject::kBuiltinsOffset));
__ push(r0);
}
......@@ -2832,7 +2832,7 @@ void FullCodeGenerator::VisitUnaryOperation(UnaryOperation* expr) {
VisitForStackValue(prop->obj());
VisitForStackValue(prop->key());
} else if (var->is_global()) {
__ ldr(r1, CodeGenerator::GlobalObject());
__ ldr(r1, GlobalObjectOperand());
__ mov(r0, Operand(var->name()));
__ Push(r1, r0);
} else {
......@@ -3098,7 +3098,7 @@ void FullCodeGenerator::VisitForTypeofValue(Expression* expr) {
VariableProxy* proxy = expr->AsVariableProxy();
if (proxy != NULL && !proxy->var()->is_this() && proxy->var()->is_global()) {
Comment cmnt(masm_, "Global variable");
__ ldr(r0, CodeGenerator::GlobalObject());
__ ldr(r0, GlobalObjectOperand());
__ mov(r2, Operand(proxy->name()));
Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
// Use a regular load, not a contextual load, to avoid a reference
......
......@@ -727,6 +727,16 @@ class CodePatcher {
// -----------------------------------------------------------------------------
// Static helper functions.
static MemOperand ContextOperand(Register context, int index) {
return MemOperand(context, Context::SlotOffset(index));
}
static inline MemOperand GlobalObjectOperand() {
return ContextOperand(cp, Context::GLOBAL_INDEX);
}
#ifdef GENERATED_CODE_COVERAGE
#define CODE_COVERAGE_STRINGIFY(x) #x
#define CODE_COVERAGE_TOSTRING(x) CODE_COVERAGE_STRINGIFY(x)
......
......@@ -301,11 +301,6 @@ bool FullCodeGenerator::MakeCode(CompilationInfo* info) {
}
MemOperand FullCodeGenerator::ContextOperand(Register context, int index) {
return CodeGenerator::ContextOperand(context, index);
}
int FullCodeGenerator::SlotOffset(Slot* slot) {
ASSERT(slot != NULL);
// Offset is negative because higher indexes are at lower addresses.
......
......@@ -464,9 +464,6 @@ class FullCodeGenerator: public AstVisitor {
// in v8::internal::Context.
void LoadContextField(Register dst, int context_index);
// Create an operand for a context field.
MemOperand ContextOperand(Register context, int context_index);
// AST node visit functions.
#define DECLARE_VISIT(type) virtual void Visit##type(type* node);
AST_NODE_LIST(DECLARE_VISIT)
......
......@@ -686,10 +686,10 @@ void CodeGenerator::Load(Expression* expr) {
void CodeGenerator::LoadGlobal() {
if (in_spilled_code()) {
frame_->EmitPush(GlobalObject());
frame_->EmitPush(GlobalObjectOperand());
} else {
Result temp = allocator_->Allocate();
__ mov(temp.reg(), GlobalObject());
__ mov(temp.reg(), GlobalObjectOperand());
frame_->Push(&temp);
}
}
......@@ -698,7 +698,7 @@ void CodeGenerator::LoadGlobal() {
void CodeGenerator::LoadGlobalReceiver() {
Result temp = allocator_->Allocate();
Register reg = temp.reg();
__ mov(reg, GlobalObject());
__ mov(reg, GlobalObjectOperand());
__ mov(reg, FieldOperand(reg, GlobalObject::kGlobalReceiverOffset));
frame_->Push(&temp);
}
......@@ -6778,8 +6778,8 @@ class DeferredIsStringWrapperSafeForDefaultValueOf : public DeferredCode {
__ mov(scratch2_,
FieldOperand(scratch2_, GlobalObject::kGlobalContextOffset));
__ cmp(scratch1_,
CodeGenerator::ContextOperand(
scratch2_, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX));
ContextOperand(scratch2_,
Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX));
__ j(not_equal, &false_result);
// Set the bit in the map to indicate that it has been checked safe for
// default valueOf and set true result.
......@@ -7934,7 +7934,7 @@ void CodeGenerator::VisitCallRuntime(CallRuntime* node) {
// Push the builtins object found in the current global object.
Result temp = allocator()->Allocate();
ASSERT(temp.is_valid());
__ mov(temp.reg(), GlobalObject());
__ mov(temp.reg(), GlobalObjectOperand());
__ mov(temp.reg(), FieldOperand(temp.reg(), GlobalObject::kBuiltinsOffset));
frame_->Push(&temp);
}
......
......@@ -352,10 +352,6 @@ class CodeGenerator: public AstVisitor {
return FieldOperand(array, index_as_smi, times_half_pointer_size, offset);
}
static Operand ContextOperand(Register context, int index) {
return Operand(context, Context::SlotOffset(index));
}
private:
// Type of a member function that generates inline code for a native function.
typedef void (CodeGenerator::*InlineFunctionGenerator)
......@@ -441,10 +437,6 @@ class CodeGenerator: public AstVisitor {
JumpTarget* slow);
// Expressions
static Operand GlobalObject() {
return ContextOperand(esi, Context::GLOBAL_INDEX);
}
void LoadCondition(Expression* expr,
ControlDestination* destination,
bool force_control);
......
......@@ -954,7 +954,7 @@ void FullCodeGenerator::EmitLoadGlobalSlotCheckExtensions(
// All extension objects were empty and it is safe to use a global
// load IC call.
__ mov(eax, CodeGenerator::GlobalObject());
__ mov(eax, GlobalObjectOperand());
__ mov(ecx, slot->var()->name());
Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
RelocInfo::Mode mode = (typeof_state == INSIDE_TYPEOF)
......@@ -1057,7 +1057,7 @@ void FullCodeGenerator::EmitVariableLoad(Variable* var) {
Comment cmnt(masm_, "Global variable");
// Use inline caching. Variable name is passed in ecx and the global
// object on the stack.
__ mov(eax, CodeGenerator::GlobalObject());
__ mov(eax, GlobalObjectOperand());
__ mov(ecx, var->name());
Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
EmitCallIC(ic, RelocInfo::CODE_TARGET_CONTEXT);
......@@ -1834,7 +1834,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
// assignment. Right-hand-side value is passed in eax, variable name in
// ecx, and the global object on the stack.
__ mov(ecx, var->name());
__ mov(edx, CodeGenerator::GlobalObject());
__ mov(edx, GlobalObjectOperand());
Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize));
EmitCallIC(ic, RelocInfo::CODE_TARGET);
......@@ -2109,7 +2109,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
context()->DropAndPlug(1, eax);
} else if (var != NULL && !var->is_this() && var->is_global()) {
// Push global object as receiver for the call IC.
__ push(CodeGenerator::GlobalObject());
__ push(GlobalObjectOperand());
EmitCallWithIC(expr, var->name(), RelocInfo::CODE_TARGET_CONTEXT);
} else if (var != NULL && var->AsSlot() != NULL &&
var->AsSlot()->type() == Slot::LOOKUP) {
......@@ -2144,7 +2144,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
// Push function.
__ push(eax);
// Push global receiver.
__ mov(ebx, CodeGenerator::GlobalObject());
__ mov(ebx, GlobalObjectOperand());
__ push(FieldOperand(ebx, GlobalObject::kGlobalReceiverOffset));
__ bind(&call);
}
......@@ -2178,7 +2178,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
// Push result (function).
__ push(eax);
// Push Global receiver.
__ mov(ecx, CodeGenerator::GlobalObject());
__ mov(ecx, GlobalObjectOperand());
__ push(FieldOperand(ecx, GlobalObject::kGlobalReceiverOffset));
EmitCallWithStub(expr);
} else {
......@@ -2199,7 +2199,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
VisitForStackValue(fun);
}
// Load global receiver object.
__ mov(ebx, CodeGenerator::GlobalObject());
__ mov(ebx, GlobalObjectOperand());
__ push(FieldOperand(ebx, GlobalObject::kGlobalReceiverOffset));
// Emit function call.
EmitCallWithStub(expr);
......@@ -3089,7 +3089,7 @@ void FullCodeGenerator::VisitCallRuntime(CallRuntime* expr) {
if (expr->is_jsruntime()) {
// Prepare for calling JS runtime function.
__ mov(eax, CodeGenerator::GlobalObject());
__ mov(eax, GlobalObjectOperand());
__ push(FieldOperand(eax, GlobalObject::kBuiltinsOffset));
}
......@@ -3140,7 +3140,7 @@ void FullCodeGenerator::VisitUnaryOperation(UnaryOperation* expr) {
VisitForStackValue(prop->obj());
VisitForStackValue(prop->key());
} else if (var->is_global()) {
__ push(CodeGenerator::GlobalObject());
__ push(GlobalObjectOperand());
__ push(Immediate(var->name()));
} else {
// Non-global variable. Call the runtime to look up the context
......@@ -3418,7 +3418,7 @@ void FullCodeGenerator::VisitForTypeofValue(Expression* expr) {
if (proxy != NULL && !proxy->var()->is_this() && proxy->var()->is_global()) {
Comment cmnt(masm_, "Global variable");
__ mov(eax, CodeGenerator::GlobalObject());
__ mov(eax, GlobalObjectOperand());
__ mov(ecx, Immediate(proxy->name()));
Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
// Use a regular load, not a contextual load, to avoid a reference
......
......@@ -642,6 +642,17 @@ static inline Operand FieldOperand(Register object,
return Operand(object, index, scale, offset - kHeapObjectTag);
}
static inline Operand ContextOperand(Register context, int index) {
return Operand(context, Context::SlotOffset(index));
}
static inline Operand GlobalObjectOperand() {
return ContextOperand(esi, Context::GLOBAL_INDEX);
}
// Generates an Operand for saving parameters after PrepareCallApiFunction.
Operand ApiParameterOperand(int index);
......
......@@ -568,10 +568,10 @@ void CodeGenerator::Load(Expression* expr) {
void CodeGenerator::LoadGlobal() {
if (in_spilled_code()) {
frame_->EmitPush(GlobalObject());
frame_->EmitPush(GlobalObjectOperand());
} else {
Result temp = allocator_->Allocate();
__ movq(temp.reg(), GlobalObject());
__ movq(temp.reg(), GlobalObjectOperand());
frame_->Push(&temp);
}
}
......@@ -580,7 +580,7 @@ void CodeGenerator::LoadGlobal() {
void CodeGenerator::LoadGlobalReceiver() {
Result temp = allocator_->Allocate();
Register reg = temp.reg();
__ movq(reg, GlobalObject());
__ movq(reg, GlobalObjectOperand());
__ movq(reg, FieldOperand(reg, GlobalObject::kGlobalReceiverOffset));
frame_->Push(&temp);
}
......@@ -6062,7 +6062,7 @@ class DeferredIsStringWrapperSafeForDefaultValueOf : public DeferredCode {
__ movq(scratch2_,
FieldOperand(scratch2_, GlobalObject::kGlobalContextOffset));
__ cmpq(scratch1_,
CodeGenerator::ContextOperand(
ContextOperand(
scratch2_, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX));
__ j(not_equal, &false_result);
// Set the bit in the map to indicate that it has been checked safe for
......@@ -7219,7 +7219,7 @@ void CodeGenerator::VisitCallRuntime(CallRuntime* node) {
// Push the builtins object found in the current global object.
Result temp = allocator()->Allocate();
ASSERT(temp.is_valid());
__ movq(temp.reg(), GlobalObject());
__ movq(temp.reg(), GlobalObjectOperand());
__ movq(temp.reg(),
FieldOperand(temp.reg(), GlobalObject::kBuiltinsOffset));
frame_->Push(&temp);
......
......@@ -341,10 +341,6 @@ class CodeGenerator: public AstVisitor {
bool in_spilled_code() const { return in_spilled_code_; }
void set_in_spilled_code(bool flag) { in_spilled_code_ = flag; }
static Operand ContextOperand(Register context, int index) {
return Operand(context, Context::SlotOffset(index));
}
private:
// Type of a member function that generates inline code for a native function.
typedef void (CodeGenerator::*InlineFunctionGenerator)
......@@ -417,10 +413,6 @@ class CodeGenerator: public AstVisitor {
JumpTarget* slow);
// Expressions
static Operand GlobalObject() {
return ContextOperand(rsi, Context::GLOBAL_INDEX);
}
void LoadCondition(Expression* x,
ControlDestination* destination,
bool force_control);
......
......@@ -912,7 +912,7 @@ void FullCodeGenerator::EmitLoadGlobalSlotCheckExtensions(
// All extension objects were empty and it is safe to use a global
// load IC call.
__ movq(rax, CodeGenerator::GlobalObject());
__ movq(rax, GlobalObjectOperand());
__ Move(rcx, slot->var()->name());
Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
RelocInfo::Mode mode = (typeof_state == INSIDE_TYPEOF)
......@@ -1016,7 +1016,7 @@ void FullCodeGenerator::EmitVariableLoad(Variable* var) {
// Use inline caching. Variable name is passed in rcx and the global
// object on the stack.
__ Move(rcx, var->name());
__ movq(rax, CodeGenerator::GlobalObject());
__ movq(rax, GlobalObjectOperand());
Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
EmitCallIC(ic, RelocInfo::CODE_TARGET_CONTEXT);
context()->Plug(rax);
......@@ -1555,7 +1555,7 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var,
// assignment. Right-hand-side value is passed in rax, variable name in
// rcx, and the global object on the stack.
__ Move(rcx, var->name());
__ movq(rdx, CodeGenerator::GlobalObject());
__ movq(rdx, GlobalObjectOperand());
Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize));
EmitCallIC(ic, RelocInfo::CODE_TARGET);
......@@ -1834,7 +1834,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
} else if (var != NULL && !var->is_this() && var->is_global()) {
// Call to a global variable.
// Push global object as receiver for the call IC lookup.
__ push(CodeGenerator::GlobalObject());
__ push(GlobalObjectOperand());
EmitCallWithIC(expr, var->name(), RelocInfo::CODE_TARGET_CONTEXT);
} else if (var != NULL && var->AsSlot() != NULL &&
var->AsSlot()->type() == Slot::LOOKUP) {
......@@ -1868,7 +1868,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
// Push function.
__ push(rax);
// Push global receiver.
__ movq(rbx, CodeGenerator::GlobalObject());
__ movq(rbx, GlobalObjectOperand());
__ push(FieldOperand(rbx, GlobalObject::kGlobalReceiverOffset));
__ bind(&call);
}
......@@ -1907,7 +1907,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
// Push result (function).
__ push(rax);
// Push receiver object on stack.
__ movq(rcx, CodeGenerator::GlobalObject());
__ movq(rcx, GlobalObjectOperand());
__ push(FieldOperand(rcx, GlobalObject::kGlobalReceiverOffset));
EmitCallWithStub(expr);
} else {
......@@ -1928,7 +1928,7 @@ void FullCodeGenerator::VisitCall(Call* expr) {
VisitForStackValue(fun);
}
// Load global receiver object.
__ movq(rbx, CodeGenerator::GlobalObject());
__ movq(rbx, GlobalObjectOperand());
__ push(FieldOperand(rbx, GlobalObject::kGlobalReceiverOffset));
// Emit function call.
EmitCallWithStub(expr);
......@@ -2801,7 +2801,7 @@ void FullCodeGenerator::VisitCallRuntime(CallRuntime* expr) {
if (expr->is_jsruntime()) {
// Prepare for calling JS runtime function.
__ movq(rax, CodeGenerator::GlobalObject());
__ movq(rax, GlobalObjectOperand());
__ push(FieldOperand(rax, GlobalObject::kBuiltinsOffset));
}
......@@ -2851,7 +2851,7 @@ void FullCodeGenerator::VisitUnaryOperation(UnaryOperation* expr) {
VisitForStackValue(prop->obj());
VisitForStackValue(prop->key());
} else if (var->is_global()) {
__ push(CodeGenerator::GlobalObject());
__ push(GlobalObjectOperand());
__ Push(var->name());
} else {
// Non-global variable. Call the runtime to look up the context
......@@ -3122,7 +3122,7 @@ void FullCodeGenerator::VisitForTypeofValue(Expression* expr) {
if (proxy != NULL && !proxy->var()->is_this() && proxy->var()->is_global()) {
Comment cmnt(masm_, "Global variable");
__ Move(rcx, proxy->name());
__ movq(rax, CodeGenerator::GlobalObject());
__ movq(rax, GlobalObjectOperand());
Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
// Use a regular load, not a contextual load, to avoid a reference
// error.
......
......@@ -982,6 +982,17 @@ static inline Operand FieldOperand(Register object,
}
static inline Operand ContextOperand(Register context, int index) {
return Operand(context, Context::SlotOffset(index));
}
static inline Operand GlobalObjectOperand() {
return ContextOperand(rsi, Context::GLOBAL_INDEX);
}
#ifdef GENERATED_CODE_COVERAGE
extern void LogGeneratedCodeCoverage(const char* file_line);
#define CODE_COVERAGE_STRINGIFY(x) #x
......
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