// Copyright 2013 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #if V8_TARGET_ARCH_ARM64 #include "src/regexp/arm64/regexp-macro-assembler-arm64.h" #include "src/arm64/macro-assembler-arm64-inl.h" #include "src/log.h" #include "src/macro-assembler.h" #include "src/objects-inl.h" #include "src/regexp/regexp-macro-assembler.h" #include "src/regexp/regexp-stack.h" #include "src/snapshot/embedded-data.h" #include "src/unicode.h" namespace v8 { namespace internal { #ifndef V8_INTERPRETED_REGEXP /* * This assembler uses the following register assignment convention: * - w19 : Used to temporarely store a value before a call to C code. * See CheckNotBackReferenceIgnoreCase. * - x20 : Pointer to the current Code object, * it includes the heap object tag. * - w21 : Current position in input, as negative offset from * the end of the string. Please notice that this is * the byte offset, not the character offset! * - w22 : Currently loaded character. Must be loaded using * LoadCurrentCharacter before using any of the dispatch methods. * - x23 : Points to tip of backtrack stack. * - w24 : Position of the first character minus one: non_position_value. * Used to initialize capture registers. * - x25 : Address at the end of the input string: input_end. * Points to byte after last character in input. * - x26 : Address at the start of the input string: input_start. * - w27 : Where to start in the input string. * - x28 : Output array pointer. * - x29/fp : Frame pointer. Used to access arguments, local variables and * RegExp registers. * - x16/x17 : IP registers, used by assembler. Very volatile. * - sp : Points to tip of C stack. * * - x0-x7 : Used as a cache to store 32 bit capture registers. These * registers need to be retained every time a call to C code * is done. * * The remaining registers are free for computations. * Each call to a public method should retain this convention. * * The stack will have the following structure: * * Location Name Description * (as referred to in * the code) * * - fp[96] isolate Address of the current isolate. * ^^^ sp when called ^^^ * - fp[88] lr Return from the RegExp code. * - fp[80] r29 Old frame pointer (CalleeSaved). * - fp[0..72] r19-r28 Backup of CalleeSaved registers. * - fp[-8] direct_call 1 => Direct call from JavaScript code. * 0 => Call through the runtime system. * - fp[-16] stack_base High end of the memory area to use as * the backtracking stack. * - fp[-24] output_size Output may fit multiple sets of matches. * - fp[-32] input Handle containing the input string. * - fp[-40] success_counter * ^^^^^^^^^^^^^ From here and downwards we store 32 bit values ^^^^^^^^^^^^^ * - fp[-44] register N Capture registers initialized with * - fp[-48] register N + 1 non_position_value. * ... The first kNumCachedRegisters (N) registers * ... are cached in x0 to x7. * ... Only positions must be stored in the first * - ... num_saved_registers_ registers. * - ... * - register N + num_registers - 1 * ^^^^^^^^^ sp ^^^^^^^^^ * * The first num_saved_registers_ registers are initialized to point to * "character -1" in the string (i.e., char_size() bytes before the first * character of the string). The remaining registers start out as garbage. * * The data up to the return address must be placed there by the calling * code and the remaining arguments are passed in registers, e.g. by calling the * code entry as cast to a function with the signature: * int (*match)(String input_string, * int start_index, * Address start, * Address end, * int* capture_output_array, * int num_capture_registers, * byte* stack_area_base, * bool direct_call = false, * Isolate* isolate); * The call is performed by NativeRegExpMacroAssembler::Execute() * (in regexp-macro-assembler.cc) via the GeneratedCode wrapper. */ #define __ ACCESS_MASM(masm_) RegExpMacroAssemblerARM64::RegExpMacroAssemblerARM64(Isolate* isolate, Zone* zone, Mode mode, int registers_to_save) : NativeRegExpMacroAssembler(isolate, zone), masm_(new MacroAssembler(isolate, nullptr, kRegExpCodeSize, CodeObjectRequired::kYes)), mode_(mode), num_registers_(registers_to_save), num_saved_registers_(registers_to_save), entry_label_(), start_label_(), success_label_(), backtrack_label_(), exit_label_() { DCHECK_EQ(0, registers_to_save % 2); // We can cache at most 16 W registers in x0-x7. STATIC_ASSERT(kNumCachedRegisters <= 16); STATIC_ASSERT((kNumCachedRegisters % 2) == 0); __ B(&entry_label_); // We'll write the entry code later. __ Bind(&start_label_); // And then continue from here. } RegExpMacroAssemblerARM64::~RegExpMacroAssemblerARM64() { delete masm_; // Unuse labels in case we throw away the assembler without calling GetCode. entry_label_.Unuse(); start_label_.Unuse(); success_label_.Unuse(); backtrack_label_.Unuse(); exit_label_.Unuse(); check_preempt_label_.Unuse(); stack_overflow_label_.Unuse(); } int RegExpMacroAssemblerARM64::stack_limit_slack() { return RegExpStack::kStackLimitSlack; } void RegExpMacroAssemblerARM64::AdvanceCurrentPosition(int by) { if (by != 0) { __ Add(current_input_offset(), current_input_offset(), by * char_size()); } } void RegExpMacroAssemblerARM64::AdvanceRegister(int reg, int by) { DCHECK((reg >= 0) && (reg < num_registers_)); if (by != 0) { RegisterState register_state = GetRegisterState(reg); switch (register_state) { case STACKED: __ Ldr(w10, register_location(reg)); __ Add(w10, w10, by); __ Str(w10, register_location(reg)); break; case CACHED_LSW: { Register to_advance = GetCachedRegister(reg); __ Add(to_advance, to_advance, by); break; } case CACHED_MSW: { Register to_advance = GetCachedRegister(reg); __ Add(to_advance, to_advance, static_cast<int64_t>(by) << kWRegSizeInBits); break; } default: UNREACHABLE(); break; } } } void RegExpMacroAssemblerARM64::Backtrack() { CheckPreemption(); Pop(w10); __ Add(x10, code_pointer(), Operand(w10, UXTW)); __ Br(x10); } void RegExpMacroAssemblerARM64::Bind(Label* label) { __ Bind(label); } void RegExpMacroAssemblerARM64::CheckCharacter(uint32_t c, Label* on_equal) { CompareAndBranchOrBacktrack(current_character(), c, eq, on_equal); } void RegExpMacroAssemblerARM64::CheckCharacterGT(uc16 limit, Label* on_greater) { CompareAndBranchOrBacktrack(current_character(), limit, hi, on_greater); } void RegExpMacroAssemblerARM64::CheckAtStart(Label* on_at_start) { __ Add(w10, current_input_offset(), Operand(-char_size())); __ Cmp(w10, string_start_minus_one()); BranchOrBacktrack(eq, on_at_start); } void RegExpMacroAssemblerARM64::CheckNotAtStart(int cp_offset, Label* on_not_at_start) { __ Add(w10, current_input_offset(), Operand(-char_size() + cp_offset * char_size())); __ Cmp(w10, string_start_minus_one()); BranchOrBacktrack(ne, on_not_at_start); } void RegExpMacroAssemblerARM64::CheckCharacterLT(uc16 limit, Label* on_less) { CompareAndBranchOrBacktrack(current_character(), limit, lo, on_less); } void RegExpMacroAssemblerARM64::CheckCharacters(Vector<const uc16> str, int cp_offset, Label* on_failure, bool check_end_of_string) { // This method is only ever called from the cctests. if (check_end_of_string) { // Is last character of required match inside string. CheckPosition(cp_offset + str.length() - 1, on_failure); } Register characters_address = x11; __ Add(characters_address, input_end(), Operand(current_input_offset(), SXTW)); if (cp_offset != 0) { __ Add(characters_address, characters_address, cp_offset * char_size()); } for (int i = 0; i < str.length(); i++) { if (mode_ == LATIN1) { __ Ldrb(w10, MemOperand(characters_address, 1, PostIndex)); DCHECK_GE(String::kMaxOneByteCharCode, str[i]); } else { __ Ldrh(w10, MemOperand(characters_address, 2, PostIndex)); } CompareAndBranchOrBacktrack(w10, str[i], ne, on_failure); } } void RegExpMacroAssemblerARM64::CheckGreedyLoop(Label* on_equal) { __ Ldr(w10, MemOperand(backtrack_stackpointer())); __ Cmp(current_input_offset(), w10); __ Cset(x11, eq); __ Add(backtrack_stackpointer(), backtrack_stackpointer(), Operand(x11, LSL, kWRegSizeLog2)); BranchOrBacktrack(eq, on_equal); } void RegExpMacroAssemblerARM64::CheckNotBackReferenceIgnoreCase( int start_reg, bool read_backward, bool unicode, Label* on_no_match) { Label fallthrough; Register capture_start_offset = w10; // Save the capture length in a callee-saved register so it will // be preserved if we call a C helper. Register capture_length = w19; DCHECK(kCalleeSaved.IncludesAliasOf(capture_length)); // Find length of back-referenced capture. DCHECK_EQ(0, start_reg % 2); if (start_reg < kNumCachedRegisters) { __ Mov(capture_start_offset.X(), GetCachedRegister(start_reg)); __ Lsr(x11, GetCachedRegister(start_reg), kWRegSizeInBits); } else { __ Ldp(w11, capture_start_offset, capture_location(start_reg, x10)); } __ Sub(capture_length, w11, capture_start_offset); // Length to check. // At this point, the capture registers are either both set or both cleared. // If the capture length is zero, then the capture is either empty or cleared. // Fall through in both cases. __ CompareAndBranch(capture_length, Operand(0), eq, &fallthrough); // Check that there are enough characters left in the input. if (read_backward) { __ Add(w12, string_start_minus_one(), capture_length); __ Cmp(current_input_offset(), w12); BranchOrBacktrack(le, on_no_match); } else { __ Cmn(capture_length, current_input_offset()); BranchOrBacktrack(gt, on_no_match); } if (mode_ == LATIN1) { Label success; Label fail; Label loop_check; Register capture_start_address = x12; Register capture_end_addresss = x13; Register current_position_address = x14; __ Add(capture_start_address, input_end(), Operand(capture_start_offset, SXTW)); __ Add(capture_end_addresss, capture_start_address, Operand(capture_length, SXTW)); __ Add(current_position_address, input_end(), Operand(current_input_offset(), SXTW)); if (read_backward) { // Offset by length when matching backwards. __ Sub(current_position_address, current_position_address, Operand(capture_length, SXTW)); } Label loop; __ Bind(&loop); __ Ldrb(w10, MemOperand(capture_start_address, 1, PostIndex)); __ Ldrb(w11, MemOperand(current_position_address, 1, PostIndex)); __ Cmp(w10, w11); __ B(eq, &loop_check); // Mismatch, try case-insensitive match (converting letters to lower-case). __ Orr(w10, w10, 0x20); // Convert capture character to lower-case. __ Orr(w11, w11, 0x20); // Also convert input character. __ Cmp(w11, w10); __ B(ne, &fail); __ Sub(w10, w10, 'a'); __ Cmp(w10, 'z' - 'a'); // Is w10 a lowercase letter? __ B(ls, &loop_check); // In range 'a'-'z'. // Latin-1: Check for values in range [224,254] but not 247. __ Sub(w10, w10, 224 - 'a'); __ Cmp(w10, 254 - 224); __ Ccmp(w10, 247 - 224, ZFlag, ls); // Check for 247. __ B(eq, &fail); // Weren't Latin-1 letters. __ Bind(&loop_check); __ Cmp(capture_start_address, capture_end_addresss); __ B(lt, &loop); __ B(&success); __ Bind(&fail); BranchOrBacktrack(al, on_no_match); __ Bind(&success); // Compute new value of character position after the matched part. __ Sub(current_input_offset().X(), current_position_address, input_end()); if (read_backward) { __ Sub(current_input_offset().X(), current_input_offset().X(), Operand(capture_length, SXTW)); } if (masm_->emit_debug_code()) { __ Cmp(current_input_offset().X(), Operand(current_input_offset(), SXTW)); __ Ccmp(current_input_offset(), 0, NoFlag, eq); // The current input offset should be <= 0, and fit in a W register. __ Check(le, AbortReason::kOffsetOutOfRange); } } else { DCHECK(mode_ == UC16); int argument_count = 4; // The cached registers need to be retained. CPURegList cached_registers(CPURegister::kRegister, kXRegSizeInBits, 0, 7); DCHECK_EQ(kNumCachedRegisters, cached_registers.Count() * 2); __ PushCPURegList(cached_registers); // Put arguments into arguments registers. // Parameters are // x0: Address byte_offset1 - Address captured substring's start. // x1: Address byte_offset2 - Address of current character position. // w2: size_t byte_length - length of capture in bytes(!) // x3: Isolate* isolate or 0 if unicode flag // Address of start of capture. __ Add(x0, input_end(), Operand(capture_start_offset, SXTW)); // Length of capture. __ Mov(w2, capture_length); // Address of current input position. __ Add(x1, input_end(), Operand(current_input_offset(), SXTW)); if (read_backward) { __ Sub(x1, x1, Operand(capture_length, SXTW)); } // Isolate. #ifdef V8_INTL_SUPPORT if (unicode) { __ Mov(x3, Operand(0)); } else // NOLINT #endif // V8_INTL_SUPPORT { __ Mov(x3, ExternalReference::isolate_address(isolate())); } { AllowExternalCallThatCantCauseGC scope(masm_); ExternalReference function = ExternalReference::re_case_insensitive_compare_uc16(isolate()); __ CallCFunction(function, argument_count); } // Check if function returned non-zero for success or zero for failure. // x0 is one of the registers used as a cache so it must be tested before // the cache is restored. __ Cmp(x0, 0); __ PopCPURegList(cached_registers); BranchOrBacktrack(eq, on_no_match); // On success, advance position by length of capture. if (read_backward) { __ Sub(current_input_offset(), current_input_offset(), capture_length); } else { __ Add(current_input_offset(), current_input_offset(), capture_length); } } __ Bind(&fallthrough); } void RegExpMacroAssemblerARM64::CheckNotBackReference(int start_reg, bool read_backward, Label* on_no_match) { Label fallthrough; Register capture_start_address = x12; Register capture_end_address = x13; Register current_position_address = x14; Register capture_length = w15; // Find length of back-referenced capture. DCHECK_EQ(0, start_reg % 2); if (start_reg < kNumCachedRegisters) { __ Mov(x10, GetCachedRegister(start_reg)); __ Lsr(x11, GetCachedRegister(start_reg), kWRegSizeInBits); } else { __ Ldp(w11, w10, capture_location(start_reg, x10)); } __ Sub(capture_length, w11, w10); // Length to check. // At this point, the capture registers are either both set or both cleared. // If the capture length is zero, then the capture is either empty or cleared. // Fall through in both cases. __ CompareAndBranch(capture_length, Operand(0), eq, &fallthrough); // Check that there are enough characters left in the input. if (read_backward) { __ Add(w12, string_start_minus_one(), capture_length); __ Cmp(current_input_offset(), w12); BranchOrBacktrack(le, on_no_match); } else { __ Cmn(capture_length, current_input_offset()); BranchOrBacktrack(gt, on_no_match); } // Compute pointers to match string and capture string __ Add(capture_start_address, input_end(), Operand(w10, SXTW)); __ Add(capture_end_address, capture_start_address, Operand(capture_length, SXTW)); __ Add(current_position_address, input_end(), Operand(current_input_offset(), SXTW)); if (read_backward) { // Offset by length when matching backwards. __ Sub(current_position_address, current_position_address, Operand(capture_length, SXTW)); } Label loop; __ Bind(&loop); if (mode_ == LATIN1) { __ Ldrb(w10, MemOperand(capture_start_address, 1, PostIndex)); __ Ldrb(w11, MemOperand(current_position_address, 1, PostIndex)); } else { DCHECK(mode_ == UC16); __ Ldrh(w10, MemOperand(capture_start_address, 2, PostIndex)); __ Ldrh(w11, MemOperand(current_position_address, 2, PostIndex)); } __ Cmp(w10, w11); BranchOrBacktrack(ne, on_no_match); __ Cmp(capture_start_address, capture_end_address); __ B(lt, &loop); // Move current character position to position after match. __ Sub(current_input_offset().X(), current_position_address, input_end()); if (read_backward) { __ Sub(current_input_offset().X(), current_input_offset().X(), Operand(capture_length, SXTW)); } if (masm_->emit_debug_code()) { __ Cmp(current_input_offset().X(), Operand(current_input_offset(), SXTW)); __ Ccmp(current_input_offset(), 0, NoFlag, eq); // The current input offset should be <= 0, and fit in a W register. __ Check(le, AbortReason::kOffsetOutOfRange); } __ Bind(&fallthrough); } void RegExpMacroAssemblerARM64::CheckNotCharacter(unsigned c, Label* on_not_equal) { CompareAndBranchOrBacktrack(current_character(), c, ne, on_not_equal); } void RegExpMacroAssemblerARM64::CheckCharacterAfterAnd(uint32_t c, uint32_t mask, Label* on_equal) { __ And(w10, current_character(), mask); CompareAndBranchOrBacktrack(w10, c, eq, on_equal); } void RegExpMacroAssemblerARM64::CheckNotCharacterAfterAnd(unsigned c, unsigned mask, Label* on_not_equal) { __ And(w10, current_character(), mask); CompareAndBranchOrBacktrack(w10, c, ne, on_not_equal); } void RegExpMacroAssemblerARM64::CheckNotCharacterAfterMinusAnd( uc16 c, uc16 minus, uc16 mask, Label* on_not_equal) { DCHECK_GT(String::kMaxUtf16CodeUnit, minus); __ Sub(w10, current_character(), minus); __ And(w10, w10, mask); CompareAndBranchOrBacktrack(w10, c, ne, on_not_equal); } void RegExpMacroAssemblerARM64::CheckCharacterInRange( uc16 from, uc16 to, Label* on_in_range) { __ Sub(w10, current_character(), from); // Unsigned lower-or-same condition. CompareAndBranchOrBacktrack(w10, to - from, ls, on_in_range); } void RegExpMacroAssemblerARM64::CheckCharacterNotInRange( uc16 from, uc16 to, Label* on_not_in_range) { __ Sub(w10, current_character(), from); // Unsigned higher condition. CompareAndBranchOrBacktrack(w10, to - from, hi, on_not_in_range); } void RegExpMacroAssemblerARM64::CheckBitInTable( Handle<ByteArray> table, Label* on_bit_set) { __ Mov(x11, Operand(table)); if ((mode_ != LATIN1) || (kTableMask != String::kMaxOneByteCharCode)) { __ And(w10, current_character(), kTableMask); __ Add(w10, w10, ByteArray::kHeaderSize - kHeapObjectTag); } else { __ Add(w10, current_character(), ByteArray::kHeaderSize - kHeapObjectTag); } __ Ldrb(w11, MemOperand(x11, w10, UXTW)); CompareAndBranchOrBacktrack(w11, 0, ne, on_bit_set); } bool RegExpMacroAssemblerARM64::CheckSpecialCharacterClass(uc16 type, Label* on_no_match) { // Range checks (c in min..max) are generally implemented by an unsigned // (c - min) <= (max - min) check switch (type) { case 's': // Match space-characters if (mode_ == LATIN1) { // One byte space characters are '\t'..'\r', ' ' and \u00a0. Label success; // Check for ' ' or 0x00A0. __ Cmp(current_character(), ' '); __ Ccmp(current_character(), 0x00A0, ZFlag, ne); __ B(eq, &success); // Check range 0x09..0x0D. __ Sub(w10, current_character(), '\t'); CompareAndBranchOrBacktrack(w10, '\r' - '\t', hi, on_no_match); __ Bind(&success); return true; } return false; case 'S': // The emitted code for generic character classes is good enough. return false; case 'd': // Match ASCII digits ('0'..'9'). __ Sub(w10, current_character(), '0'); CompareAndBranchOrBacktrack(w10, '9' - '0', hi, on_no_match); return true; case 'D': // Match ASCII non-digits. __ Sub(w10, current_character(), '0'); CompareAndBranchOrBacktrack(w10, '9' - '0', ls, on_no_match); return true; case '.': { // Match non-newlines (not 0x0A('\n'), 0x0D('\r'), 0x2028 and 0x2029) // Here we emit the conditional branch only once at the end to make branch // prediction more efficient, even though we could branch out of here // as soon as a character matches. __ Cmp(current_character(), 0x0A); __ Ccmp(current_character(), 0x0D, ZFlag, ne); if (mode_ == UC16) { __ Sub(w10, current_character(), 0x2028); // If the Z flag was set we clear the flags to force a branch. __ Ccmp(w10, 0x2029 - 0x2028, NoFlag, ne); // ls -> !((C==1) && (Z==0)) BranchOrBacktrack(ls, on_no_match); } else { BranchOrBacktrack(eq, on_no_match); } return true; } case 'n': { // Match newlines (0x0A('\n'), 0x0D('\r'), 0x2028 and 0x2029) // We have to check all 4 newline characters before emitting // the conditional branch. __ Cmp(current_character(), 0x0A); __ Ccmp(current_character(), 0x0D, ZFlag, ne); if (mode_ == UC16) { __ Sub(w10, current_character(), 0x2028); // If the Z flag was set we clear the flags to force a fall-through. __ Ccmp(w10, 0x2029 - 0x2028, NoFlag, ne); // hi -> (C==1) && (Z==0) BranchOrBacktrack(hi, on_no_match); } else { BranchOrBacktrack(ne, on_no_match); } return true; } case 'w': { if (mode_ != LATIN1) { // Table is 256 entries, so all Latin1 characters can be tested. CompareAndBranchOrBacktrack(current_character(), 'z', hi, on_no_match); } ExternalReference map = ExternalReference::re_word_character_map(isolate()); __ Mov(x10, map); __ Ldrb(w10, MemOperand(x10, current_character(), UXTW)); CompareAndBranchOrBacktrack(w10, 0, eq, on_no_match); return true; } case 'W': { Label done; if (mode_ != LATIN1) { // Table is 256 entries, so all Latin1 characters can be tested. __ Cmp(current_character(), 'z'); __ B(hi, &done); } ExternalReference map = ExternalReference::re_word_character_map(isolate()); __ Mov(x10, map); __ Ldrb(w10, MemOperand(x10, current_character(), UXTW)); CompareAndBranchOrBacktrack(w10, 0, ne, on_no_match); __ Bind(&done); return true; } case '*': // Match any character. return true; // No custom implementation (yet): s(UC16), S(UC16). default: return false; } } void RegExpMacroAssemblerARM64::Fail() { __ Mov(w0, FAILURE); __ B(&exit_label_); } Handle<HeapObject> RegExpMacroAssemblerARM64::GetCode(Handle<String> source) { Label return_w0; // Finalize code - write the entry point code now we know how many // registers we need. // Entry code: __ Bind(&entry_label_); // Arguments on entry: // x0: String input // x1: int start_offset // x2: byte* input_start // x3: byte* input_end // x4: int* output array // x5: int output array size // x6: Address stack_base // x7: int direct_call // sp[8]: address of the current isolate // sp[0]: secondary link/return address used by native call // Tell the system that we have a stack frame. Because the type is MANUAL, no // code is generated. FrameScope scope(masm_, StackFrame::MANUAL); // Push registers on the stack, only push the argument registers that we need. CPURegList argument_registers(x0, x5, x6, x7); CPURegList registers_to_retain = kCalleeSaved; DCHECK_EQ(11, kCalleeSaved.Count()); registers_to_retain.Combine(lr); __ PushCPURegList(registers_to_retain); __ PushCPURegList(argument_registers); // Set frame pointer in place. __ Add(frame_pointer(), sp, argument_registers.Count() * kPointerSize); // Initialize callee-saved registers. __ Mov(start_offset(), w1); __ Mov(input_start(), x2); __ Mov(input_end(), x3); __ Mov(output_array(), x4); // Set the number of registers we will need to allocate, that is: // - success_counter (X register) // - (num_registers_ - kNumCachedRegisters) (W registers) int num_wreg_to_allocate = num_registers_ - kNumCachedRegisters; // Do not allocate registers on the stack if they can all be cached. if (num_wreg_to_allocate < 0) { num_wreg_to_allocate = 0; } // Make room for the success_counter. num_wreg_to_allocate += 2; // Make sure the stack alignment will be respected. int alignment = masm_->ActivationFrameAlignment(); DCHECK_EQ(alignment % 16, 0); int align_mask = (alignment / kWRegSize) - 1; num_wreg_to_allocate = (num_wreg_to_allocate + align_mask) & ~align_mask; // Check if we have space on the stack. Label stack_limit_hit; Label stack_ok; ExternalReference stack_limit = ExternalReference::address_of_stack_limit(isolate()); __ Mov(x10, stack_limit); __ Ldr(x10, MemOperand(x10)); __ Subs(x10, sp, x10); // Handle it if the stack pointer is already below the stack limit. __ B(ls, &stack_limit_hit); // Check if there is room for the variable number of registers above // the stack limit. __ Cmp(x10, num_wreg_to_allocate * kWRegSize); __ B(hs, &stack_ok); // Exit with OutOfMemory exception. There is not enough space on the stack // for our working registers. __ Mov(w0, EXCEPTION); __ B(&return_w0); __ Bind(&stack_limit_hit); CallCheckStackGuardState(x10); // If returned value is non-zero, we exit with the returned value as result. __ Cbnz(w0, &return_w0); __ Bind(&stack_ok); // Allocate space on stack. __ Claim(num_wreg_to_allocate, kWRegSize); // Initialize success_counter with 0. __ Str(wzr, MemOperand(frame_pointer(), kSuccessCounter)); // Find negative length (offset of start relative to end). __ Sub(x10, input_start(), input_end()); if (masm_->emit_debug_code()) { // Check that the size of the input string chars is in range. __ Neg(x11, x10); __ Cmp(x11, SeqTwoByteString::kMaxCharsSize); __ Check(ls, AbortReason::kInputStringTooLong); } __ Mov(current_input_offset(), w10); // The non-position value is used as a clearing value for the // capture registers, it corresponds to the position of the first character // minus one. __ Sub(string_start_minus_one(), current_input_offset(), char_size()); __ Sub(string_start_minus_one(), string_start_minus_one(), Operand(start_offset(), LSL, (mode_ == UC16) ? 1 : 0)); // We can store this value twice in an X register for initializing // on-stack registers later. __ Orr(twice_non_position_value(), string_start_minus_one().X(), Operand(string_start_minus_one().X(), LSL, kWRegSizeInBits)); // Initialize code pointer register. __ Mov(code_pointer(), Operand(masm_->CodeObject())); Label load_char_start_regexp, start_regexp; // Load newline if index is at start, previous character otherwise. __ Cbnz(start_offset(), &load_char_start_regexp); __ Mov(current_character(), '\n'); __ B(&start_regexp); // Global regexp restarts matching here. __ Bind(&load_char_start_regexp); // Load previous char as initial value of current character register. LoadCurrentCharacterUnchecked(-1, 1); __ Bind(&start_regexp); // Initialize on-stack registers. if (num_saved_registers_ > 0) { ClearRegisters(0, num_saved_registers_ - 1); } // Initialize backtrack stack pointer. __ Ldr(backtrack_stackpointer(), MemOperand(frame_pointer(), kStackBase)); // Execute __ B(&start_label_); if (backtrack_label_.is_linked()) { __ Bind(&backtrack_label_); Backtrack(); } if (success_label_.is_linked()) { Register first_capture_start = w15; // Save captures when successful. __ Bind(&success_label_); if (num_saved_registers_ > 0) { // V8 expects the output to be an int32_t array. Register capture_start = w12; Register capture_end = w13; Register input_length = w14; // Copy captures to output. // Get string length. __ Sub(x10, input_end(), input_start()); if (masm_->emit_debug_code()) { // Check that the size of the input string chars is in range. __ Cmp(x10, SeqTwoByteString::kMaxCharsSize); __ Check(ls, AbortReason::kInputStringTooLong); } // input_start has a start_offset offset on entry. We need to include // it when computing the length of the whole string. if (mode_ == UC16) { __ Add(input_length, start_offset(), Operand(w10, LSR, 1)); } else { __ Add(input_length, start_offset(), w10); } // Copy the results to the output array from the cached registers first. for (int i = 0; (i < num_saved_registers_) && (i < kNumCachedRegisters); i += 2) { __ Mov(capture_start.X(), GetCachedRegister(i)); __ Lsr(capture_end.X(), capture_start.X(), kWRegSizeInBits); if ((i == 0) && global_with_zero_length_check()) { // Keep capture start for the zero-length check later. __ Mov(first_capture_start, capture_start); } // Offsets need to be relative to the start of the string. if (mode_ == UC16) { __ Add(capture_start, input_length, Operand(capture_start, ASR, 1)); __ Add(capture_end, input_length, Operand(capture_end, ASR, 1)); } else { __ Add(capture_start, input_length, capture_start); __ Add(capture_end, input_length, capture_end); } // The output pointer advances for a possible global match. __ Stp(capture_start, capture_end, MemOperand(output_array(), kPointerSize, PostIndex)); } // Only carry on if there are more than kNumCachedRegisters capture // registers. int num_registers_left_on_stack = num_saved_registers_ - kNumCachedRegisters; if (num_registers_left_on_stack > 0) { Register base = x10; // There are always an even number of capture registers. A couple of // registers determine one match with two offsets. DCHECK_EQ(0, num_registers_left_on_stack % 2); __ Add(base, frame_pointer(), kFirstCaptureOnStack); // We can unroll the loop here, we should not unroll for less than 2 // registers. STATIC_ASSERT(kNumRegistersToUnroll > 2); if (num_registers_left_on_stack <= kNumRegistersToUnroll) { for (int i = 0; i < num_registers_left_on_stack / 2; i++) { __ Ldp(capture_end, capture_start, MemOperand(base, -kPointerSize, PostIndex)); if ((i == 0) && global_with_zero_length_check()) { // Keep capture start for the zero-length check later. __ Mov(first_capture_start, capture_start); } // Offsets need to be relative to the start of the string. if (mode_ == UC16) { __ Add(capture_start, input_length, Operand(capture_start, ASR, 1)); __ Add(capture_end, input_length, Operand(capture_end, ASR, 1)); } else { __ Add(capture_start, input_length, capture_start); __ Add(capture_end, input_length, capture_end); } // The output pointer advances for a possible global match. __ Stp(capture_start, capture_end, MemOperand(output_array(), kPointerSize, PostIndex)); } } else { Label loop, start; __ Mov(x11, num_registers_left_on_stack); __ Ldp(capture_end, capture_start, MemOperand(base, -kPointerSize, PostIndex)); if (global_with_zero_length_check()) { __ Mov(first_capture_start, capture_start); } __ B(&start); __ Bind(&loop); __ Ldp(capture_end, capture_start, MemOperand(base, -kPointerSize, PostIndex)); __ Bind(&start); if (mode_ == UC16) { __ Add(capture_start, input_length, Operand(capture_start, ASR, 1)); __ Add(capture_end, input_length, Operand(capture_end, ASR, 1)); } else { __ Add(capture_start, input_length, capture_start); __ Add(capture_end, input_length, capture_end); } // The output pointer advances for a possible global match. __ Stp(capture_start, capture_end, MemOperand(output_array(), kPointerSize, PostIndex)); __ Sub(x11, x11, 2); __ Cbnz(x11, &loop); } } } if (global()) { Register success_counter = w0; Register output_size = x10; // Restart matching if the regular expression is flagged as global. // Increment success counter. __ Ldr(success_counter, MemOperand(frame_pointer(), kSuccessCounter)); __ Add(success_counter, success_counter, 1); __ Str(success_counter, MemOperand(frame_pointer(), kSuccessCounter)); // Capture results have been stored, so the number of remaining global // output registers is reduced by the number of stored captures. __ Ldr(output_size, MemOperand(frame_pointer(), kOutputSize)); __ Sub(output_size, output_size, num_saved_registers_); // Check whether we have enough room for another set of capture results. __ Cmp(output_size, num_saved_registers_); __ B(lt, &return_w0); // The output pointer is already set to the next field in the output // array. // Update output size on the frame before we restart matching. __ Str(output_size, MemOperand(frame_pointer(), kOutputSize)); if (global_with_zero_length_check()) { // Special case for zero-length matches. __ Cmp(current_input_offset(), first_capture_start); // Not a zero-length match, restart. __ B(ne, &load_char_start_regexp); // Offset from the end is zero if we already reached the end. __ Cbz(current_input_offset(), &return_w0); // Advance current position after a zero-length match. Label advance; __ bind(&advance); __ Add(current_input_offset(), current_input_offset(), Operand((mode_ == UC16) ? 2 : 1)); if (global_unicode()) CheckNotInSurrogatePair(0, &advance); } __ B(&load_char_start_regexp); } else { __ Mov(w0, SUCCESS); } } if (exit_label_.is_linked()) { // Exit and return w0 __ Bind(&exit_label_); if (global()) { __ Ldr(w0, MemOperand(frame_pointer(), kSuccessCounter)); } } __ Bind(&return_w0); // Set stack pointer back to first register to retain __ Mov(sp, fp); // Restore registers. __ PopCPURegList(registers_to_retain); __ Ret(); Label exit_with_exception; // Registers x0 to x7 are used to store the first captures, they need to be // retained over calls to C++ code. CPURegList cached_registers(CPURegister::kRegister, kXRegSizeInBits, 0, 7); DCHECK_EQ(kNumCachedRegisters, cached_registers.Count() * 2); if (check_preempt_label_.is_linked()) { __ Bind(&check_preempt_label_); SaveLinkRegister(); // The cached registers need to be retained. __ PushCPURegList(cached_registers); CallCheckStackGuardState(x10); // Returning from the regexp code restores the stack (sp <- fp) // so we don't need to drop the link register from it before exiting. __ Cbnz(w0, &return_w0); // Reset the cached registers. __ PopCPURegList(cached_registers); RestoreLinkRegister(); __ Ret(); } if (stack_overflow_label_.is_linked()) { __ Bind(&stack_overflow_label_); SaveLinkRegister(); // The cached registers need to be retained. __ PushCPURegList(cached_registers); // Call GrowStack(backtrack_stackpointer(), &stack_base) __ Mov(x2, ExternalReference::isolate_address(isolate())); __ Add(x1, frame_pointer(), kStackBase); __ Mov(x0, backtrack_stackpointer()); ExternalReference grow_stack = ExternalReference::re_grow_stack(isolate()); __ CallCFunction(grow_stack, 3); // If return nullptr, we have failed to grow the stack, and // must exit with a stack-overflow exception. // Returning from the regexp code restores the stack (sp <- fp) // so we don't need to drop the link register from it before exiting. __ Cbz(w0, &exit_with_exception); // Otherwise use return value as new stack pointer. __ Mov(backtrack_stackpointer(), x0); // Reset the cached registers. __ PopCPURegList(cached_registers); RestoreLinkRegister(); __ Ret(); } if (exit_with_exception.is_linked()) { __ Bind(&exit_with_exception); __ Mov(w0, EXCEPTION); __ B(&return_w0); } CodeDesc code_desc; masm_->GetCode(isolate(), &code_desc); Handle<Code> code = isolate()->factory()->NewCode(code_desc, Code::REGEXP, masm_->CodeObject()); PROFILE(masm_->isolate(), RegExpCodeCreateEvent(AbstractCode::cast(*code), *source)); return Handle<HeapObject>::cast(code); } void RegExpMacroAssemblerARM64::GoTo(Label* to) { BranchOrBacktrack(al, to); } void RegExpMacroAssemblerARM64::IfRegisterGE(int reg, int comparand, Label* if_ge) { Register to_compare = GetRegister(reg, w10); CompareAndBranchOrBacktrack(to_compare, comparand, ge, if_ge); } void RegExpMacroAssemblerARM64::IfRegisterLT(int reg, int comparand, Label* if_lt) { Register to_compare = GetRegister(reg, w10); CompareAndBranchOrBacktrack(to_compare, comparand, lt, if_lt); } void RegExpMacroAssemblerARM64::IfRegisterEqPos(int reg, Label* if_eq) { Register to_compare = GetRegister(reg, w10); __ Cmp(to_compare, current_input_offset()); BranchOrBacktrack(eq, if_eq); } RegExpMacroAssembler::IrregexpImplementation RegExpMacroAssemblerARM64::Implementation() { return kARM64Implementation; } void RegExpMacroAssemblerARM64::LoadCurrentCharacter(int cp_offset, Label* on_end_of_input, bool check_bounds, int characters) { // TODO(pielan): Make sure long strings are caught before this, and not // just asserted in debug mode. // Be sane! (And ensure that an int32_t can be used to index the string) DCHECK(cp_offset < (1<<30)); if (check_bounds) { if (cp_offset >= 0) { CheckPosition(cp_offset + characters - 1, on_end_of_input); } else { CheckPosition(cp_offset, on_end_of_input); } } LoadCurrentCharacterUnchecked(cp_offset, characters); } void RegExpMacroAssemblerARM64::PopCurrentPosition() { Pop(current_input_offset()); } void RegExpMacroAssemblerARM64::PopRegister(int register_index) { Pop(w10); StoreRegister(register_index, w10); } void RegExpMacroAssemblerARM64::PushBacktrack(Label* label) { if (label->is_bound()) { int target = label->pos(); __ Mov(w10, target + Code::kHeaderSize - kHeapObjectTag); } else { __ Adr(x10, label, MacroAssembler::kAdrFar); __ Sub(x10, x10, code_pointer()); if (masm_->emit_debug_code()) { __ Cmp(x10, kWRegMask); // The code offset has to fit in a W register. __ Check(ls, AbortReason::kOffsetOutOfRange); } } Push(w10); CheckStackLimit(); } void RegExpMacroAssemblerARM64::PushCurrentPosition() { Push(current_input_offset()); } void RegExpMacroAssemblerARM64::PushRegister(int register_index, StackCheckFlag check_stack_limit) { Register to_push = GetRegister(register_index, w10); Push(to_push); if (check_stack_limit) CheckStackLimit(); } void RegExpMacroAssemblerARM64::ReadCurrentPositionFromRegister(int reg) { RegisterState register_state = GetRegisterState(reg); switch (register_state) { case STACKED: __ Ldr(current_input_offset(), register_location(reg)); break; case CACHED_LSW: __ Mov(current_input_offset(), GetCachedRegister(reg).W()); break; case CACHED_MSW: __ Lsr(current_input_offset().X(), GetCachedRegister(reg), kWRegSizeInBits); break; default: UNREACHABLE(); break; } } void RegExpMacroAssemblerARM64::ReadStackPointerFromRegister(int reg) { Register read_from = GetRegister(reg, w10); __ Ldr(x11, MemOperand(frame_pointer(), kStackBase)); __ Add(backtrack_stackpointer(), x11, Operand(read_from, SXTW)); } void RegExpMacroAssemblerARM64::SetCurrentPositionFromEnd(int by) { Label after_position; __ Cmp(current_input_offset(), -by * char_size()); __ B(ge, &after_position); __ Mov(current_input_offset(), -by * char_size()); // On RegExp code entry (where this operation is used), the character before // the current position is expected to be already loaded. // We have advanced the position, so it's safe to read backwards. LoadCurrentCharacterUnchecked(-1, 1); __ Bind(&after_position); } void RegExpMacroAssemblerARM64::SetRegister(int register_index, int to) { DCHECK(register_index >= num_saved_registers_); // Reserved for positions! Register set_to = wzr; if (to != 0) { set_to = w10; __ Mov(set_to, to); } StoreRegister(register_index, set_to); } bool RegExpMacroAssemblerARM64::Succeed() { __ B(&success_label_); return global(); } void RegExpMacroAssemblerARM64::WriteCurrentPositionToRegister(int reg, int cp_offset) { Register position = current_input_offset(); if (cp_offset != 0) { position = w10; __ Add(position, current_input_offset(), cp_offset * char_size()); } StoreRegister(reg, position); } void RegExpMacroAssemblerARM64::ClearRegisters(int reg_from, int reg_to) { DCHECK(reg_from <= reg_to); int num_registers = reg_to - reg_from + 1; // If the first capture register is cached in a hardware register but not // aligned on a 64-bit one, we need to clear the first one specifically. if ((reg_from < kNumCachedRegisters) && ((reg_from % 2) != 0)) { StoreRegister(reg_from, string_start_minus_one()); num_registers--; reg_from++; } // Clear cached registers in pairs as far as possible. while ((num_registers >= 2) && (reg_from < kNumCachedRegisters)) { DCHECK(GetRegisterState(reg_from) == CACHED_LSW); __ Mov(GetCachedRegister(reg_from), twice_non_position_value()); reg_from += 2; num_registers -= 2; } if ((num_registers % 2) == 1) { StoreRegister(reg_from, string_start_minus_one()); num_registers--; reg_from++; } if (num_registers > 0) { // If there are some remaining registers, they are stored on the stack. DCHECK_LE(kNumCachedRegisters, reg_from); // Move down the indexes of the registers on stack to get the correct offset // in memory. reg_from -= kNumCachedRegisters; reg_to -= kNumCachedRegisters; // We should not unroll the loop for less than 2 registers. STATIC_ASSERT(kNumRegistersToUnroll > 2); // We position the base pointer to (reg_from + 1). int base_offset = kFirstRegisterOnStack - kWRegSize - (kWRegSize * reg_from); if (num_registers > kNumRegistersToUnroll) { Register base = x10; __ Add(base, frame_pointer(), base_offset); Label loop; __ Mov(x11, num_registers); __ Bind(&loop); __ Str(twice_non_position_value(), MemOperand(base, -kPointerSize, PostIndex)); __ Sub(x11, x11, 2); __ Cbnz(x11, &loop); } else { for (int i = reg_from; i <= reg_to; i += 2) { __ Str(twice_non_position_value(), MemOperand(frame_pointer(), base_offset)); base_offset -= kWRegSize * 2; } } } } void RegExpMacroAssemblerARM64::WriteStackPointerToRegister(int reg) { __ Ldr(x10, MemOperand(frame_pointer(), kStackBase)); __ Sub(x10, backtrack_stackpointer(), x10); if (masm_->emit_debug_code()) { __ Cmp(x10, Operand(w10, SXTW)); // The stack offset needs to fit in a W register. __ Check(eq, AbortReason::kOffsetOutOfRange); } StoreRegister(reg, w10); } // Helper function for reading a value out of a stack frame. template <typename T> static T& frame_entry(Address re_frame, int frame_offset) { return *reinterpret_cast<T*>(re_frame + frame_offset); } template <typename T> static T* frame_entry_address(Address re_frame, int frame_offset) { return reinterpret_cast<T*>(re_frame + frame_offset); } int RegExpMacroAssemblerARM64::CheckStackGuardState( Address* return_address, Address raw_code, Address re_frame, int start_index, const byte** input_start, const byte** input_end) { Code re_code = Code::cast(ObjectPtr(raw_code)); return NativeRegExpMacroAssembler::CheckStackGuardState( frame_entry<Isolate*>(re_frame, kIsolate), start_index, frame_entry<int>(re_frame, kDirectCall) == 1, return_address, re_code, frame_entry_address<Address>(re_frame, kInput), input_start, input_end); } void RegExpMacroAssemblerARM64::CheckPosition(int cp_offset, Label* on_outside_input) { if (cp_offset >= 0) { CompareAndBranchOrBacktrack(current_input_offset(), -cp_offset * char_size(), ge, on_outside_input); } else { __ Add(w12, current_input_offset(), Operand(cp_offset * char_size())); __ Cmp(w12, string_start_minus_one()); BranchOrBacktrack(le, on_outside_input); } } // Private methods: void RegExpMacroAssemblerARM64::CallCheckStackGuardState(Register scratch) { DCHECK(!isolate()->ShouldLoadConstantsFromRootList()); DCHECK(!masm_->options().isolate_independent_code); // Allocate space on the stack to store the return address. The // CheckStackGuardState C++ function will override it if the code // moved. Allocate extra space for 2 arguments passed by pointers. // AAPCS64 requires the stack to be 16 byte aligned. int alignment = masm_->ActivationFrameAlignment(); DCHECK_EQ(alignment % 16, 0); int align_mask = (alignment / kXRegSize) - 1; int xreg_to_claim = (3 + align_mask) & ~align_mask; __ Claim(xreg_to_claim); // CheckStackGuardState needs the end and start addresses of the input string. __ Poke(input_end(), 2 * kPointerSize); __ Add(x5, sp, 2 * kPointerSize); __ Poke(input_start(), kPointerSize); __ Add(x4, sp, kPointerSize); __ Mov(w3, start_offset()); // RegExp code frame pointer. __ Mov(x2, frame_pointer()); // Code of self. __ Mov(x1, Operand(masm_->CodeObject())); // We need to pass a pointer to the return address as first argument. // DirectCEntry will place the return address on the stack before calling so // the stack pointer will point to it. __ Mov(x0, sp); DCHECK_EQ(scratch, x10); ExternalReference check_stack_guard_state = ExternalReference::re_check_stack_guard_state(isolate()); __ Mov(scratch, check_stack_guard_state); if (FLAG_embedded_builtins) { UseScratchRegisterScope temps(masm_); Register scratch = temps.AcquireX(); EmbeddedData d = EmbeddedData::FromBlob(); CHECK(Builtins::IsIsolateIndependent(Builtins::kDirectCEntry)); Address entry = d.InstructionStartOfBuiltin(Builtins::kDirectCEntry); __ Ldr(scratch, Operand(entry, RelocInfo::OFF_HEAP_TARGET)); __ Call(scratch); } else { // TODO(v8:8519): Remove this once embedded builtins are on unconditionally. Handle<Code> code = BUILTIN_CODE(isolate(), DirectCEntry); __ Call(code, RelocInfo::CODE_TARGET); } // The input string may have been moved in memory, we need to reload it. __ Peek(input_start(), kPointerSize); __ Peek(input_end(), 2 * kPointerSize); __ Drop(xreg_to_claim); // Reload the Code pointer. __ Mov(code_pointer(), Operand(masm_->CodeObject())); } void RegExpMacroAssemblerARM64::BranchOrBacktrack(Condition condition, Label* to) { if (condition == al) { // Unconditional. if (to == nullptr) { Backtrack(); return; } __ B(to); return; } if (to == nullptr) { to = &backtrack_label_; } __ B(condition, to); } void RegExpMacroAssemblerARM64::CompareAndBranchOrBacktrack(Register reg, int immediate, Condition condition, Label* to) { if ((immediate == 0) && ((condition == eq) || (condition == ne))) { if (to == nullptr) { to = &backtrack_label_; } if (condition == eq) { __ Cbz(reg, to); } else { __ Cbnz(reg, to); } } else { __ Cmp(reg, immediate); BranchOrBacktrack(condition, to); } } void RegExpMacroAssemblerARM64::CheckPreemption() { // Check for preemption. ExternalReference stack_limit = ExternalReference::address_of_stack_limit(isolate()); __ Mov(x10, stack_limit); __ Ldr(x10, MemOperand(x10)); __ Cmp(sp, x10); CallIf(&check_preempt_label_, ls); } void RegExpMacroAssemblerARM64::CheckStackLimit() { ExternalReference stack_limit = ExternalReference::address_of_regexp_stack_limit(isolate()); __ Mov(x10, stack_limit); __ Ldr(x10, MemOperand(x10)); __ Cmp(backtrack_stackpointer(), x10); CallIf(&stack_overflow_label_, ls); } void RegExpMacroAssemblerARM64::Push(Register source) { DCHECK(source.Is32Bits()); DCHECK(!source.is(backtrack_stackpointer())); __ Str(source, MemOperand(backtrack_stackpointer(), -static_cast<int>(kWRegSize), PreIndex)); } void RegExpMacroAssemblerARM64::Pop(Register target) { DCHECK(target.Is32Bits()); DCHECK(!target.is(backtrack_stackpointer())); __ Ldr(target, MemOperand(backtrack_stackpointer(), kWRegSize, PostIndex)); } Register RegExpMacroAssemblerARM64::GetCachedRegister(int register_index) { DCHECK_GT(kNumCachedRegisters, register_index); return Register::Create(register_index / 2, kXRegSizeInBits); } Register RegExpMacroAssemblerARM64::GetRegister(int register_index, Register maybe_result) { DCHECK(maybe_result.Is32Bits()); DCHECK_LE(0, register_index); if (num_registers_ <= register_index) { num_registers_ = register_index + 1; } Register result = NoReg; RegisterState register_state = GetRegisterState(register_index); switch (register_state) { case STACKED: __ Ldr(maybe_result, register_location(register_index)); result = maybe_result; break; case CACHED_LSW: result = GetCachedRegister(register_index).W(); break; case CACHED_MSW: __ Lsr(maybe_result.X(), GetCachedRegister(register_index), kWRegSizeInBits); result = maybe_result; break; default: UNREACHABLE(); break; } DCHECK(result.Is32Bits()); return result; } void RegExpMacroAssemblerARM64::StoreRegister(int register_index, Register source) { DCHECK(source.Is32Bits()); DCHECK_LE(0, register_index); if (num_registers_ <= register_index) { num_registers_ = register_index + 1; } RegisterState register_state = GetRegisterState(register_index); switch (register_state) { case STACKED: __ Str(source, register_location(register_index)); break; case CACHED_LSW: { Register cached_register = GetCachedRegister(register_index); if (!source.Is(cached_register.W())) { __ Bfi(cached_register, source.X(), 0, kWRegSizeInBits); } break; } case CACHED_MSW: { Register cached_register = GetCachedRegister(register_index); __ Bfi(cached_register, source.X(), kWRegSizeInBits, kWRegSizeInBits); break; } default: UNREACHABLE(); break; } } void RegExpMacroAssemblerARM64::CallIf(Label* to, Condition condition) { Label skip_call; if (condition != al) __ B(&skip_call, NegateCondition(condition)); __ Bl(to); __ Bind(&skip_call); } void RegExpMacroAssemblerARM64::RestoreLinkRegister() { __ Pop(lr, xzr); __ Add(lr, lr, Operand(masm_->CodeObject())); } void RegExpMacroAssemblerARM64::SaveLinkRegister() { __ Sub(lr, lr, Operand(masm_->CodeObject())); __ Push(xzr, lr); } MemOperand RegExpMacroAssemblerARM64::register_location(int register_index) { DCHECK(register_index < (1<<30)); DCHECK_LE(kNumCachedRegisters, register_index); if (num_registers_ <= register_index) { num_registers_ = register_index + 1; } register_index -= kNumCachedRegisters; int offset = kFirstRegisterOnStack - register_index * kWRegSize; return MemOperand(frame_pointer(), offset); } MemOperand RegExpMacroAssemblerARM64::capture_location(int register_index, Register scratch) { DCHECK(register_index < (1<<30)); DCHECK(register_index < num_saved_registers_); DCHECK_LE(kNumCachedRegisters, register_index); DCHECK_EQ(register_index % 2, 0); register_index -= kNumCachedRegisters; int offset = kFirstCaptureOnStack - register_index * kWRegSize; // capture_location is used with Stp instructions to load/store 2 registers. // The immediate field in the encoding is limited to 7 bits (signed). if (is_int7(offset)) { return MemOperand(frame_pointer(), offset); } else { __ Add(scratch, frame_pointer(), offset); return MemOperand(scratch); } } void RegExpMacroAssemblerARM64::LoadCurrentCharacterUnchecked(int cp_offset, int characters) { Register offset = current_input_offset(); // The ldr, str, ldrh, strh instructions can do unaligned accesses, if the CPU // and the operating system running on the target allow it. // If unaligned load/stores are not supported then this function must only // be used to load a single character at a time. // ARMv8 supports unaligned accesses but V8 or the kernel can decide to // disable it. // TODO(pielan): See whether or not we should disable unaligned accesses. if (!CanReadUnaligned()) { DCHECK_EQ(1, characters); } if (cp_offset != 0) { if (masm_->emit_debug_code()) { __ Mov(x10, cp_offset * char_size()); __ Add(x10, x10, Operand(current_input_offset(), SXTW)); __ Cmp(x10, Operand(w10, SXTW)); // The offset needs to fit in a W register. __ Check(eq, AbortReason::kOffsetOutOfRange); } else { __ Add(w10, current_input_offset(), cp_offset * char_size()); } offset = w10; } if (mode_ == LATIN1) { if (characters == 4) { __ Ldr(current_character(), MemOperand(input_end(), offset, SXTW)); } else if (characters == 2) { __ Ldrh(current_character(), MemOperand(input_end(), offset, SXTW)); } else { DCHECK_EQ(1, characters); __ Ldrb(current_character(), MemOperand(input_end(), offset, SXTW)); } } else { DCHECK(mode_ == UC16); if (characters == 2) { __ Ldr(current_character(), MemOperand(input_end(), offset, SXTW)); } else { DCHECK_EQ(1, characters); __ Ldrh(current_character(), MemOperand(input_end(), offset, SXTW)); } } } #endif // V8_INTERPRETED_REGEXP } // namespace internal } // namespace v8 #undef __ #endif // V8_TARGET_ARCH_ARM64