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