From: whesse@chromium.org Date: Fri, 19 Nov 2010 09:25:46 +0000 (+0000) Subject: Add a fast case to Array.join when all the elements and the separator are flat ascii... X-Git-Tag: upstream/4.7.83~20927 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=1d11e32a01e8b767f8d9e9f8d0c4fda88e36a685;p=platform%2Fupstream%2Fv8.git Add a fast case to Array.join when all the elements and the separator are flat ascii strings. Review URL: http://codereview.chromium.org/5122005 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5860 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- diff --git a/src/arm/codegen-arm.cc b/src/arm/codegen-arm.cc index dd0520fee..fe7ea8bd8 100644 --- a/src/arm/codegen-arm.cc +++ b/src/arm/codegen-arm.cc @@ -5816,6 +5816,15 @@ void CodeGenerator::GenerateGetCachedArrayIndex(ZoneList* args) { } +void CodeGenerator::GenerateFastAsciiArrayJoin(ZoneList* args) { + ASSERT(args->length() == 2); + Load(args->at(0)); + Register value = frame_->PopToRegister(); + __ LoadRoot(value, Heap::kUndefinedValueRootIndex); + frame_->EmitPush(value); +} + + void CodeGenerator::VisitCallRuntime(CallRuntime* node) { #ifdef DEBUG int original_height = frame_->height(); diff --git a/src/arm/codegen-arm.h b/src/arm/codegen-arm.h index 2e8f46668..9495f1b60 100644 --- a/src/arm/codegen-arm.h +++ b/src/arm/codegen-arm.h @@ -528,6 +528,7 @@ class CodeGenerator: public AstVisitor { void GenerateHasCachedArrayIndex(ZoneList* args); void GenerateGetCachedArrayIndex(ZoneList* args); + void GenerateFastAsciiArrayJoin(ZoneList* args); // Simple condition analysis. enum ConditionAnalysis { diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index c50f84ad5..f0fa5a774 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -2772,6 +2772,13 @@ void FullCodeGenerator::EmitGetCachedArrayIndex(ZoneList* args) { } +void FullCodeGenerator::EmitFastAsciiArrayJoin(ZoneList* args) { + __ LoadRoot(r0, Heap::kUndefinedValueRootIndex); + context()->Plug(r0); + return; +} + + void FullCodeGenerator::VisitCallRuntime(CallRuntime* expr) { Handle name = expr->name(); if (name->length() > 0 && name->Get(0) == '_') { diff --git a/src/array.js b/src/array.js index b2ebece9c..5ecf5e303 100644 --- a/src/array.js +++ b/src/array.js @@ -364,6 +364,10 @@ function ArrayJoin(separator) { } else if (!IS_STRING(separator)) { separator = ToString(separator); } + + var result = %_FastAsciiArrayJoin(this, separator); + if (typeof result != "undefined") return result; + var length = TO_UINT32(this.length); return Join(this, length, separator, ConvertToString); } diff --git a/src/ia32/codegen-ia32.cc b/src/ia32/codegen-ia32.cc index fb7a138d5..060ccce84 100644 --- a/src/ia32/codegen-ia32.cc +++ b/src/ia32/codegen-ia32.cc @@ -6642,6 +6642,190 @@ void CodeGenerator::GenerateIsArray(ZoneList* args) { } +void CodeGenerator::GenerateFastAsciiArrayJoin(ZoneList* args) { + ASSERT(args->length() == 2); + Load(args->at(1)); + Load(args->at(0)); + Result array_result = frame_->Pop(); + array_result.ToRegister(eax); + frame_->SpillAll(); + + Label bailout; + Label done; + // All aliases of the same register have disjoint lifetimes. + Register array = eax; + Register result_pos = no_reg; + + Register index = edi; + + Register current_string_length = ecx; // Will be ecx when live. + + Register current_string = edx; + + Register scratch = ebx; + + Register scratch_2 = esi; + Register new_padding_chars = scratch_2; + + Operand separator = Operand(esp, 4 * kPointerSize); // Already pushed. + Operand elements = Operand(esp, 3 * kPointerSize); + Operand result = Operand(esp, 2 * kPointerSize); + Operand padding_chars = Operand(esp, 1 * kPointerSize); + Operand array_length = Operand(esp, 0); + __ sub(Operand(esp), Immediate(4 * kPointerSize)); + + // Check that eax is a JSArray + __ test(array, Immediate(kSmiTagMask)); + __ j(zero, &bailout); + __ CmpObjectType(array, JS_ARRAY_TYPE, scratch); + __ j(not_equal, &bailout); + + // Check that the array has fast elements. + __ test_b(FieldOperand(scratch, Map::kBitField2Offset), + 1 << Map::kHasFastElements); + __ j(zero, &bailout); + + // If the array is empty, return the empty string. + __ mov(scratch, FieldOperand(array, JSArray::kLengthOffset)); + __ sar(scratch, 1); + Label non_trivial; + __ j(not_zero, &non_trivial); + __ mov(result, Factory::empty_string()); + __ jmp(&done); + + __ bind(&non_trivial); + __ mov(array_length, scratch); + + __ mov(scratch, FieldOperand(array, JSArray::kElementsOffset)); + __ mov(elements, scratch); + + // End of array's live range. + result_pos = array; + array = no_reg; + + + // Check that the separator is a flat ascii string. + __ mov(current_string, separator); + __ test(current_string, Immediate(kSmiTagMask)); + __ j(zero, &bailout); + __ mov(scratch, FieldOperand(current_string, HeapObject::kMapOffset)); + __ mov_b(scratch, FieldOperand(scratch, Map::kInstanceTypeOffset)); + __ and_(scratch, Immediate( + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask)); + __ cmp(scratch, kStringTag | kAsciiStringTag | kSeqStringTag); + __ j(not_equal, &bailout); + // If the separator is the empty string, replace it with NULL. + // The test for NULL is quicker than the empty string test, in a loop. + __ cmp(FieldOperand(current_string, SeqAsciiString::kLengthOffset), + Immediate(0)); + Label separator_checked; + __ j(not_zero, &separator_checked); + __ mov(separator, Immediate(0)); + __ bind(&separator_checked); + + // Check that elements[0] is a flat ascii string, and copy it in new space. + __ mov(scratch, elements); + __ mov(current_string, FieldOperand(scratch, FixedArray::kHeaderSize)); + __ test(current_string, Immediate(kSmiTagMask)); + __ j(zero, &bailout); + __ mov(scratch, FieldOperand(current_string, HeapObject::kMapOffset)); + __ mov_b(scratch, FieldOperand(scratch, Map::kInstanceTypeOffset)); + __ and_(scratch, Immediate( + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask)); + __ cmp(scratch, kStringTag | kAsciiStringTag | kSeqStringTag); + __ j(not_equal, &bailout); + + // Allocate space to copy it. Round up the size to the alignment granularity. + __ mov(current_string_length, + FieldOperand(current_string, String::kLengthOffset)); + __ shr(current_string_length, 1); + + // Live registers and stack values: + // current_string_length: length of elements[0]. + + // New string result in new space = elements[0] + __ AllocateAsciiString(result_pos, current_string_length, scratch_2, + index, no_reg, &bailout); + __ mov(result, result_pos); + + // Adjust current_string_length to include padding bytes at end of string. + // Keep track of the number of padding bytes. + __ mov(new_padding_chars, current_string_length); + __ add(Operand(current_string_length), Immediate(kObjectAlignmentMask)); + __ and_(Operand(current_string_length), Immediate(~kObjectAlignmentMask)); + __ sub(new_padding_chars, Operand(current_string_length)); + __ neg(new_padding_chars); + __ mov(padding_chars, new_padding_chars); + + Label copy_loop_1_done; + Label copy_loop_1; + __ test(current_string_length, Operand(current_string_length)); + __ j(zero, ©_loop_1_done); + __ bind(©_loop_1); + __ sub(Operand(current_string_length), Immediate(kPointerSize)); + __ mov(scratch, FieldOperand(current_string, current_string_length, + times_1, SeqAsciiString::kHeaderSize)); + __ mov(FieldOperand(result_pos, current_string_length, + times_1, SeqAsciiString::kHeaderSize), + scratch); + __ j(not_zero, ©_loop_1); + __ bind(©_loop_1_done); + + __ mov(index, Immediate(1)); + // Loop condition: while (index < length). + Label loop; + __ bind(&loop); + __ cmp(index, array_length); + __ j(greater_equal, &done); + + // If the separator is the empty string, signalled by NULL, skip it. + Label separator_done; + __ mov(current_string, separator); + __ test(current_string, Operand(current_string)); + __ j(zero, &separator_done); + + // Append separator to result. It is known to be a flat ascii string. + __ AppendStringToTopOfNewSpace(current_string, current_string_length, + result_pos, scratch, scratch_2, result, + padding_chars, &bailout); + __ bind(&separator_done); + + // Add next element of array to the end of the result. + // Get current_string = array[index]. + __ mov(scratch, elements); + __ mov(current_string, FieldOperand(scratch, index, + times_pointer_size, + FixedArray::kHeaderSize)); + // If current != flat ascii string drop result, return undefined. + __ test(current_string, Immediate(kSmiTagMask)); + __ j(zero, &bailout); + __ mov(scratch, FieldOperand(current_string, HeapObject::kMapOffset)); + __ mov_b(scratch, FieldOperand(scratch, Map::kInstanceTypeOffset)); + __ and_(scratch, Immediate( + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask)); + __ cmp(scratch, kStringTag | kAsciiStringTag | kSeqStringTag); + __ j(not_equal, &bailout); + + // Append current to the result. + __ AppendStringToTopOfNewSpace(current_string, current_string_length, + result_pos, scratch, scratch_2, result, + padding_chars, &bailout); + __ add(Operand(index), Immediate(1)); + __ jmp(&loop); // End while (index < length). + + __ bind(&bailout); + __ mov(result, Factory::undefined_value()); + __ bind(&done); + __ mov(eax, result); + // Drop temp values from the stack, and restore context register. + __ add(Operand(esp), Immediate(4 * kPointerSize)); + + __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); + frame_->Drop(1); + frame_->Push(&array_result); +} + + void CodeGenerator::GenerateIsRegExp(ZoneList* args) { ASSERT(args->length() == 1); Load(args->at(0)); diff --git a/src/ia32/codegen-ia32.h b/src/ia32/codegen-ia32.h index a945e217a..f15e08c54 100644 --- a/src/ia32/codegen-ia32.h +++ b/src/ia32/codegen-ia32.h @@ -710,6 +710,7 @@ class CodeGenerator: public AstVisitor { void GenerateHasCachedArrayIndex(ZoneList* args); void GenerateGetCachedArrayIndex(ZoneList* args); + void GenerateFastAsciiArrayJoin(ZoneList* args); // Simple condition analysis. enum ConditionAnalysis { diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index ad32dc85b..b742d352c 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -3084,6 +3084,190 @@ void FullCodeGenerator::EmitGetCachedArrayIndex(ZoneList* args) { } +void FullCodeGenerator::EmitFastAsciiArrayJoin(ZoneList* args) { + Label bailout; + Label done; + + ASSERT(args->length() == 2); + // We will leave the separator on the stack until the end of the function. + VisitForStackValue(args->at(1)); + // Load this to eax (= array) + VisitForAccumulatorValue(args->at(0)); + + // All aliases of the same register have disjoint lifetimes. + Register array = eax; + Register result_pos = no_reg; + + Register index = edi; + + Register current_string_length = ecx; // Will be ecx when live. + + Register current_string = edx; + + Register scratch = ebx; + + Register scratch_2 = esi; + Register new_padding_chars = scratch_2; + + Operand separator = Operand(esp, 4 * kPointerSize); // Already pushed. + Operand elements = Operand(esp, 3 * kPointerSize); + Operand result = Operand(esp, 2 * kPointerSize); + Operand padding_chars = Operand(esp, 1 * kPointerSize); + Operand array_length = Operand(esp, 0); + __ sub(Operand(esp), Immediate(4 * kPointerSize)); + + + // Check that eax is a JSArray + __ test(array, Immediate(kSmiTagMask)); + __ j(zero, &bailout); + __ CmpObjectType(array, JS_ARRAY_TYPE, scratch); + __ j(not_equal, &bailout); + + // Check that the array has fast elements. + __ test_b(FieldOperand(scratch, Map::kBitField2Offset), + 1 << Map::kHasFastElements); + __ j(zero, &bailout); + + // If the array is empty, return the empty string. + __ mov(scratch, FieldOperand(array, JSArray::kLengthOffset)); + __ sar(scratch, 1); + Label non_trivial; + __ j(not_zero, &non_trivial); + __ mov(result, Factory::empty_string()); + __ jmp(&done); + + __ bind(&non_trivial); + __ mov(array_length, scratch); + + __ mov(scratch, FieldOperand(array, JSArray::kElementsOffset)); + __ mov(elements, scratch); + + // End of array's live range. + result_pos = array; + array = no_reg; + + + // Check that the separator is a flat ascii string. + __ mov(current_string, separator); + __ test(current_string, Immediate(kSmiTagMask)); + __ j(zero, &bailout); + __ mov(scratch, FieldOperand(current_string, HeapObject::kMapOffset)); + __ mov_b(scratch, FieldOperand(scratch, Map::kInstanceTypeOffset)); + __ and_(scratch, Immediate( + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask)); + __ cmp(scratch, kStringTag | kAsciiStringTag | kSeqStringTag); + __ j(not_equal, &bailout); + // If the separator is the empty string, replace it with NULL. + // The test for NULL is quicker than the empty string test, in a loop. + __ cmp(FieldOperand(current_string, SeqAsciiString::kLengthOffset), + Immediate(0)); + Label separator_checked; + __ j(not_zero, &separator_checked); + __ mov(separator, Immediate(0)); + __ bind(&separator_checked); + + // Check that elements[0] is a flat ascii string, and copy it in new space. + __ mov(scratch, elements); + __ mov(current_string, FieldOperand(scratch, FixedArray::kHeaderSize)); + __ test(current_string, Immediate(kSmiTagMask)); + __ j(zero, &bailout); + __ mov(scratch, FieldOperand(current_string, HeapObject::kMapOffset)); + __ mov_b(scratch, FieldOperand(scratch, Map::kInstanceTypeOffset)); + __ and_(scratch, Immediate( + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask)); + __ cmp(scratch, kStringTag | kAsciiStringTag | kSeqStringTag); + __ j(not_equal, &bailout); + + // Allocate space to copy it. Round up the size to the alignment granularity. + __ mov(current_string_length, + FieldOperand(current_string, String::kLengthOffset)); + __ shr(current_string_length, 1); + + // Live registers and stack values: + // current_string_length: length of elements[0]. + + // New string result in new space = elements[0] + __ AllocateAsciiString(result_pos, current_string_length, scratch_2, + index, no_reg, &bailout); + __ mov(result, result_pos); + + // Adjust current_string_length to include padding bytes at end of string. + // Keep track of the number of padding bytes. + __ mov(new_padding_chars, current_string_length); + __ add(Operand(current_string_length), Immediate(kObjectAlignmentMask)); + __ and_(Operand(current_string_length), Immediate(~kObjectAlignmentMask)); + __ sub(new_padding_chars, Operand(current_string_length)); + __ neg(new_padding_chars); + __ mov(padding_chars, new_padding_chars); + + Label copy_loop_1_done; + Label copy_loop_1; + __ test(current_string_length, Operand(current_string_length)); + __ j(zero, ©_loop_1_done); + __ bind(©_loop_1); + __ sub(Operand(current_string_length), Immediate(kPointerSize)); + __ mov(scratch, FieldOperand(current_string, current_string_length, + times_1, SeqAsciiString::kHeaderSize)); + __ mov(FieldOperand(result_pos, current_string_length, + times_1, SeqAsciiString::kHeaderSize), + scratch); + __ j(not_zero, ©_loop_1); + __ bind(©_loop_1_done); + + __ mov(index, Immediate(1)); + // Loop condition: while (index < length). + Label loop; + __ bind(&loop); + __ cmp(index, array_length); + __ j(greater_equal, &done); + + // If the separator is the empty string, signalled by NULL, skip it. + Label separator_done; + __ mov(current_string, separator); + __ test(current_string, Operand(current_string)); + __ j(zero, &separator_done); + + // Append separator to result. It is known to be a flat ascii string. + __ AppendStringToTopOfNewSpace(current_string, current_string_length, + result_pos, scratch, scratch_2, result, + padding_chars, &bailout); + __ bind(&separator_done); + + // Add next element of array to the end of the result. + // Get current_string = array[index]. + __ mov(scratch, elements); + __ mov(current_string, FieldOperand(scratch, index, + times_pointer_size, + FixedArray::kHeaderSize)); + // If current != flat ascii string drop result, return undefined. + __ test(current_string, Immediate(kSmiTagMask)); + __ j(zero, &bailout); + __ mov(scratch, FieldOperand(current_string, HeapObject::kMapOffset)); + __ mov_b(scratch, FieldOperand(scratch, Map::kInstanceTypeOffset)); + __ and_(scratch, Immediate( + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask)); + __ cmp(scratch, kStringTag | kAsciiStringTag | kSeqStringTag); + __ j(not_equal, &bailout); + + // Append current to the result. + __ AppendStringToTopOfNewSpace(current_string, current_string_length, + result_pos, scratch, scratch_2, result, + padding_chars, &bailout); + __ add(Operand(index), Immediate(1)); + __ jmp(&loop); // End while (index < length). + + __ bind(&bailout); + __ mov(result, Factory::undefined_value()); + __ bind(&done); + __ mov(eax, result); + // Drop temp values from the stack, and restore context register. + __ add(Operand(esp), Immediate(5 * kPointerSize)); + + __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); + context()->Plug(eax); +} + + void FullCodeGenerator::VisitCallRuntime(CallRuntime* expr) { Handle name = expr->name(); if (name->length() > 0 && name->Get(0) == '_') { diff --git a/src/ia32/macro-assembler-ia32.cc b/src/ia32/macro-assembler-ia32.cc index db26c1042..61aadf7eb 100644 --- a/src/ia32/macro-assembler-ia32.cc +++ b/src/ia32/macro-assembler-ia32.cc @@ -889,6 +889,57 @@ void MacroAssembler::AllocateAsciiConsString(Register result, Immediate(Factory::cons_ascii_string_map())); } +// All registers must be distinct. Only current_string needs valid contents +// on entry. All registers may be invalid on exit. result_operand is +// unchanged, padding_chars is updated correctly. +void MacroAssembler::AppendStringToTopOfNewSpace( + Register current_string, // Tagged pointer to string to copy. + Register current_string_length, + Register result_pos, + Register scratch, + Register new_padding_chars, + Operand operand_result, + Operand operand_padding_chars, + Label* bailout) { + mov(current_string_length, + FieldOperand(current_string, String::kLengthOffset)); + shr(current_string_length, 1); + sub(current_string_length, operand_padding_chars); + mov(new_padding_chars, current_string_length); + add(Operand(current_string_length), Immediate(kObjectAlignmentMask)); + and_(Operand(current_string_length), Immediate(~kObjectAlignmentMask)); + sub(new_padding_chars, Operand(current_string_length)); + neg(new_padding_chars); + // We need an allocation even if current_string_length is 0, to fetch + // result_pos. Consider using a faster fetch of result_pos in that case. + AllocateInNewSpace(current_string_length, result_pos, scratch, no_reg, + bailout, NO_ALLOCATION_FLAGS); + sub(result_pos, operand_padding_chars); + mov(operand_padding_chars, new_padding_chars); + + Register scratch_2 = new_padding_chars; // Used to compute total length. + // Copy string to the end of result. + mov(current_string_length, + FieldOperand(current_string, String::kLengthOffset)); + mov(scratch, operand_result); + mov(scratch_2, current_string_length); + add(scratch_2, FieldOperand(scratch, String::kLengthOffset)); + mov(FieldOperand(scratch, String::kLengthOffset), scratch_2); + shr(current_string_length, 1); + lea(current_string, + FieldOperand(current_string, SeqAsciiString::kHeaderSize)); + // Loop condition: while (--current_string_length >= 0). + Label copy_loop; + Label copy_loop_entry; + jmp(©_loop_entry); + bind(©_loop); + mov_b(scratch, Operand(current_string, current_string_length, times_1, 0)); + mov_b(Operand(result_pos, current_string_length, times_1, 0), scratch); + bind(©_loop_entry); + sub(Operand(current_string_length), Immediate(1)); + j(greater_equal, ©_loop); +} + void MacroAssembler::NegativeZeroTest(CodeGenerator* cgen, Register result, diff --git a/src/ia32/macro-assembler-ia32.h b/src/ia32/macro-assembler-ia32.h index 0a6e0ee35..cea7a7018 100644 --- a/src/ia32/macro-assembler-ia32.h +++ b/src/ia32/macro-assembler-ia32.h @@ -379,6 +379,23 @@ class MacroAssembler: public Assembler { Register scratch2, Label* gc_required); + // All registers must be distinct. Only current_string needs valid contents + // on entry. All registers may be invalid on exit. result_operand is + // unchanged, padding_chars is updated correctly. + // The top of new space must contain a sequential ascii string with + // padding_chars bytes free in its top word. The sequential ascii string + // current_string is concatenated to it, allocating the necessary amount + // of new memory. + void AppendStringToTopOfNewSpace( + Register current_string, // Tagged pointer to string to copy. + Register current_string_length, + Register result_pos, + Register scratch, + Register new_padding_chars, + Operand operand_result, + Operand operand_padding_chars, + Label* bailout); + // --------------------------------------------------------------------------- // Support functions. diff --git a/src/runtime.h b/src/runtime.h index 76b3c778a..3436f124b 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -418,7 +418,8 @@ namespace internal { F(MathSqrt, 1, 1) \ F(IsRegExpEquivalent, 2, 1) \ F(HasCachedArrayIndex, 1, 1) \ - F(GetCachedArrayIndex, 1, 1) + F(GetCachedArrayIndex, 1, 1) \ + F(FastAsciiArrayJoin, 2, 1) // ---------------------------------------------------------------------------- diff --git a/src/x64/codegen-x64.cc b/src/x64/codegen-x64.cc index 6e98a0090..395f7c39c 100644 --- a/src/x64/codegen-x64.cc +++ b/src/x64/codegen-x64.cc @@ -7217,6 +7217,11 @@ void CodeGenerator::GenerateGetCachedArrayIndex(ZoneList* args) { } +void CodeGenerator::GenerateFastAsciiArrayJoin(ZoneList* args) { + frame_->Push(Factory::undefined_value()); +} + + void CodeGenerator::VisitCallRuntime(CallRuntime* node) { if (CheckForInlineRuntimeCall(node)) { return; diff --git a/src/x64/codegen-x64.h b/src/x64/codegen-x64.h index c3270add2..2bc82a46f 100644 --- a/src/x64/codegen-x64.h +++ b/src/x64/codegen-x64.h @@ -668,6 +668,7 @@ class CodeGenerator: public AstVisitor { void GenerateHasCachedArrayIndex(ZoneList* args); void GenerateGetCachedArrayIndex(ZoneList* args); + void GenerateFastAsciiArrayJoin(ZoneList* args); // Simple condition analysis. enum ConditionAnalysis { diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index e4b24ff4c..4dbc655d3 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -2795,6 +2795,11 @@ void FullCodeGenerator::EmitGetCachedArrayIndex(ZoneList* args) { } +void FullCodeGenerator::EmitFastAsciiArrayJoin(ZoneList* args) { + context()->Plug(Heap::kUndefinedValueRootIndex); +} + + void FullCodeGenerator::VisitCallRuntime(CallRuntime* expr) { Handle name = expr->name(); if (name->length() > 0 && name->Get(0) == '_') {