• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/api.h"
6 #include "src/arm64/assembler-arm64-inl.h"
7 #include "src/arm64/macro-assembler-arm64-inl.h"
8 #include "src/deoptimizer.h"
9 #include "src/frame-constants.h"
10 #include "src/register-configuration.h"
11 #include "src/safepoint-table.h"
12 
13 
14 namespace v8 {
15 namespace internal {
16 
17 #define __ masm()->
18 
19 namespace {
20 
CopyRegListToFrame(MacroAssembler * masm,const Register & dst,int dst_offset,const CPURegList & reg_list,const Register & temp0,const Register & temp1,int src_offset=0)21 void CopyRegListToFrame(MacroAssembler* masm, const Register& dst,
22                         int dst_offset, const CPURegList& reg_list,
23                         const Register& temp0, const Register& temp1,
24                         int src_offset = 0) {
25   DCHECK_EQ(reg_list.Count() % 2, 0);
26   UseScratchRegisterScope temps(masm);
27   CPURegList copy_to_input = reg_list;
28   int reg_size = reg_list.RegisterSizeInBytes();
29   DCHECK_EQ(temp0.SizeInBytes(), reg_size);
30   DCHECK_EQ(temp1.SizeInBytes(), reg_size);
31 
32   // Compute some temporary addresses to avoid having the macro assembler set
33   // up a temp with an offset for accesses out of the range of the addressing
34   // mode.
35   Register src = temps.AcquireX();
36   masm->Add(src, sp, src_offset);
37   masm->Add(dst, dst, dst_offset);
38 
39   // Write reg_list into the frame pointed to by dst.
40   for (int i = 0; i < reg_list.Count(); i += 2) {
41     masm->Ldp(temp0, temp1, MemOperand(src, i * reg_size));
42 
43     CPURegister reg0 = copy_to_input.PopLowestIndex();
44     CPURegister reg1 = copy_to_input.PopLowestIndex();
45     int offset0 = reg0.code() * reg_size;
46     int offset1 = reg1.code() * reg_size;
47 
48     // Pair up adjacent stores, otherwise write them separately.
49     if (offset1 == offset0 + reg_size) {
50       masm->Stp(temp0, temp1, MemOperand(dst, offset0));
51     } else {
52       masm->Str(temp0, MemOperand(dst, offset0));
53       masm->Str(temp1, MemOperand(dst, offset1));
54     }
55   }
56   masm->Sub(dst, dst, dst_offset);
57 }
58 
RestoreRegList(MacroAssembler * masm,const CPURegList & reg_list,const Register & src_base,int src_offset)59 void RestoreRegList(MacroAssembler* masm, const CPURegList& reg_list,
60                     const Register& src_base, int src_offset) {
61   DCHECK_EQ(reg_list.Count() % 2, 0);
62   UseScratchRegisterScope temps(masm);
63   CPURegList restore_list = reg_list;
64   int reg_size = restore_list.RegisterSizeInBytes();
65 
66   // Compute a temporary addresses to avoid having the macro assembler set
67   // up a temp with an offset for accesses out of the range of the addressing
68   // mode.
69   Register src = temps.AcquireX();
70   masm->Add(src, src_base, src_offset);
71 
72   // Restore every register in restore_list from src.
73   while (!restore_list.IsEmpty()) {
74     CPURegister reg0 = restore_list.PopLowestIndex();
75     CPURegister reg1 = restore_list.PopLowestIndex();
76     int offset0 = reg0.code() * reg_size;
77     int offset1 = reg1.code() * reg_size;
78 
79     // Pair up adjacent loads, otherwise read them separately.
80     if (offset1 == offset0 + reg_size) {
81       masm->Ldp(reg0, reg1, MemOperand(src, offset0));
82     } else {
83       masm->Ldr(reg0, MemOperand(src, offset0));
84       masm->Ldr(reg1, MemOperand(src, offset1));
85     }
86   }
87 }
88 }  // namespace
89 
Generate()90 void Deoptimizer::TableEntryGenerator::Generate() {
91   GeneratePrologue();
92 
93   // TODO(all): This code needs to be revisited. We probably only need to save
94   // caller-saved registers here. Callee-saved registers can be stored directly
95   // in the input frame.
96 
97   // Save all allocatable double registers.
98   CPURegList saved_double_registers(
99       CPURegister::kVRegister, kDRegSizeInBits,
100       RegisterConfiguration::Default()->allocatable_double_codes_mask());
101   DCHECK_EQ(saved_double_registers.Count() % 2, 0);
102   __ PushCPURegList(saved_double_registers);
103 
104   CPURegList saved_float_registers(
105       CPURegister::kVRegister, kSRegSizeInBits,
106       RegisterConfiguration::Default()->allocatable_float_codes_mask());
107   DCHECK_EQ(saved_float_registers.Count() % 4, 0);
108   __ PushCPURegList(saved_float_registers);
109 
110   // We save all the registers except sp, lr and the masm scratches.
111   CPURegList saved_registers(CPURegister::kRegister, kXRegSizeInBits, 0, 28);
112   saved_registers.Remove(ip0);
113   saved_registers.Remove(ip1);
114   saved_registers.Combine(fp);
115   DCHECK_EQ(saved_registers.Count() % 2, 0);
116   __ PushCPURegList(saved_registers);
117 
118   __ Mov(x3, Operand(ExternalReference::Create(
119                  IsolateAddressId::kCEntryFPAddress, isolate())));
120   __ Str(fp, MemOperand(x3));
121 
122   const int kSavedRegistersAreaSize =
123       (saved_registers.Count() * kXRegSize) +
124       (saved_double_registers.Count() * kDRegSize) +
125       (saved_float_registers.Count() * kSRegSize);
126 
127   // Floating point registers are saved on the stack above core registers.
128   const int kFloatRegistersOffset = saved_registers.Count() * kXRegSize;
129   const int kDoubleRegistersOffset =
130       kFloatRegistersOffset + saved_float_registers.Count() * kSRegSize;
131 
132   // Get the bailout id from the stack.
133   Register bailout_id = x2;
134   __ Peek(bailout_id, kSavedRegistersAreaSize);
135 
136   Register code_object = x3;
137   Register fp_to_sp = x4;
138   // Get the address of the location in the code object. This is the return
139   // address for lazy deoptimization.
140   __ Mov(code_object, lr);
141   // Compute the fp-to-sp delta, adding two words for alignment padding and
142   // bailout id.
143   __ Add(fp_to_sp, sp, kSavedRegistersAreaSize + (2 * kPointerSize));
144   __ Sub(fp_to_sp, fp, fp_to_sp);
145 
146   // Allocate a new deoptimizer object.
147   __ Ldr(x1, MemOperand(fp, CommonFrameConstants::kContextOrFrameTypeOffset));
148 
149   // Ensure we can safely load from below fp.
150   DCHECK_GT(kSavedRegistersAreaSize,
151             -JavaScriptFrameConstants::kFunctionOffset);
152   __ Ldr(x0, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
153 
154   // If x1 is a smi, zero x0.
155   __ Tst(x1, kSmiTagMask);
156   __ CzeroX(x0, eq);
157 
158   __ Mov(x1, static_cast<int>(deopt_kind()));
159   // Following arguments are already loaded:
160   //  - x2: bailout id
161   //  - x3: code object address
162   //  - x4: fp-to-sp delta
163   __ Mov(x5, ExternalReference::isolate_address(isolate()));
164 
165   {
166     // Call Deoptimizer::New().
167     AllowExternalCallThatCantCauseGC scope(masm());
168     __ CallCFunction(ExternalReference::new_deoptimizer_function(), 6);
169   }
170 
171   // Preserve "deoptimizer" object in register x0.
172   Register deoptimizer = x0;
173 
174   // Get the input frame descriptor pointer.
175   __ Ldr(x1, MemOperand(deoptimizer, Deoptimizer::input_offset()));
176 
177   // Copy core registers into the input frame.
178   CopyRegListToFrame(masm(), x1, FrameDescription::registers_offset(),
179                      saved_registers, x2, x3);
180 
181   // Copy double registers to the input frame.
182   CopyRegListToFrame(masm(), x1, FrameDescription::double_registers_offset(),
183                      saved_double_registers, x2, x3, kDoubleRegistersOffset);
184 
185   // Copy float registers to the input frame.
186   // TODO(arm): these are the lower 32-bits of the double registers stored
187   // above, so we shouldn't need to store them again.
188   CopyRegListToFrame(masm(), x1, FrameDescription::float_registers_offset(),
189                      saved_float_registers, w2, w3, kFloatRegistersOffset);
190 
191   // Remove the padding, bailout id and the saved registers from the stack.
192   DCHECK_EQ(kSavedRegistersAreaSize % kXRegSize, 0);
193   __ Drop(2 + (kSavedRegistersAreaSize / kXRegSize));
194 
195   // Compute a pointer to the unwinding limit in register x2; that is
196   // the first stack slot not part of the input frame.
197   Register unwind_limit = x2;
198   __ Ldr(unwind_limit, MemOperand(x1, FrameDescription::frame_size_offset()));
199 
200   // Unwind the stack down to - but not including - the unwinding
201   // limit and copy the contents of the activation frame to the input
202   // frame description.
203   __ Add(x3, x1, FrameDescription::frame_content_offset());
204   __ SlotAddress(x1, 0);
205   __ Lsr(unwind_limit, unwind_limit, kPointerSizeLog2);
206   __ Mov(x5, unwind_limit);
207   __ CopyDoubleWords(x3, x1, x5);
208   __ Drop(unwind_limit);
209 
210   // Compute the output frame in the deoptimizer.
211   __ Push(padreg, x0);  // Preserve deoptimizer object across call.
212   {
213     // Call Deoptimizer::ComputeOutputFrames().
214     AllowExternalCallThatCantCauseGC scope(masm());
215     __ CallCFunction(ExternalReference::compute_output_frames_function(), 1);
216   }
217   __ Pop(x4, padreg);  // Restore deoptimizer object (class Deoptimizer).
218 
219   {
220     UseScratchRegisterScope temps(masm());
221     Register scratch = temps.AcquireX();
222     __ Ldr(scratch, MemOperand(x4, Deoptimizer::caller_frame_top_offset()));
223     __ Mov(sp, scratch);
224   }
225 
226   // Replace the current (input) frame with the output frames.
227   Label outer_push_loop, inner_push_loop,
228       outer_loop_header, inner_loop_header;
229   __ Ldrsw(x1, MemOperand(x4, Deoptimizer::output_count_offset()));
230   __ Ldr(x0, MemOperand(x4, Deoptimizer::output_offset()));
231   __ Add(x1, x0, Operand(x1, LSL, kPointerSizeLog2));
232   __ B(&outer_loop_header);
233 
234   __ Bind(&outer_push_loop);
235   Register current_frame = x2;
236   Register frame_size = x3;
237   __ Ldr(current_frame, MemOperand(x0, kPointerSize, PostIndex));
238   __ Ldr(x3, MemOperand(current_frame, FrameDescription::frame_size_offset()));
239   __ Lsr(frame_size, x3, kPointerSizeLog2);
240   __ Claim(frame_size);
241 
242   __ Add(x7, current_frame, FrameDescription::frame_content_offset());
243   __ SlotAddress(x6, 0);
244   __ CopyDoubleWords(x6, x7, frame_size);
245 
246   __ Bind(&outer_loop_header);
247   __ Cmp(x0, x1);
248   __ B(lt, &outer_push_loop);
249 
250   __ Ldr(x1, MemOperand(x4, Deoptimizer::input_offset()));
251   RestoreRegList(masm(), saved_double_registers, x1,
252                  FrameDescription::double_registers_offset());
253 
254   // TODO(all): ARM copies a lot (if not all) of the last output frame onto the
255   // stack, then pops it all into registers. Here, we try to load it directly
256   // into the relevant registers. Is this correct? If so, we should improve the
257   // ARM code.
258 
259   // Restore registers from the last output frame.
260   // Note that lr is not in the list of saved_registers and will be restored
261   // later. We can use it to hold the address of last output frame while
262   // reloading the other registers.
263   DCHECK(!saved_registers.IncludesAliasOf(lr));
264   Register last_output_frame = lr;
265   __ Mov(last_output_frame, current_frame);
266 
267   RestoreRegList(masm(), saved_registers, last_output_frame,
268                  FrameDescription::registers_offset());
269 
270   Register continuation = x7;
271   __ Ldr(continuation, MemOperand(last_output_frame,
272                                   FrameDescription::continuation_offset()));
273   __ Ldr(lr, MemOperand(last_output_frame, FrameDescription::pc_offset()));
274   __ InitializeRootRegister();
275   __ Br(continuation);
276 }
277 
278 // Size of an entry of the second level deopt table. Since we do not generate
279 // a table for ARM64, the size is zero.
280 const int Deoptimizer::table_entry_size_ = 0 * kInstrSize;
281 
GeneratePrologue()282 void Deoptimizer::TableEntryGenerator::GeneratePrologue() {
283   UseScratchRegisterScope temps(masm());
284   // The MacroAssembler will have put the deoptimization id in x16, the first
285   // temp register allocated. We can't assert that the id is in there, but we
286   // can check that x16 the first allocated temp and that the value it contains
287   // is in the expected range.
288   Register entry_id = temps.AcquireX();
289   DCHECK(entry_id.Is(x16));
290   __ Push(padreg, entry_id);
291 
292   if (__ emit_debug_code()) {
293     // Ensure the entry_id looks sensible, ie. 0 <= entry_id < count().
294     __ Cmp(entry_id, count());
295     __ Check(lo, AbortReason::kOffsetOutOfRange);
296   }
297 }
298 
PadTopOfStackRegister()299 bool Deoptimizer::PadTopOfStackRegister() { return true; }
300 
SetCallerPc(unsigned offset,intptr_t value)301 void FrameDescription::SetCallerPc(unsigned offset, intptr_t value) {
302   SetFrameSlot(offset, value);
303 }
304 
305 
SetCallerFp(unsigned offset,intptr_t value)306 void FrameDescription::SetCallerFp(unsigned offset, intptr_t value) {
307   SetFrameSlot(offset, value);
308 }
309 
310 
SetCallerConstantPool(unsigned offset,intptr_t value)311 void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
312   // No embedded constant pool support.
313   UNREACHABLE();
314 }
315 
316 
317 #undef __
318 
319 }  // namespace internal
320 }  // namespace v8
321