Allow bailing out of the register allocator when running out of virtual registers.
1. Instead of checking upfront and estimating a limit for the number, we now are able to stop register allocation and bailout when we don't have enough virtual registers. 2. GCed some out-dated flags from flag-definition.h 3. Simplified the interface from the Lithium builder to the register allocator in lithium-*.cc: For uses and definitions, we just record the virtual register number given by the Hydrogen value id. For temporaries, we request a new virtual register from the allocator. For fixed temps, we don't need to do anything. 4. Increased number of deoptimization entries to 16K. Eventually we probably want to make this array grow dynamically. Review URL: https://chromiumcodereview.appspot.com/9325019 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@10597 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
a297d3f9af
commit
0ec7773680
@ -671,7 +671,7 @@ LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) {
|
||||
HInstruction* instr = HInstruction::cast(value);
|
||||
VisitInstruction(instr);
|
||||
}
|
||||
allocator_->RecordUse(value, operand);
|
||||
operand->set_virtual_register(value->id());
|
||||
return operand;
|
||||
}
|
||||
|
||||
@ -679,7 +679,7 @@ LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) {
|
||||
template<int I, int T>
|
||||
LInstruction* LChunkBuilder::Define(LTemplateInstruction<1, I, T>* instr,
|
||||
LUnallocated* result) {
|
||||
allocator_->RecordDefinition(current_instruction_, result);
|
||||
result->set_virtual_register(current_instruction_->id());
|
||||
instr->set_result(result);
|
||||
return instr;
|
||||
}
|
||||
@ -791,21 +791,22 @@ LInstruction* LChunkBuilder::AssignPointerMap(LInstruction* instr) {
|
||||
|
||||
LUnallocated* LChunkBuilder::TempRegister() {
|
||||
LUnallocated* operand = new LUnallocated(LUnallocated::MUST_HAVE_REGISTER);
|
||||
allocator_->RecordTemporary(operand);
|
||||
operand->set_virtual_register(allocator_->GetVirtualRegister());
|
||||
if (!allocator_->AllocationOk()) Abort("Not enough virtual registers.");
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
||||
LOperand* LChunkBuilder::FixedTemp(Register reg) {
|
||||
LUnallocated* operand = ToUnallocated(reg);
|
||||
allocator_->RecordTemporary(operand);
|
||||
ASSERT(operand->HasFixedPolicy());
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
||||
LOperand* LChunkBuilder::FixedTemp(DoubleRegister reg) {
|
||||
LUnallocated* operand = ToUnallocated(reg);
|
||||
allocator_->RecordTemporary(operand);
|
||||
ASSERT(operand->HasFixedPolicy());
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
@ -194,7 +194,7 @@ static bool MakeCrankshaftCode(CompilationInfo* info) {
|
||||
// Fall back to using the full code generator if it's not possible
|
||||
// to use the Hydrogen-based optimizing compiler. We already have
|
||||
// generated code for this from the shared function object.
|
||||
if (AlwaysFullCompiler() || !FLAG_use_hydrogen) {
|
||||
if (AlwaysFullCompiler()) {
|
||||
info->SetCode(code);
|
||||
return true;
|
||||
}
|
||||
@ -291,7 +291,7 @@ static bool MakeCrankshaftCode(CompilationInfo* info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (graph != NULL && FLAG_build_lithium) {
|
||||
if (graph != NULL) {
|
||||
Handle<Code> optimized_code = graph->Compile(info);
|
||||
if (!optimized_code.is_null()) {
|
||||
info->SetCode(optimized_code);
|
||||
|
@ -270,7 +270,7 @@ class Deoptimizer : public Malloced {
|
||||
#ifdef V8_TARGET_ARCH_MIPS
|
||||
static const int kNumberOfEntries = 4096;
|
||||
#else
|
||||
static const int kNumberOfEntries = 8192;
|
||||
static const int kNumberOfEntries = 16384;
|
||||
#endif
|
||||
|
||||
Deoptimizer(Isolate* isolate,
|
||||
|
@ -130,10 +130,6 @@ DEFINE_bool(string_slices, true, "use string slices")
|
||||
// Flags for Crankshaft.
|
||||
DEFINE_bool(crankshaft, true, "use crankshaft")
|
||||
DEFINE_string(hydrogen_filter, "", "hydrogen use/trace filter")
|
||||
DEFINE_bool(use_hydrogen, true, "use generated hydrogen for compilation")
|
||||
DEFINE_bool(build_lithium, true, "use lithium chunk builder")
|
||||
DEFINE_bool(alloc_lithium, true, "use lithium register allocator")
|
||||
DEFINE_bool(use_lithium, true, "use lithium code generator")
|
||||
DEFINE_bool(use_range, true, "use hydrogen range analysis")
|
||||
DEFINE_bool(eliminate_dead_phis, true, "eliminate dead phis")
|
||||
DEFINE_bool(use_gvn, true, "use hydrogen global value numbering")
|
||||
|
@ -625,25 +625,23 @@ HGraph::HGraph(CompilationInfo* info)
|
||||
|
||||
Handle<Code> HGraph::Compile(CompilationInfo* info) {
|
||||
int values = GetMaximumValueID();
|
||||
if (values > LAllocator::max_initial_value_ids()) {
|
||||
if (values > LUnallocated::kMaxVirtualRegisters) {
|
||||
if (FLAG_trace_bailout) {
|
||||
SmartArrayPointer<char> name(
|
||||
info->shared_info()->DebugName()->ToCString());
|
||||
PrintF("Function @\"%s\" is too big.\n", *name);
|
||||
PrintF("Not enough virtual registers for (values).\n");
|
||||
}
|
||||
return Handle<Code>::null();
|
||||
}
|
||||
|
||||
LAllocator allocator(values, this);
|
||||
LChunkBuilder builder(info, this, &allocator);
|
||||
LChunk* chunk = builder.Build();
|
||||
if (chunk == NULL) return Handle<Code>::null();
|
||||
|
||||
if (!FLAG_alloc_lithium) return Handle<Code>::null();
|
||||
|
||||
allocator.Allocate(chunk);
|
||||
|
||||
if (!FLAG_use_lithium) return Handle<Code>::null();
|
||||
if (!allocator.Allocate(chunk)) {
|
||||
if (FLAG_trace_bailout) {
|
||||
PrintF("Not enough virtual registers (regalloc).\n");
|
||||
}
|
||||
return Handle<Code>::null();
|
||||
}
|
||||
|
||||
MacroAssembler assembler(info->isolate(), NULL, 0);
|
||||
LCodeGen generator(chunk, &assembler, info);
|
||||
|
@ -670,7 +670,7 @@ LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) {
|
||||
HInstruction* instr = HInstruction::cast(value);
|
||||
VisitInstruction(instr);
|
||||
}
|
||||
allocator_->RecordUse(value, operand);
|
||||
operand->set_virtual_register(value->id());
|
||||
return operand;
|
||||
}
|
||||
|
||||
@ -678,7 +678,7 @@ LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) {
|
||||
template<int I, int T>
|
||||
LInstruction* LChunkBuilder::Define(LTemplateInstruction<1, I, T>* instr,
|
||||
LUnallocated* result) {
|
||||
allocator_->RecordDefinition(current_instruction_, result);
|
||||
result->set_virtual_register(current_instruction_->id());
|
||||
instr->set_result(result);
|
||||
return instr;
|
||||
}
|
||||
@ -796,21 +796,22 @@ LInstruction* LChunkBuilder::AssignPointerMap(LInstruction* instr) {
|
||||
LUnallocated* LChunkBuilder::TempRegister() {
|
||||
LUnallocated* operand =
|
||||
new(zone()) LUnallocated(LUnallocated::MUST_HAVE_REGISTER);
|
||||
allocator_->RecordTemporary(operand);
|
||||
operand->set_virtual_register(allocator_->GetVirtualRegister());
|
||||
if (!allocator_->AllocationOk()) Abort("Not enough virtual registers (temps).");
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
||||
LOperand* LChunkBuilder::FixedTemp(Register reg) {
|
||||
LUnallocated* operand = ToUnallocated(reg);
|
||||
allocator_->RecordTemporary(operand);
|
||||
ASSERT(operand->HasFixedPolicy());
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
||||
LOperand* LChunkBuilder::FixedTemp(XMMRegister reg) {
|
||||
LUnallocated* operand = ToUnallocated(reg);
|
||||
allocator_->RecordTemporary(operand);
|
||||
ASSERT(operand->HasFixedPolicy());
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
@ -546,6 +546,7 @@ LifetimePosition LiveRange::FirstIntersection(LiveRange* other) {
|
||||
|
||||
LAllocator::LAllocator(int num_values, HGraph* graph)
|
||||
: chunk_(NULL),
|
||||
allocation_ok_(true),
|
||||
live_in_sets_(graph->blocks()->length()),
|
||||
live_ranges_(num_values * 2),
|
||||
fixed_live_ranges_(NULL),
|
||||
@ -787,6 +788,7 @@ void LAllocator::MeetRegisterConstraints(HBasicBlock* block) {
|
||||
if (i < end) instr = InstructionAt(i + 1);
|
||||
if (i > start) prev_instr = InstructionAt(i - 1);
|
||||
MeetConstraintsBetween(prev_instr, instr, i);
|
||||
if (!AllocationOk()) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -852,7 +854,8 @@ void LAllocator::MeetConstraintsBetween(LInstruction* first,
|
||||
ASSERT(!cur_input->IsUsedAtStart());
|
||||
|
||||
LUnallocated* input_copy = cur_input->CopyUnconstrained();
|
||||
cur_input->set_virtual_register(next_virtual_register_++);
|
||||
cur_input->set_virtual_register(GetVirtualRegister());
|
||||
if(!AllocationOk()) return;
|
||||
|
||||
if (RequiredRegisterKind(input_copy->virtual_register()) ==
|
||||
DOUBLE_REGISTERS) {
|
||||
@ -1069,18 +1072,22 @@ void LAllocator::ResolvePhis(HBasicBlock* block) {
|
||||
}
|
||||
|
||||
|
||||
void LAllocator::Allocate(LChunk* chunk) {
|
||||
bool LAllocator::Allocate(LChunk* chunk) {
|
||||
ASSERT(chunk_ == NULL);
|
||||
chunk_ = chunk;
|
||||
MeetRegisterConstraints();
|
||||
if (!AllocationOk()) return false;
|
||||
ResolvePhis();
|
||||
BuildLiveRanges();
|
||||
AllocateGeneralRegisters();
|
||||
if (!AllocationOk()) return false;
|
||||
AllocateDoubleRegisters();
|
||||
if (!AllocationOk()) return false;
|
||||
PopulatePointerMaps();
|
||||
if (has_osr_entry_) ProcessOsrEntry();
|
||||
ConnectRanges();
|
||||
ResolveControlFlow();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -1091,6 +1098,7 @@ void LAllocator::MeetRegisterConstraints() {
|
||||
for (int i = 0; i < blocks->length(); ++i) {
|
||||
HBasicBlock* block = blocks->at(i);
|
||||
MeetRegisterConstraints(block);
|
||||
if (!AllocationOk()) return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1544,6 +1552,7 @@ void LAllocator::AllocateRegisters() {
|
||||
// Do not spill live range eagerly if use position that can benefit from
|
||||
// the register is too close to the start of live range.
|
||||
SpillBetween(current, current->Start(), pos->pos());
|
||||
if (!AllocationOk()) return;
|
||||
ASSERT(UnhandledIsSorted());
|
||||
continue;
|
||||
}
|
||||
@ -1574,9 +1583,10 @@ void LAllocator::AllocateRegisters() {
|
||||
ASSERT(!current->HasRegisterAssigned() && !current->IsSpilled());
|
||||
|
||||
bool result = TryAllocateFreeReg(current);
|
||||
if (!result) {
|
||||
AllocateBlockedReg(current);
|
||||
}
|
||||
if (!AllocationOk()) return;
|
||||
|
||||
if (!result) AllocateBlockedReg(current);
|
||||
if (!AllocationOk()) return;
|
||||
|
||||
if (current->HasRegisterAssigned()) {
|
||||
AddToActive(current);
|
||||
@ -1630,29 +1640,6 @@ RegisterKind LAllocator::RequiredRegisterKind(int virtual_register) const {
|
||||
}
|
||||
|
||||
|
||||
void LAllocator::RecordDefinition(HInstruction* instr, LUnallocated* operand) {
|
||||
operand->set_virtual_register(instr->id());
|
||||
}
|
||||
|
||||
|
||||
void LAllocator::RecordTemporary(LUnallocated* operand) {
|
||||
ASSERT(next_virtual_register_ < LUnallocated::kMaxVirtualRegisters);
|
||||
if (!operand->HasFixedPolicy()) {
|
||||
operand->set_virtual_register(next_virtual_register_++);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LAllocator::RecordUse(HValue* value, LUnallocated* operand) {
|
||||
operand->set_virtual_register(value->id());
|
||||
}
|
||||
|
||||
|
||||
int LAllocator::max_initial_value_ids() {
|
||||
return LUnallocated::kMaxVirtualRegisters / 16;
|
||||
}
|
||||
|
||||
|
||||
void LAllocator::AddToActive(LiveRange* range) {
|
||||
TraceAlloc("Add live range %d to active\n", range->id());
|
||||
active_live_ranges_.Add(range);
|
||||
@ -1847,7 +1834,8 @@ bool LAllocator::TryAllocateFreeReg(LiveRange* current) {
|
||||
if (pos.Value() < current->End().Value()) {
|
||||
// Register reg is available at the range start but becomes blocked before
|
||||
// the range end. Split current at position where it becomes blocked.
|
||||
LiveRange* tail = SplitAt(current, pos);
|
||||
LiveRange* tail = SplitRangeAt(current, pos);
|
||||
if (!AllocationOk()) return false;
|
||||
AddToUnhandledSorted(tail);
|
||||
}
|
||||
|
||||
@ -2002,7 +1990,7 @@ bool LAllocator::IsBlockBoundary(LifetimePosition pos) {
|
||||
}
|
||||
|
||||
|
||||
LiveRange* LAllocator::SplitAt(LiveRange* range, LifetimePosition pos) {
|
||||
LiveRange* LAllocator::SplitRangeAt(LiveRange* range, LifetimePosition pos) {
|
||||
ASSERT(!range->IsFixed());
|
||||
TraceAlloc("Splitting live range %d at %d\n", range->id(), pos.Value());
|
||||
|
||||
@ -2013,7 +2001,8 @@ LiveRange* LAllocator::SplitAt(LiveRange* range, LifetimePosition pos) {
|
||||
ASSERT(pos.IsInstructionStart() ||
|
||||
!chunk_->instructions()->at(pos.InstructionIndex())->IsControl());
|
||||
|
||||
LiveRange* result = LiveRangeFor(next_virtual_register_++);
|
||||
LiveRange* result = LiveRangeFor(GetVirtualRegister());
|
||||
if (!AllocationOk()) return NULL;
|
||||
range->SplitAt(pos, result);
|
||||
return result;
|
||||
}
|
||||
@ -2030,7 +2019,7 @@ LiveRange* LAllocator::SplitBetween(LiveRange* range,
|
||||
|
||||
LifetimePosition split_pos = FindOptimalSplitPos(start, end);
|
||||
ASSERT(split_pos.Value() >= start.Value());
|
||||
return SplitAt(range, split_pos);
|
||||
return SplitRangeAt(range, split_pos);
|
||||
}
|
||||
|
||||
|
||||
@ -2069,7 +2058,8 @@ LifetimePosition LAllocator::FindOptimalSplitPos(LifetimePosition start,
|
||||
|
||||
|
||||
void LAllocator::SpillAfter(LiveRange* range, LifetimePosition pos) {
|
||||
LiveRange* second_part = SplitAt(range, pos);
|
||||
LiveRange* second_part = SplitRangeAt(range, pos);
|
||||
if (!AllocationOk()) return;
|
||||
Spill(second_part);
|
||||
}
|
||||
|
||||
@ -2078,7 +2068,8 @@ void LAllocator::SpillBetween(LiveRange* range,
|
||||
LifetimePosition start,
|
||||
LifetimePosition end) {
|
||||
ASSERT(start.Value() < end.Value());
|
||||
LiveRange* second_part = SplitAt(range, start);
|
||||
LiveRange* second_part = SplitRangeAt(range, start);
|
||||
if (!AllocationOk()) return;
|
||||
|
||||
if (second_part->Start().Value() < end.Value()) {
|
||||
// The split result intersects with [start, end[.
|
||||
|
@ -431,24 +431,13 @@ class LAllocator BASE_EMBEDDED {
|
||||
|
||||
static void TraceAlloc(const char* msg, ...);
|
||||
|
||||
// Lithium translation support.
|
||||
// Record a use of an input operand in the current instruction.
|
||||
void RecordUse(HValue* value, LUnallocated* operand);
|
||||
// Record the definition of the output operand.
|
||||
void RecordDefinition(HInstruction* instr, LUnallocated* operand);
|
||||
// Record a temporary operand.
|
||||
void RecordTemporary(LUnallocated* operand);
|
||||
|
||||
// Checks whether the value of a given virtual register is tagged.
|
||||
bool HasTaggedValue(int virtual_register) const;
|
||||
|
||||
// Returns the register kind required by the given virtual register.
|
||||
RegisterKind RequiredRegisterKind(int virtual_register) const;
|
||||
|
||||
// Control max function size.
|
||||
static int max_initial_value_ids();
|
||||
|
||||
void Allocate(LChunk* chunk);
|
||||
bool Allocate(LChunk* chunk);
|
||||
|
||||
const ZoneList<LiveRange*>* live_ranges() const { return &live_ranges_; }
|
||||
const Vector<LiveRange*>* fixed_live_ranges() const {
|
||||
@ -461,6 +450,15 @@ class LAllocator BASE_EMBEDDED {
|
||||
LChunk* chunk() const { return chunk_; }
|
||||
HGraph* graph() const { return graph_; }
|
||||
|
||||
int GetVirtualRegister() {
|
||||
if (next_virtual_register_ > LUnallocated::kMaxVirtualRegisters) {
|
||||
allocation_ok_ = false;
|
||||
}
|
||||
return next_virtual_register_++;
|
||||
}
|
||||
|
||||
bool AllocationOk() { return allocation_ok_; }
|
||||
|
||||
void MarkAsOsrEntry() {
|
||||
// There can be only one.
|
||||
ASSERT(!has_osr_entry_);
|
||||
@ -533,7 +531,7 @@ class LAllocator BASE_EMBEDDED {
|
||||
// Otherwise returns the live range that starts at pos and contains
|
||||
// all uses from the original range that follow pos. Uses at pos will
|
||||
// still be owned by the original range after splitting.
|
||||
LiveRange* SplitAt(LiveRange* range, LifetimePosition pos);
|
||||
LiveRange* SplitRangeAt(LiveRange* range, LifetimePosition pos);
|
||||
|
||||
// Split the given range in a position from the interval [start, end].
|
||||
LiveRange* SplitBetween(LiveRange* range,
|
||||
@ -591,6 +589,9 @@ class LAllocator BASE_EMBEDDED {
|
||||
|
||||
LChunk* chunk_;
|
||||
|
||||
// Indicates success or failure during register allocation.
|
||||
bool allocation_ok_;
|
||||
|
||||
// During liveness analysis keep a mapping from block id to live_in sets
|
||||
// for blocks already analyzed.
|
||||
ZoneList<BitVector*> live_in_sets_;
|
||||
|
@ -671,7 +671,7 @@ LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) {
|
||||
HInstruction* instr = HInstruction::cast(value);
|
||||
VisitInstruction(instr);
|
||||
}
|
||||
allocator_->RecordUse(value, operand);
|
||||
operand->set_virtual_register(value->id());
|
||||
return operand;
|
||||
}
|
||||
|
||||
@ -679,7 +679,7 @@ LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) {
|
||||
template<int I, int T>
|
||||
LInstruction* LChunkBuilder::Define(LTemplateInstruction<1, I, T>* instr,
|
||||
LUnallocated* result) {
|
||||
allocator_->RecordDefinition(current_instruction_, result);
|
||||
result->set_virtual_register(current_instruction_->id());
|
||||
instr->set_result(result);
|
||||
return instr;
|
||||
}
|
||||
@ -791,21 +791,22 @@ LInstruction* LChunkBuilder::AssignPointerMap(LInstruction* instr) {
|
||||
|
||||
LUnallocated* LChunkBuilder::TempRegister() {
|
||||
LUnallocated* operand = new LUnallocated(LUnallocated::MUST_HAVE_REGISTER);
|
||||
allocator_->RecordTemporary(operand);
|
||||
operand->set_virtual_register(allocator_->GetVirtualRegister());
|
||||
if (!allocator_->AllocationOk()) Abort("Not enough virtual registers.");
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
||||
LOperand* LChunkBuilder::FixedTemp(Register reg) {
|
||||
LUnallocated* operand = ToUnallocated(reg);
|
||||
allocator_->RecordTemporary(operand);
|
||||
ASSERT(operand->HasFixedPolicy());
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
||||
LOperand* LChunkBuilder::FixedTemp(DoubleRegister reg) {
|
||||
LUnallocated* operand = ToUnallocated(reg);
|
||||
allocator_->RecordTemporary(operand);
|
||||
ASSERT(operand->HasFixedPolicy());
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
@ -664,7 +664,7 @@ LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) {
|
||||
HInstruction* instr = HInstruction::cast(value);
|
||||
VisitInstruction(instr);
|
||||
}
|
||||
allocator_->RecordUse(value, operand);
|
||||
operand->set_virtual_register(value->id());
|
||||
return operand;
|
||||
}
|
||||
|
||||
@ -672,7 +672,7 @@ LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) {
|
||||
template<int I, int T>
|
||||
LInstruction* LChunkBuilder::Define(LTemplateInstruction<1, I, T>* instr,
|
||||
LUnallocated* result) {
|
||||
allocator_->RecordDefinition(current_instruction_, result);
|
||||
result->set_virtual_register(current_instruction_->id());
|
||||
instr->set_result(result);
|
||||
return instr;
|
||||
}
|
||||
@ -786,21 +786,22 @@ LInstruction* LChunkBuilder::AssignPointerMap(LInstruction* instr) {
|
||||
|
||||
LUnallocated* LChunkBuilder::TempRegister() {
|
||||
LUnallocated* operand = new LUnallocated(LUnallocated::MUST_HAVE_REGISTER);
|
||||
allocator_->RecordTemporary(operand);
|
||||
operand->set_virtual_register(allocator_->GetVirtualRegister());
|
||||
if (!allocator_->AllocationOk()) Abort("Not enough virtual registers.");
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
||||
LOperand* LChunkBuilder::FixedTemp(Register reg) {
|
||||
LUnallocated* operand = ToUnallocated(reg);
|
||||
allocator_->RecordTemporary(operand);
|
||||
ASSERT(operand->HasFixedPolicy());
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
||||
LOperand* LChunkBuilder::FixedTemp(XMMRegister reg) {
|
||||
LUnallocated* operand = ToUnallocated(reg);
|
||||
allocator_->RecordTemporary(operand);
|
||||
ASSERT(operand->HasFixedPolicy());
|
||||
return operand;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user