Chromium Code Reviews| Index: src/x64/code-stubs-x64.cc |
| diff --git a/src/x64/code-stubs-x64.cc b/src/x64/code-stubs-x64.cc |
| index 7b57c2cd19e73f7c74b0689a69a5f234d396cda1..fc080f4c4cc2fc63abbf122d7d4f4e23b903892d 100644 |
| --- a/src/x64/code-stubs-x64.cc |
| +++ b/src/x64/code-stubs-x64.cc |
| @@ -351,177 +351,10 @@ void MathPowStub::Generate(MacroAssembler* masm) { |
| } |
| void RegExpExecStub::Generate(MacroAssembler* masm) { |
| - // Just jump directly to runtime if native RegExp is not selected at compile |
| - // time or if regexp entry in generated code is turned off runtime switch or |
| - // at compilation. |
| #ifdef V8_INTERPRETED_REGEXP |
| - __ TailCallRuntime(Runtime::kRegExpExec); |
| + // This case is handled prior to the RegExpExecStub call. |
| + __ Abort(kUnexpectedRegExpExecCall); |
| #else // V8_INTERPRETED_REGEXP |
| - |
| - // Stack frame on entry. |
| - // rsp[0] : return address |
| - // rsp[8] : last_match_info (expected JSArray) |
| - // rsp[16] : previous index |
| - // rsp[24] : subject string |
| - // rsp[32] : JSRegExp object |
| - |
| - enum RegExpExecStubArgumentIndices { |
| - JS_REG_EXP_OBJECT_ARGUMENT_INDEX, |
| - SUBJECT_STRING_ARGUMENT_INDEX, |
| - PREVIOUS_INDEX_ARGUMENT_INDEX, |
| - LAST_MATCH_INFO_ARGUMENT_INDEX, |
| - REG_EXP_EXEC_ARGUMENT_COUNT |
| - }; |
| - |
| - StackArgumentsAccessor args(rsp, REG_EXP_EXEC_ARGUMENT_COUNT, |
| - ARGUMENTS_DONT_CONTAIN_RECEIVER); |
| - Label runtime; |
| - // Ensure that a RegExp stack is allocated. |
| - ExternalReference address_of_regexp_stack_memory_address = |
| - ExternalReference::address_of_regexp_stack_memory_address(isolate()); |
| - ExternalReference address_of_regexp_stack_memory_size = |
| - ExternalReference::address_of_regexp_stack_memory_size(isolate()); |
| - __ Load(kScratchRegister, address_of_regexp_stack_memory_size); |
| - __ testp(kScratchRegister, kScratchRegister); |
| - __ j(zero, &runtime); |
| - |
| - // Check that the first argument is a JSRegExp object. |
| - __ movp(rax, args.GetArgumentOperand(JS_REG_EXP_OBJECT_ARGUMENT_INDEX)); |
| - __ JumpIfSmi(rax, &runtime); |
| - __ CmpObjectType(rax, JS_REGEXP_TYPE, kScratchRegister); |
| - __ j(not_equal, &runtime); |
| - |
| - // Check that the RegExp has been compiled (data contains a fixed array). |
| - __ movp(rax, FieldOperand(rax, JSRegExp::kDataOffset)); |
| - if (FLAG_debug_code) { |
| - Condition is_smi = masm->CheckSmi(rax); |
| - __ Check(NegateCondition(is_smi), |
| - kUnexpectedTypeForRegExpDataFixedArrayExpected); |
| - __ CmpObjectType(rax, FIXED_ARRAY_TYPE, kScratchRegister); |
| - __ Check(equal, kUnexpectedTypeForRegExpDataFixedArrayExpected); |
| - } |
| - |
| - // rax: RegExp data (FixedArray) |
| - // Check the type of the RegExp. Only continue if type is JSRegExp::IRREGEXP. |
| - __ SmiToInteger32(rbx, FieldOperand(rax, JSRegExp::kDataTagOffset)); |
|
Igor Sheludko
2017/03/15 00:39:04
Please also delete these unused offset constants f
jgruber
2017/03/15 13:43:44
Done.
|
| - __ cmpl(rbx, Immediate(JSRegExp::IRREGEXP)); |
| - __ j(not_equal, &runtime); |
| - |
| - // rax: RegExp data (FixedArray) |
| - // Check that the number of captures fit in the static offsets vector buffer. |
| - __ SmiToInteger32(rdx, |
| - FieldOperand(rax, JSRegExp::kIrregexpCaptureCountOffset)); |
| - // Check (number_of_captures + 1) * 2 <= offsets vector size |
| - // Or number_of_captures <= offsets vector size / 2 - 1 |
| - STATIC_ASSERT(Isolate::kJSRegexpStaticOffsetsVectorSize >= 2); |
| - __ cmpl(rdx, Immediate(Isolate::kJSRegexpStaticOffsetsVectorSize / 2 - 1)); |
| - __ j(above, &runtime); |
| - |
| - // Reset offset for possibly sliced string. |
| - __ Set(r14, 0); |
| - __ movp(rdi, args.GetArgumentOperand(SUBJECT_STRING_ARGUMENT_INDEX)); |
| - __ JumpIfSmi(rdi, &runtime); |
| - __ movp(r15, rdi); // Make a copy of the original subject string. |
| - // rax: RegExp data (FixedArray) |
| - // rdi: subject string |
| - // r15: subject string |
| - // Handle subject string according to its encoding and representation: |
| - // (1) Sequential two byte? If yes, go to (9). |
| - // (2) Sequential one byte? If yes, go to (5). |
| - // (3) Sequential or cons? If not, go to (6). |
| - // (4) Cons string. If the string is flat, replace subject with first string |
| - // and go to (1). Otherwise bail out to runtime. |
| - // (5) One byte sequential. Load regexp code for one byte. |
| - // (E) Carry on. |
| - /// [...] |
| - |
| - // Deferred code at the end of the stub: |
| - // (6) Long external string? If not, go to (10). |
| - // (7) External string. Make it, offset-wise, look like a sequential string. |
| - // (8) Is the external string one byte? If yes, go to (5). |
| - // (9) Two byte sequential. Load regexp code for two byte. Go to (E). |
| - // (10) Short external string or not a string? If yes, bail out to runtime. |
| - // (11) Sliced or thin string. Replace subject with parent. Go to (1). |
| - |
| - Label seq_one_byte_string /* 5 */, seq_two_byte_string /* 9 */, |
| - external_string /* 7 */, check_underlying /* 1 */, |
| - not_seq_nor_cons /* 6 */, check_code /* E */, not_long_external /* 10 */; |
| - |
| - __ bind(&check_underlying); |
| - __ movp(rbx, FieldOperand(rdi, HeapObject::kMapOffset)); |
| - __ movzxbl(rbx, FieldOperand(rbx, Map::kInstanceTypeOffset)); |
| - |
| - // (1) Sequential two byte? If yes, go to (9). |
| - __ andb(rbx, Immediate(kIsNotStringMask | |
| - kStringRepresentationMask | |
| - kStringEncodingMask | |
| - kShortExternalStringMask)); |
| - STATIC_ASSERT((kStringTag | kSeqStringTag | kTwoByteStringTag) == 0); |
| - __ j(zero, &seq_two_byte_string); // Go to (9). |
| - |
| - // (2) Sequential one byte? If yes, go to (5). |
| - // Any other sequential string must be one byte. |
| - __ andb(rbx, Immediate(kIsNotStringMask | |
| - kStringRepresentationMask | |
| - kShortExternalStringMask)); |
| - __ j(zero, &seq_one_byte_string, Label::kNear); // Go to (5). |
| - |
| - // (3) Sequential or cons? If not, go to (6). |
| - // We check whether the subject string is a cons, since sequential strings |
| - // have already been covered. |
| - STATIC_ASSERT(kConsStringTag < kExternalStringTag); |
| - STATIC_ASSERT(kSlicedStringTag > kExternalStringTag); |
| - STATIC_ASSERT(kThinStringTag > kExternalStringTag); |
| - STATIC_ASSERT(kIsNotStringMask > kExternalStringTag); |
| - STATIC_ASSERT(kShortExternalStringTag > kExternalStringTag); |
| - __ cmpp(rbx, Immediate(kExternalStringTag)); |
| - __ j(greater_equal, ¬_seq_nor_cons); // Go to (6). |
| - |
| - // (4) Cons string. Check that it's flat. |
| - // Replace subject with first string and reload instance type. |
| - __ CompareRoot(FieldOperand(rdi, ConsString::kSecondOffset), |
| - Heap::kempty_stringRootIndex); |
| - __ j(not_equal, &runtime); |
| - __ movp(rdi, FieldOperand(rdi, ConsString::kFirstOffset)); |
| - __ jmp(&check_underlying); |
| - |
| - // (5) One byte sequential. Load regexp code for one byte. |
| - __ bind(&seq_one_byte_string); |
| - // rax: RegExp data (FixedArray) |
| - __ movp(r11, FieldOperand(rax, JSRegExp::kDataOneByteCodeOffset)); |
| - __ Set(rcx, 1); // Type is one byte. |
| - |
| - // (E) Carry on. String handling is done. |
| - __ bind(&check_code); |
| - // r11: irregexp code |
| - // Check that the irregexp code has been generated for the actual string |
| - // encoding. If it has, the field contains a code object otherwise it contains |
| - // smi (code flushing support) |
| - __ JumpIfSmi(r11, &runtime); |
| - |
| - // rdi: sequential subject string (or look-alike, external string) |
| - // r15: original subject string |
| - // rcx: encoding of subject string (1 if one_byte, 0 if two_byte); |
| - // r11: code |
| - // Load used arguments before starting to push arguments for call to native |
| - // RegExp code to avoid handling changing stack height. |
| - // We have to use r15 instead of rdi to load the length because rdi might |
| - // have been only made to look like a sequential string when it actually |
| - // is an external string. |
| - __ movp(rbx, args.GetArgumentOperand(PREVIOUS_INDEX_ARGUMENT_INDEX)); |
| - __ JumpIfNotSmi(rbx, &runtime); |
| - __ SmiCompare(rbx, FieldOperand(r15, String::kLengthOffset)); |
| - __ j(above_equal, &runtime); |
| - __ SmiToInteger64(rbx, rbx); |
| - |
| - // rdi: subject string |
| - // rbx: previous index |
| - // rcx: encoding of subject string (1 if one_byte 0 if two_byte); |
| - // r11: code |
| - // All checks done. Now push arguments for native regexp code. |
| - Counters* counters = isolate()->counters(); |
| - __ IncrementCounter(counters->regexp_entry_native(), 1); |
| - |
| // Isolates: note we add an additional parameter here (isolate pointer). |
| static const int kRegExpExecuteArguments = 9; |
| int argument_slots_on_stack = |
| @@ -539,11 +372,15 @@ void RegExpExecStub::Generate(MacroAssembler* masm) { |
| Immediate(1)); |
| // Argument 7: Start (high end) of backtracking stack memory area. |
| + ExternalReference address_of_regexp_stack_memory_address = |
| + ExternalReference::address_of_regexp_stack_memory_address(isolate()); |
| + ExternalReference address_of_regexp_stack_memory_size = |
| + ExternalReference::address_of_regexp_stack_memory_size(isolate()); |
| __ Move(kScratchRegister, address_of_regexp_stack_memory_address); |
| - __ movp(r9, Operand(kScratchRegister, 0)); |
| + __ movp(r12, Operand(kScratchRegister, 0)); |
| __ Move(kScratchRegister, address_of_regexp_stack_memory_size); |
| - __ addp(r9, Operand(kScratchRegister, 0)); |
| - __ movq(Operand(rsp, (argument_slots_on_stack - 3) * kRegisterSize), r9); |
| + __ addp(r12, Operand(kScratchRegister, 0)); |
| + __ movq(Operand(rsp, (argument_slots_on_stack - 3) * kRegisterSize), r12); |
| // Argument 6: Set the number of capture registers to zero to force global |
| // regexps to behave as non-global. This does not affect non-global regexps. |
| @@ -556,222 +393,38 @@ void RegExpExecStub::Generate(MacroAssembler* masm) { |
| #endif |
| // Argument 5: static offsets vector buffer. |
| - __ LoadAddress( |
| - r8, ExternalReference::address_of_static_offsets_vector(isolate())); |
| // Argument 5 passed in r8 on Linux and on the stack on Windows. |
| #ifdef _WIN64 |
| - __ movq(Operand(rsp, (argument_slots_on_stack - 5) * kRegisterSize), r8); |
| + __ LoadAddress( |
| + r12, ExternalReference::address_of_static_offsets_vector(isolate())); |
| + __ movq(Operand(rsp, (argument_slots_on_stack - 5) * kRegisterSize), r12); |
| +#else // _WIN64 |
| + __ LoadAddress( |
| + r8, ExternalReference::address_of_static_offsets_vector(isolate())); |
| #endif |
| - // rdi: subject string |
| - // rbx: previous index |
| - // rcx: encoding of subject string (1 if one_byte 0 if two_byte); |
| - // r11: code |
| - // r14: slice offset |
| - // r15: original subject string |
| - |
| // Argument 2: Previous index. |
| - __ movp(arg_reg_2, rbx); |
| + // TODO(jgruber): Ideally, LastIndexRegister would already equal arg_reg_2, |
| + // but that makes register allocation fail. |
| + __ movp(arg_reg_2, RegExpExecDescriptor::LastIndexRegister()); |
| // Argument 4: End of string data |
| // Argument 3: Start of string data |
| - Label setup_two_byte, setup_rest, got_length, length_not_from_slice; |
| - // Prepare start and end index of the input. |
| - // Load the length from the original sliced string if that is the case. |
| - __ addp(rbx, r14); |
| - __ SmiToInteger32(arg_reg_3, FieldOperand(r15, String::kLengthOffset)); |
| - __ addp(r14, arg_reg_3); // Using arg3 as scratch. |
| - |
| - // rbx: start index of the input |
| - // r14: end index of the input |
| - // r15: original subject string |
| - __ testb(rcx, rcx); // Last use of rcx as encoding of subject string. |
| - __ j(zero, &setup_two_byte, Label::kNear); |
| - __ leap(arg_reg_4, |
| - FieldOperand(rdi, r14, times_1, SeqOneByteString::kHeaderSize)); |
| - __ leap(arg_reg_3, |
| - FieldOperand(rdi, rbx, times_1, SeqOneByteString::kHeaderSize)); |
| - __ jmp(&setup_rest, Label::kNear); |
| - __ bind(&setup_two_byte); |
| - __ leap(arg_reg_4, |
| - FieldOperand(rdi, r14, times_2, SeqTwoByteString::kHeaderSize)); |
| - __ leap(arg_reg_3, |
| - FieldOperand(rdi, rbx, times_2, SeqTwoByteString::kHeaderSize)); |
| - __ bind(&setup_rest); |
| + CHECK(arg_reg_4.is(RegExpExecDescriptor::StringEndRegister())); |
| + CHECK(arg_reg_3.is(RegExpExecDescriptor::StringStartRegister())); |
| // Argument 1: Original subject string. |
| - // The original subject is in the previous stack frame. Therefore we have to |
| - // use rbp, which points exactly to one pointer size below the previous rsp. |
| - // (Because creating a new stack frame pushes the previous rbp onto the stack |
| - // and thereby moves up rsp by one kPointerSize.) |
| - __ movp(arg_reg_1, r15); |
| + CHECK(arg_reg_1.is(RegExpExecDescriptor::StringRegister())); |
| - // Locate the code entry and call it. |
| - __ addp(r11, Immediate(Code::kHeaderSize - kHeapObjectTag)); |
| - __ call(r11); |
| + __ addp(RegExpExecDescriptor::CodeRegister(), |
| + Immediate(Code::kHeaderSize - kHeapObjectTag)); |
| + __ call(RegExpExecDescriptor::CodeRegister()); |
| __ LeaveApiExitFrame(true); |
| - // Check the result. |
| - Label success; |
| - Label exception; |
| - __ cmpl(rax, Immediate(1)); |
| - // We expect exactly one result since we force the called regexp to behave |
| - // as non-global. |
| - __ j(equal, &success, Label::kNear); |
| - __ cmpl(rax, Immediate(NativeRegExpMacroAssembler::EXCEPTION)); |
| - __ j(equal, &exception); |
| - __ cmpl(rax, Immediate(NativeRegExpMacroAssembler::FAILURE)); |
| - // If none of the above, it can only be retry. |
| - // Handle that in the runtime system. |
| - __ j(not_equal, &runtime); |
| - |
| - // For failure return null. |
| - __ LoadRoot(rax, Heap::kNullValueRootIndex); |
| - __ ret(REG_EXP_EXEC_ARGUMENT_COUNT * kPointerSize); |
| - |
| - // Load RegExp data. |
| - __ bind(&success); |
| - __ movp(rax, args.GetArgumentOperand(JS_REG_EXP_OBJECT_ARGUMENT_INDEX)); |
| - __ movp(rcx, FieldOperand(rax, JSRegExp::kDataOffset)); |
| - __ SmiToInteger32(rax, |
| - FieldOperand(rcx, JSRegExp::kIrregexpCaptureCountOffset)); |
| - // Calculate number of capture registers (number_of_captures + 1) * 2. |
| - __ leal(rdx, Operand(rax, rax, times_1, 2)); |
| - |
| - // rdx: Number of capture registers |
| - // Check that the last match info is a FixedArray. |
| - __ movp(rbx, args.GetArgumentOperand(LAST_MATCH_INFO_ARGUMENT_INDEX)); |
| - __ JumpIfSmi(rbx, &runtime); |
| - // Check that the object has fast elements. |
| - __ movp(rax, FieldOperand(rbx, HeapObject::kMapOffset)); |
| - __ CompareRoot(rax, Heap::kFixedArrayMapRootIndex); |
| - __ j(not_equal, &runtime); |
| - // Check that the last match info has space for the capture registers and the |
| - // additional information. Ensure no overflow in add. |
| - STATIC_ASSERT(FixedArray::kMaxLength < kMaxInt - FixedArray::kLengthOffset); |
| - __ SmiToInteger32(rax, FieldOperand(rbx, FixedArray::kLengthOffset)); |
| - __ subl(rax, Immediate(RegExpMatchInfo::kLastMatchOverhead)); |
| - __ cmpl(rdx, rax); |
| - __ j(greater, &runtime); |
| - |
| - // rbx: last_match_info (FixedArray) |
| - // rdx: number of capture registers |
| - // Store the capture count. |
| - __ Integer32ToSmi(kScratchRegister, rdx); |
| - __ movp(FieldOperand(rbx, RegExpMatchInfo::kNumberOfCapturesOffset), |
| - kScratchRegister); |
| - // Store last subject and last input. |
| - __ movp(rax, args.GetArgumentOperand(SUBJECT_STRING_ARGUMENT_INDEX)); |
| - __ movp(FieldOperand(rbx, RegExpMatchInfo::kLastSubjectOffset), rax); |
| - __ movp(rcx, rax); |
| - __ RecordWriteField(rbx, RegExpMatchInfo::kLastSubjectOffset, rax, rdi, |
| - kDontSaveFPRegs); |
| - __ movp(rax, rcx); |
| - __ movp(FieldOperand(rbx, RegExpMatchInfo::kLastInputOffset), rax); |
| - __ RecordWriteField(rbx, RegExpMatchInfo::kLastInputOffset, rax, rdi, |
| - kDontSaveFPRegs); |
| - |
| - // Get the static offsets vector filled by the native regexp code. |
| - __ LoadAddress( |
| - rcx, ExternalReference::address_of_static_offsets_vector(isolate())); |
| - |
| - // rbx: last_match_info (FixedArray) |
| - // rcx: offsets vector |
| - // rdx: number of capture registers |
| - Label next_capture, done; |
| - // Capture register counter starts from number of capture registers and |
| - // counts down until wrapping after zero. |
| - __ bind(&next_capture); |
| - __ subp(rdx, Immediate(1)); |
| - __ j(negative, &done, Label::kNear); |
| - // Read the value from the static offsets vector buffer and make it a smi. |
| - __ movl(rdi, Operand(rcx, rdx, times_int_size, 0)); |
| - __ Integer32ToSmi(rdi, rdi); |
| - // Store the smi value in the last match info. |
| - __ movp(FieldOperand(rbx, rdx, times_pointer_size, |
| - RegExpMatchInfo::kFirstCaptureOffset), |
| - rdi); |
| - __ jmp(&next_capture); |
| - __ bind(&done); |
| - |
| - // Return last match info. |
| - __ movp(rax, rbx); |
| - __ ret(REG_EXP_EXEC_ARGUMENT_COUNT * kPointerSize); |
| - |
| - __ bind(&exception); |
| - // Result must now be exception. If there is no pending exception already a |
| - // stack overflow (on the backtrack stack) was detected in RegExp code but |
| - // haven't created the exception yet. Handle that in the runtime system. |
| - // TODO(592): Rerunning the RegExp to get the stack overflow exception. |
| - ExternalReference pending_exception_address( |
| - Isolate::kPendingExceptionAddress, isolate()); |
| - Operand pending_exception_operand = |
| - masm->ExternalOperand(pending_exception_address, rbx); |
| - __ movp(rax, pending_exception_operand); |
| - __ LoadRoot(rdx, Heap::kTheHoleValueRootIndex); |
| - __ cmpp(rax, rdx); |
| - __ j(equal, &runtime); |
| - |
| - // For exception, throw the exception again. |
| - __ TailCallRuntime(Runtime::kRegExpExecReThrow); |
| - |
| - // Do the runtime call to execute the regexp. |
| - __ bind(&runtime); |
| - __ TailCallRuntime(Runtime::kRegExpExec); |
| - |
| - // Deferred code for string handling. |
| - // (6) Long external string? If not, go to (10). |
| - __ bind(¬_seq_nor_cons); |
| - // Compare flags are still set from (3). |
| - __ j(greater, ¬_long_external, Label::kNear); // Go to (10). |
| - |
| - // (7) External string. Short external strings have been ruled out. |
| - __ bind(&external_string); |
| - __ movp(rbx, FieldOperand(rdi, HeapObject::kMapOffset)); |
| - __ movzxbl(rbx, FieldOperand(rbx, Map::kInstanceTypeOffset)); |
| - if (FLAG_debug_code) { |
| - // Assert that we do not have a cons or slice (indirect strings) here. |
| - // Sequential strings have already been ruled out. |
| - __ testb(rbx, Immediate(kIsIndirectStringMask)); |
| - __ Assert(zero, kExternalStringExpectedButNotFound); |
| - } |
| - __ movp(rdi, FieldOperand(rdi, ExternalString::kResourceDataOffset)); |
| - // Move the pointer so that offset-wise, it looks like a sequential string. |
| - STATIC_ASSERT(SeqTwoByteString::kHeaderSize == SeqOneByteString::kHeaderSize); |
| - __ subp(rdi, Immediate(SeqTwoByteString::kHeaderSize - kHeapObjectTag)); |
| - STATIC_ASSERT(kTwoByteStringTag == 0); |
| - // (8) Is the external string one byte? If yes, go to (5). |
| - __ testb(rbx, Immediate(kStringEncodingMask)); |
| - __ j(not_zero, &seq_one_byte_string); // Go to (5). |
| - |
| - // rdi: subject string (flat two-byte) |
| - // rax: RegExp data (FixedArray) |
| - // (9) Two byte sequential. Load regexp code for two byte. Go to (E). |
| - __ bind(&seq_two_byte_string); |
| - __ movp(r11, FieldOperand(rax, JSRegExp::kDataUC16CodeOffset)); |
| - __ Set(rcx, 0); // Type is two byte. |
| - __ jmp(&check_code); // Go to (E). |
| - |
| - // (10) Not a string or a short external string? If yes, bail out to runtime. |
| - __ bind(¬_long_external); |
| - // Catch non-string subject or short external string. |
| - STATIC_ASSERT(kNotStringTag != 0 && kShortExternalStringTag !=0); |
| - __ testb(rbx, Immediate(kIsNotStringMask | kShortExternalStringMask)); |
| - __ j(not_zero, &runtime); |
| - |
| - // (11) Sliced or thin string. Replace subject with parent. Go to (1). |
| - Label thin_string; |
| - __ cmpl(rbx, Immediate(kThinStringTag)); |
| - __ j(equal, &thin_string, Label::kNear); |
| - // Load offset into r14 and replace subject string with parent. |
| - __ SmiToInteger32(r14, FieldOperand(rdi, SlicedString::kOffsetOffset)); |
| - __ movp(rdi, FieldOperand(rdi, SlicedString::kParentOffset)); |
| - __ jmp(&check_underlying); |
| - |
| - __ bind(&thin_string); |
| - __ movp(rdi, FieldOperand(rdi, ThinString::kActualOffset)); |
| - __ jmp(&check_underlying); |
| + // TODO(jgruber): Don't tag return value once this is supported by stubs. |
| + __ Integer32ToSmi(rax, rax); |
| + __ ret(0 * kPointerSize); |
| #endif // V8_INTERPRETED_REGEXP |
| } |