}
-void CodeGenerator::GenerateRegExpCloneResult(ZoneList<Expression*>* args) {
- ASSERT_EQ(1, args->length());
-
- Load(args->at(0));
- frame_->PopToR0();
- {
- VirtualFrame::SpilledScope spilled_scope(frame_);
-
- Label done;
- Label call_runtime;
- __ BranchOnSmi(r0, &done);
-
- // Load JSRegExp map into r1. Check that argument object has this map.
- // Arguments to this function should be results of calling RegExp exec,
- // which is either an unmodified JSRegExpResult or null. Anything not having
- // the unmodified JSRegExpResult map is returned unmodified.
- // This also ensures that elements are fast.
-
- __ ldr(r1, ContextOperand(cp, Context::GLOBAL_INDEX));
- __ ldr(r1, FieldMemOperand(r1, GlobalObject::kGlobalContextOffset));
- __ ldr(r1, ContextOperand(r1, Context::REGEXP_RESULT_MAP_INDEX));
- __ ldr(ip, FieldMemOperand(r0, HeapObject::kMapOffset));
- __ cmp(r1, Operand(ip));
- __ b(ne, &done);
-
- if (FLAG_debug_code) {
- __ LoadRoot(r2, Heap::kEmptyFixedArrayRootIndex);
- __ ldr(ip, FieldMemOperand(r0, JSObject::kPropertiesOffset));
- __ cmp(ip, r2);
- __ Check(eq, "JSRegExpResult: default map but non-empty properties.");
- }
-
- // All set, copy the contents to a new object.
- __ AllocateInNewSpace(JSRegExpResult::kSize,
- r2,
- r3,
- r4,
- &call_runtime,
- NO_ALLOCATION_FLAGS);
- // Store RegExpResult map as map of allocated object.
- ASSERT(JSRegExpResult::kSize == 6 * kPointerSize);
- // Copy all fields (map is already in r1) from (untagged) r0 to r2.
- // Change map of elements array (ends up in r4) to be a FixedCOWArray.
- __ bic(r0, r0, Operand(kHeapObjectTagMask));
- __ ldm(ib, r0, r3.bit() | r4.bit() | r5.bit() | r6.bit() | r7.bit());
- __ stm(ia, r2,
- r1.bit() | r3.bit() | r4.bit() | r5.bit() | r6.bit() | r7.bit());
- ASSERT(JSRegExp::kElementsOffset == 2 * kPointerSize);
- // Check whether elements array is empty fixed array, and otherwise make
- // it copy-on-write (it never should be empty unless someone is messing
- // with the arguments to the runtime function).
- __ LoadRoot(ip, Heap::kEmptyFixedArrayRootIndex);
- __ add(r0, r2, Operand(kHeapObjectTag)); // Tag result and move it to r0.
- __ cmp(r4, ip);
- __ b(eq, &done);
- __ LoadRoot(ip, Heap::kFixedCOWArrayMapRootIndex);
- __ str(ip, FieldMemOperand(r4, HeapObject::kMapOffset));
- __ b(&done);
- __ bind(&call_runtime);
- __ push(r0);
- __ CallRuntime(Runtime::kRegExpCloneResult, 1);
- __ bind(&done);
- }
- frame_->EmitPush(r0);
-}
-
-
class DeferredSearchCache: public DeferredCode {
public:
DeferredSearchCache(Register dst, Register cache, Register key)
void GenerateRegExpConstructResult(ZoneList<Expression*>* args);
- void GenerateRegExpCloneResult(ZoneList<Expression*>* args);
-
// Support for fast native caches.
void GenerateGetFromCache(ZoneList<Expression*>* args);
}
-void FullCodeGenerator::EmitRegExpCloneResult(ZoneList<Expression*>* args) {
- ASSERT(args->length() == 1);
- VisitForStackValue(args->at(0));
- __ CallRuntime(Runtime::kRegExpCloneResult, 1);
- context()->Plug(result_register());
-}
-
#undef __
}
-void CodeGenerator::GenerateRegExpCloneResult(ZoneList<Expression*>* args) {
- ASSERT_EQ(1, args->length());
-
- Load(args->at(0));
- Result object_result = frame_->Pop();
- object_result.ToRegister(eax);
- object_result.Unuse();
- {
- VirtualFrame::SpilledScope spilled_scope;
-
- Label done;
-
- __ test(eax, Immediate(kSmiTagMask));
- __ j(zero, &done);
-
- // Load JSRegExpResult map into edx.
- // Arguments to this function should be results of calling RegExp exec,
- // which is either an unmodified JSRegExpResult or null. Anything not having
- // the unmodified JSRegExpResult map is returned unmodified.
- // This also ensures that elements are fast.
- __ mov(edx, ContextOperand(esi, Context::GLOBAL_INDEX));
- __ mov(edx, FieldOperand(edx, GlobalObject::kGlobalContextOffset));
- __ mov(edx, ContextOperand(edx, Context::REGEXP_RESULT_MAP_INDEX));
- __ cmp(edx, FieldOperand(eax, HeapObject::kMapOffset));
- __ j(not_equal, &done);
-
- if (FLAG_debug_code) {
- // Check that object really has empty properties array, as the map
- // should guarantee.
- __ cmp(FieldOperand(eax, JSObject::kPropertiesOffset),
- Immediate(Factory::empty_fixed_array()));
- __ Check(equal, "JSRegExpResult: default map but non-empty properties.");
- }
-
- DeferredAllocateInNewSpace* allocate_fallback =
- new DeferredAllocateInNewSpace(JSRegExpResult::kSize,
- ebx,
- edx.bit() | eax.bit());
-
- // All set, copy the contents to a new object.
- __ AllocateInNewSpace(JSRegExpResult::kSize,
- ebx,
- ecx,
- no_reg,
- allocate_fallback->entry_label(),
- TAG_OBJECT);
- __ bind(allocate_fallback->exit_label());
-
- // Copy all fields from eax to ebx.
- STATIC_ASSERT(JSRegExpResult::kSize % (2 * kPointerSize) == 0);
- // There is an even number of fields, so unroll the loop once
- // for efficiency.
- for (int i = 0; i < JSRegExpResult::kSize; i += 2 * kPointerSize) {
- STATIC_ASSERT(JSObject::kMapOffset % (2 * kPointerSize) == 0);
- if (i != JSObject::kMapOffset) {
- // The map was already loaded into edx.
- __ mov(edx, FieldOperand(eax, i));
- }
- __ mov(ecx, FieldOperand(eax, i + kPointerSize));
-
- STATIC_ASSERT(JSObject::kElementsOffset % (2 * kPointerSize) == 0);
- if (i == JSObject::kElementsOffset) {
- // If the elements array isn't empty, make it copy-on-write
- // before copying it.
- Label empty;
- __ cmp(Operand(edx), Immediate(Factory::empty_fixed_array()));
- __ j(equal, &empty);
- __ mov(FieldOperand(edx, HeapObject::kMapOffset),
- Immediate(Factory::fixed_cow_array_map()));
- __ bind(&empty);
- }
- __ mov(FieldOperand(ebx, i), edx);
- __ mov(FieldOperand(ebx, i + kPointerSize), ecx);
- }
- __ mov(eax, ebx);
-
- __ bind(&done);
- }
- frame_->Push(eax);
-}
-
-
class DeferredSearchCache: public DeferredCode {
public:
DeferredSearchCache(Register dst, Register cache, Register key)
// Construct a RegExp exec result with two in-object properties.
void GenerateRegExpConstructResult(ZoneList<Expression*>* args);
- // Clone the result of a regexp function.
- // Must be an object created by GenerateRegExpConstructResult with
- // no extra properties.
- void GenerateRegExpCloneResult(ZoneList<Expression*>* args);
-
// Support for fast native caches.
void GenerateGetFromCache(ZoneList<Expression*>* args);
}
}
- if (!isConstructorCall) {
- regExpCache.type = 'none';
- }
%RegExpInitializeObject(object, pattern, global, ignoreCase, multiline);
// Call internal function to compile the pattern.
}
-function RegExpCache() {
- this.type = 'none';
- this.regExp = 0;
- this.subject = 0;
- this.replaceString = 0;
- this.answer = 0;
- // answerSaved marks whether the contents of answer is valid for a cache
- // hit in RegExpExec, StringMatch and StringSplit.
- this.answerSaved = false;
- this.splitLimit = 0; // Used only when type is "split".
-}
-
-
-var regExpCache = new RegExpCache();
-
-
function BuildResultFromMatchInfo(lastMatchInfo, s) {
var numResults = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1;
var result = %_RegExpConstructResult(numResults, lastMatchInfo[CAPTURE0], s);
['RegExp.prototype.exec', this]);
}
- var cache = regExpCache;
- var saveAnswer = false;
-
- var lastIndex = this.lastIndex;
-
- // Since cache.subject is always a string, a matching input can not
- // cause visible side-effects when converted to a string, so we can omit
- // the conversion required by the specification.
- // Likewise, the regexp.lastIndex and regexp.global properties are value
- // properties that are not configurable, so reading them can also not cause
- // any side effects (converting lastIndex to a number can, though).
- if (%_ObjectEquals(cache.type, 'exec') &&
- %_ObjectEquals(0, lastIndex) &&
- %_IsRegExpEquivalent(cache.regExp, this) &&
- %_ObjectEquals(cache.subject, string)) {
- if (cache.answerSaved) {
- // The regexp.lastIndex value must be 0 for non-global RegExps, and for
- // global RegExps we only cache negative results, which gives a lastIndex
- // of zero as well.
- this.lastIndex = 0;
- return %_RegExpCloneResult(cache.answer);
- } else {
- saveAnswer = true;
- }
- }
-
if (%_ArgumentsLength() === 0) {
var regExpInput = LAST_INPUT(lastMatchInfo);
if (IS_UNDEFINED(regExpInput)) {
} else {
s = ToString(string);
}
- var global = this.global;
+ var lastIndex = this.lastIndex;
// Conversion is required by the ES5 specification (RegExp.prototype.exec
// algorithm, step 5) even if the value is discarded for non-global RegExps.
var i = TO_INTEGER(lastIndex);
+
+ var global = this.global;
if (global) {
if (i < 0 || i > s.length) {
this.lastIndex = 0;
if (matchIndices === null) {
if (global) {
- // Cache negative result only if initial lastIndex was zero.
this.lastIndex = 0;
- if (lastIndex !== 0) return matchIndices;
}
- cache.regExp = this;
- cache.subject = s; // Always a string.
- cache.answer = null;
- cache.answerSaved = true; // Safe since no cloning is needed.
- cache.type = 'exec';
- return matchIndices; // No match.
+ return null;
}
// Successful match.
var result = BuildResultFromMatchInfo(matchIndices, s);
if (global) {
- // Don't cache positive results for global regexps.
this.lastIndex = lastMatchInfo[CAPTURE1];
- } else {
- cache.regExp = this;
- cache.subject = s;
- if (saveAnswer) cache.answer = %_RegExpCloneResult(result);
- cache.answerSaved = saveAnswer;
- cache.type = 'exec';
}
return result;
-
}
string = regExpInput;
}
- var lastIndex = this.lastIndex;
-
- var cache = regExpCache;
- if (%_ObjectEquals(cache.type, 'test') &&
- %_IsRegExpEquivalent(cache.regExp, this) &&
- %_ObjectEquals(cache.subject, string) &&
- %_ObjectEquals(0, lastIndex)) {
- // The regexp.lastIndex value must be 0 for non-global RegExps, and for
- // global RegExps we only cache negative results, which gives a resulting
- // lastIndex of zero as well.
- if (global) this.lastIndex = 0;
- return cache.answer;
- }
-
var s;
if (IS_STRING(string)) {
s = string;
}
var length = s.length;
+ var lastIndex = this.lastIndex;
+
// Conversion is required by the ES5 specification (RegExp.prototype.exec
// algorithm, step 5) even if the value is discarded for non-global RegExps.
var i = TO_INTEGER(lastIndex);
+
+ var global = this.global;
if (global) {
if (i < 0 || i > length) {
this.lastIndex = 0;
i = 0;
}
- var global = this.global;
-
// Remove irrelevant preceeding '.*' in a test regexp. The expression
// checks whether this.source starts with '.*' and that the third
// char is not a '?'
if (!%_ObjectEquals(regexp_key, this)) {
regexp_key = this;
regexp_val = new $RegExp(this.source.substring(2, this.source.length),
- (this.global ? 'g' : '')
+ (global ? 'g' : '')
+ (this.ignoreCase ? 'i' : '')
+ (this.multiline ? 'm' : ''));
}
if (!regexp_val.test(s)) return false;
}
+ var length = s.length;
+
+ if (i < 0 || i > length) {
+ this.lastIndex = 0;
+ return false;
+ }
+
%_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]);
// matchIndices is either null or the lastMatchInfo array.
var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo);
- var result = (matchIndices !== null);
- if (result) {
- lastMatchInfoOverride = null;
- }
- if (global) {
- if (result) {
- this.lastIndex = lastMatchInfo[CAPTURE1];
- return true;
- } else {
- this.lastIndex = 0;
- if (lastIndex !== 0) return false;
- }
+ if (matchIndices === null) {
+ if (global) this.lastIndex = 0;
+ return false;
}
- cache.type = 'test';
- cache.regExp = this;
- cache.subject = s;
- cache.answer = result;
- return result;
+ lastMatchInfoOverride = null;
+ if (global) this.lastIndex = lastMatchInfo[CAPTURE1];
+ return true;
}
return IS_UNDEFINED(regExpInput) ? "" : regExpInput;
}
function RegExpSetInput(string) {
- regExpCache.type = 'none';
LAST_INPUT(lastMatchInfo) = ToString(string);
};
}
-static MaybeObject* Runtime_RegExpCloneResult(Arguments args) {
- ASSERT(args.length() == 1);
- Map* regexp_result_map;
- {
- AssertNoAllocation no_gc;
- HandleScope handles;
- regexp_result_map = Top::global_context()->regexp_result_map();
- }
- if (!args[0]->IsJSArray()) return args[0];
-
- JSArray* result = JSArray::cast(args[0]);
- // Arguments to RegExpCloneResult should always be fresh RegExp exec call
- // results (either a fresh JSRegExpResult or null).
- // If the argument is not a JSRegExpResult, or isn't unmodified, just return
- // the argument uncloned.
- if (result->map() != regexp_result_map) return result;
-
- // Having the original JSRegExpResult map guarantees that we have
- // fast elements and no properties except the two in-object properties.
- ASSERT(result->HasFastElements());
- ASSERT(result->properties() == Heap::empty_fixed_array());
- ASSERT_EQ(2, regexp_result_map->inobject_properties());
-
- Object* new_array_alloc;
- { MaybeObject* maybe_new_array_alloc =
- Heap::AllocateRaw(JSRegExpResult::kSize, NEW_SPACE, OLD_POINTER_SPACE);
- if (!maybe_new_array_alloc->ToObject(&new_array_alloc)) {
- return maybe_new_array_alloc;
- }
- }
-
- // Set HeapObject map to JSRegExpResult map.
- reinterpret_cast<HeapObject*>(new_array_alloc)->set_map(regexp_result_map);
-
- JSArray* new_array = JSArray::cast(new_array_alloc);
-
- // Copy JSObject properties.
- new_array->set_properties(result->properties()); // Empty FixedArray.
-
- // Copy JSObject elements as copy-on-write.
- FixedArray* elements = FixedArray::cast(result->elements());
- if (elements != Heap::empty_fixed_array()) {
- elements->set_map(Heap::fixed_cow_array_map());
- }
- new_array->set_elements(elements);
-
- // Copy JSArray length.
- new_array->set_length(result->length());
-
- // Copy JSRegExpResult in-object property fields input and index.
- new_array->FastPropertyAtPut(JSRegExpResult::kIndexIndex,
- result->FastPropertyAt(
- JSRegExpResult::kIndexIndex));
- new_array->FastPropertyAtPut(JSRegExpResult::kInputIndex,
- result->FastPropertyAt(
- JSRegExpResult::kInputIndex));
- return new_array;
-}
-
-
static MaybeObject* Runtime_RegExpInitializeObject(Arguments args) {
AssertNoAllocation no_alloc;
ASSERT(args.length() == 5);
F(RegExpExecMultiple, 4, 1) \
F(RegExpInitializeObject, 5, 1) \
F(RegExpConstructResult, 3, 1) \
- F(RegExpCloneResult, 1, 1) \
\
/* JSON */ \
F(ParseJson, 1, 1) \
// ----------------------------------------------------------------------------
// INLINE_AND_RUNTIME_FUNCTION_LIST defines all inlined functions accessed
// with a native call of the form %_name from within JS code that also have
- // a corresponding runtime function, that is called for slow cases.
+// a corresponding runtime function, that is called for slow cases.
// Entries have the form F(name, number of arguments, number of return values).
#define INLINE_RUNTIME_FUNCTION_LIST(F) \
F(IsConstructCall, 0, 1) \
F(StringCompare, 2, 1) \
F(RegExpExec, 4, 1) \
F(RegExpConstructResult, 3, 1) \
- F(RegExpCloneResult, 1, 1) \
F(GetFromCache, 2, 1) \
F(NumberToString, 1, 1) \
F(SwapElements, 3, 1)
}
-function CloneDenseArray(array) {
- if (array === null) return null;
- var clone = new $Array(array.length);
- for (var i = 0; i < array.length; i++) {
- clone[i] = array[i];
- }
- return clone;
-}
-
-
// ECMA-262 section 15.5.4.9
//
// This function is implementation specific. For now, we do not
if (IS_REGEXP(regexp)) {
if (!regexp.global) return regexp.exec(subject);
- var cache = regExpCache;
var saveAnswer = false;
- if (%_ObjectEquals(cache.type, 'match') &&
- %_IsRegExpEquivalent(cache.regExp, regexp) &&
- %_ObjectEquals(cache.subject, subject)) {
- if (cache.answerSaved) {
- return CloneDenseArray(cache.answer);
- } else {
- saveAnswer = true;
- }
- }
%_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]);
// lastMatchInfo is defined in regexp.js.
- var result = %StringMatch(subject, regexp, lastMatchInfo);
- cache.type = 'match';
- cache.regExp = regexp;
- cache.subject = subject;
- if (saveAnswer) cache.answer = CloneDenseArray(result);
- cache.answerSaved = saveAnswer;
- return result;
+ return %StringMatch(subject, regexp, lastMatchInfo);
}
// Non-regexp argument.
regexp = new $RegExp(regexp);
- // Don't check regexp exec cache, since the regexp is new.
- // TODO(lrn): Change this if we start caching regexps here.
return RegExpExecNoTests(regexp, subject, 0);
}
if (IS_REGEXP(search)) {
%_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]);
if (IS_FUNCTION(replace)) {
- regExpCache.type = 'none';
if (search.global) {
return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
} else {
// Helper function for regular expressions in String.prototype.replace.
function StringReplaceRegExp(subject, regexp, replace) {
- var cache = regExpCache;
- if (%_ObjectEquals(cache.type, 'replace') &&
- %_IsRegExpEquivalent(cache.regExp, regexp) &&
- %_ObjectEquals(cache.replaceString, replace) &&
- %_ObjectEquals(cache.subject, subject)) {
- return cache.answer;
- }
- replace = TO_STRING_INLINE(replace);
- var answer = %StringReplaceRegExpWithString(subject,
- regexp,
- replace,
- lastMatchInfo);
- cache.subject = subject;
- cache.regExp = regexp;
- cache.replaceString = replace;
- cache.answer = answer;
- cache.type = 'replace';
- return answer;
+ return %StringReplaceRegExpWithString(subject,
+ regexp,
+ TO_STRING_INLINE(replace),
+ lastMatchInfo);
}
return result;
}
- var cache = regExpCache;
var saveAnswer = false;
- if (%_ObjectEquals(cache.type, 'split') &&
- %_IsRegExpEquivalent(cache.regExp, separator) &&
- %_ObjectEquals(cache.subject, subject) &&
- %_ObjectEquals(cache.splitLimit, limit)) {
- if (cache.answerSaved) {
- return CloneDenseArray(cache.answer);
- } else {
- saveAnswer = true;
- }
- }
-
- cache.type = 'split';
- cache.regExp = separator;
- cache.subject = subject;
- cache.splitLimit = limit;
-
%_Log('regexp', 'regexp-split,%0S,%1r', [subject, separator]);
if (length === 0) {
- cache.answerSaved = true;
if (splitMatch(separator, subject, 0, 0) != null) {
- cache.answer = [];
return [];
}
- cache.answer = [subject];
return [subject];
}
startIndex = currentIndex = endIndex;
}
- if (saveAnswer) cache.answer = CloneDenseArray(result);
- cache.answerSaved = saveAnswer;
return result;
}
}
-void CodeGenerator::GenerateRegExpCloneResult(ZoneList<Expression*>* args) {
- ASSERT_EQ(1, args->length());
-
- Load(args->at(0));
- Result object_result = frame_->Pop();
- object_result.ToRegister(rax);
- object_result.Unuse();
- {
- VirtualFrame::SpilledScope spilled_scope;
-
- Label done;
- __ JumpIfSmi(rax, &done);
-
- // Load JSRegExpResult map into rdx.
- // Arguments to this function should be results of calling RegExp exec,
- // which is either an unmodified JSRegExpResult or null. Anything not having
- // the unmodified JSRegExpResult map is returned unmodified.
- // This also ensures that elements are fast.
-
- __ movq(rdx, ContextOperand(rsi, Context::GLOBAL_INDEX));
- __ movq(rdx, FieldOperand(rdx, GlobalObject::kGlobalContextOffset));
- __ movq(rdx, ContextOperand(rdx, Context::REGEXP_RESULT_MAP_INDEX));
- __ cmpq(rdx, FieldOperand(rax, HeapObject::kMapOffset));
- __ j(not_equal, &done);
-
- if (FLAG_debug_code) {
- // Check that object really has empty properties array, as the map
- // should guarantee.
- __ CompareRoot(FieldOperand(rax, JSObject::kPropertiesOffset),
- Heap::kEmptyFixedArrayRootIndex);
- __ Check(equal, "JSRegExpResult: default map but non-empty properties.");
- }
-
- DeferredAllocateInNewSpace* allocate_fallback =
- new DeferredAllocateInNewSpace(JSRegExpResult::kSize,
- rbx,
- rdx.bit() | rax.bit());
-
- // All set, copy the contents to a new object.
- __ AllocateInNewSpace(JSRegExpResult::kSize,
- rbx,
- no_reg,
- no_reg,
- allocate_fallback->entry_label(),
- TAG_OBJECT);
- __ bind(allocate_fallback->exit_label());
-
- STATIC_ASSERT(JSRegExpResult::kSize % (2 * kPointerSize) == 0);
- // There is an even number of fields, so unroll the loop once
- // for efficiency.
- for (int i = 0; i < JSRegExpResult::kSize; i += 2 * kPointerSize) {
- STATIC_ASSERT(JSObject::kMapOffset % (2 * kPointerSize) == 0);
- if (i != JSObject::kMapOffset) {
- // The map was already loaded into edx.
- __ movq(rdx, FieldOperand(rax, i));
- }
- __ movq(rcx, FieldOperand(rax, i + kPointerSize));
-
- STATIC_ASSERT(JSObject::kElementsOffset % (2 * kPointerSize) == 0);
- if (i == JSObject::kElementsOffset) {
- // If the elements array isn't empty, make it copy-on-write
- // before copying it.
- Label empty;
- __ CompareRoot(rdx, Heap::kEmptyFixedArrayRootIndex);
- __ j(equal, &empty);
- __ LoadRoot(kScratchRegister, Heap::kFixedCOWArrayMapRootIndex);
- __ movq(FieldOperand(rdx, HeapObject::kMapOffset), kScratchRegister);
- __ bind(&empty);
- }
- __ movq(FieldOperand(rbx, i), rdx);
- __ movq(FieldOperand(rbx, i + kPointerSize), rcx);
- }
- __ movq(rax, rbx);
-
- __ bind(&done);
- }
- frame_->Push(rax);
-}
-
-
class DeferredSearchCache: public DeferredCode {
public:
DeferredSearchCache(Register dst,
void GenerateRegExpConstructResult(ZoneList<Expression*>* args);
- void GenerateRegExpCloneResult(ZoneList<Expression*>* args);
-
// Support for fast native caches.
void GenerateGetFromCache(ZoneList<Expression*>* args);