1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef A64_ASSEMBLER_A64_H_
8 #define A64_ASSEMBLER_A64_H_
9 
10 #include "jit/arm64/vixl/Assembler-vixl.h"
11 
12 #include "jit/JitCompartment.h"
13 
14 namespace js {
15 namespace jit {
16 
17 // VIXL imports.
18 typedef vixl::Register ARMRegister;
19 typedef vixl::FPRegister ARMFPRegister;
20 using vixl::ARMBuffer;
21 using vixl::Instruction;
22 
23 static const uint32_t AlignmentAtPrologue = 0;
24 static const uint32_t AlignmentMidPrologue = 8;
25 static const Scale ScalePointer = TimesEight;
26 static const uint32_t AlignmentAtAsmJSPrologue = sizeof(void*);
27 
28 // The MacroAssembler uses scratch registers extensively and unexpectedly.
29 // For safety, scratch registers should always be acquired using
30 // vixl::UseScratchRegisterScope.
31 static constexpr Register ScratchReg = { Registers::ip0 };
32 static constexpr ARMRegister ScratchReg64 = { ScratchReg, 64 };
33 
34 static constexpr Register ScratchReg2 = { Registers::ip1 };
35 static constexpr ARMRegister ScratchReg2_64 = { ScratchReg2, 64 };
36 
37 static constexpr FloatRegister ScratchDoubleReg = { FloatRegisters::d31, FloatRegisters::Double };
38 static constexpr FloatRegister ReturnDoubleReg = { FloatRegisters::d0, FloatRegisters::Double };
39 
40 static constexpr FloatRegister ReturnFloat32Reg = { FloatRegisters::s0, FloatRegisters::Single };
41 static constexpr FloatRegister ScratchFloat32Reg = { FloatRegisters::s31, FloatRegisters::Single };
42 
43 static constexpr Register InvalidReg = { Registers::invalid_reg };
44 static constexpr FloatRegister InvalidFloatReg = { FloatRegisters::invalid_fpreg, FloatRegisters::Single };
45 
46 static constexpr Register OsrFrameReg = { Registers::x3 };
47 static constexpr Register ArgumentsRectifierReg = { Registers::x8 };
48 static constexpr Register CallTempReg0 = { Registers::x9 };
49 static constexpr Register CallTempReg1 = { Registers::x10 };
50 static constexpr Register CallTempReg2 = { Registers::x11 };
51 static constexpr Register CallTempReg3 = { Registers::x12 };
52 static constexpr Register CallTempReg4 = { Registers::x13 };
53 static constexpr Register CallTempReg5 = { Registers::x14 };
54 
55 static constexpr Register PreBarrierReg = { Registers::x1 };
56 
57 static constexpr Register ReturnReg = { Registers::x0 };
58 static constexpr Register JSReturnReg = { Registers::x2 };
59 static constexpr Register FramePointer = { Registers::fp };
60 static constexpr Register ZeroRegister = { Registers::sp };
61 static constexpr ARMRegister ZeroRegister64 = { Registers::sp, 64 };
62 static constexpr ARMRegister ZeroRegister32 = { Registers::sp, 32 };
63 
64 static constexpr FloatRegister ReturnSimd128Reg = InvalidFloatReg;
65 static constexpr FloatRegister ScratchSimd128Reg = InvalidFloatReg;
66 
67 // StackPointer is intentionally undefined on ARM64 to prevent misuse:
68 //  using sp as a base register is only valid if sp % 16 == 0.
69 static constexpr Register RealStackPointer = { Registers::sp };
70 
71 static constexpr Register PseudoStackPointer = { Registers::x28 };
72 static constexpr ARMRegister PseudoStackPointer64 = { Registers::x28, 64 };
73 static constexpr ARMRegister PseudoStackPointer32 = { Registers::x28, 32 };
74 
75 // StackPointer for use by irregexp.
76 static constexpr Register RegExpStackPointer = PseudoStackPointer;
77 
78 static constexpr Register IntArgReg0 = { Registers::x0 };
79 static constexpr Register IntArgReg1 = { Registers::x1 };
80 static constexpr Register IntArgReg2 = { Registers::x2 };
81 static constexpr Register IntArgReg3 = { Registers::x3 };
82 static constexpr Register IntArgReg4 = { Registers::x4 };
83 static constexpr Register IntArgReg5 = { Registers::x5 };
84 static constexpr Register IntArgReg6 = { Registers::x6 };
85 static constexpr Register IntArgReg7 = { Registers::x7 };
86 static constexpr Register GlobalReg =  { Registers::x20 };
87 static constexpr Register HeapReg = { Registers::x21 };
88 static constexpr Register HeapLenReg = { Registers::x22 };
89 
90 // Define unsized Registers.
91 #define DEFINE_UNSIZED_REGISTERS(N)  \
92 static constexpr Register r##N = { Registers::x##N };
93 REGISTER_CODE_LIST(DEFINE_UNSIZED_REGISTERS)
94 #undef DEFINE_UNSIZED_REGISTERS
95 static constexpr Register ip0 = { Registers::x16 };
96 static constexpr Register ip1 = { Registers::x16 };
97 static constexpr Register fp  = { Registers::x30 };
98 static constexpr Register lr  = { Registers::x30 };
99 static constexpr Register rzr = { Registers::xzr };
100 
101 // Import VIXL registers into the js::jit namespace.
102 #define IMPORT_VIXL_REGISTERS(N)  \
103 static constexpr ARMRegister w##N = vixl::w##N;  \
104 static constexpr ARMRegister x##N = vixl::x##N;
105 REGISTER_CODE_LIST(IMPORT_VIXL_REGISTERS)
106 #undef IMPORT_VIXL_REGISTERS
107 static constexpr ARMRegister wzr = vixl::wzr;
108 static constexpr ARMRegister xzr = vixl::xzr;
109 static constexpr ARMRegister wsp = vixl::wsp;
110 static constexpr ARMRegister sp = vixl::sp;
111 
112 // Import VIXL VRegisters into the js::jit namespace.
113 #define IMPORT_VIXL_VREGISTERS(N)  \
114 static constexpr ARMFPRegister s##N = vixl::s##N;  \
115 static constexpr ARMFPRegister d##N = vixl::d##N;
116 REGISTER_CODE_LIST(IMPORT_VIXL_VREGISTERS)
117 #undef IMPORT_VIXL_VREGISTERS
118 
119 static constexpr ValueOperand JSReturnOperand = ValueOperand(JSReturnReg);
120 
121 // Registers used in the GenerateFFIIonExit Enable Activation block.
122 static constexpr Register AsmJSIonExitRegCallee = r8;
123 static constexpr Register AsmJSIonExitRegE0 = r0;
124 static constexpr Register AsmJSIonExitRegE1 = r1;
125 static constexpr Register AsmJSIonExitRegE2 = r2;
126 static constexpr Register AsmJSIonExitRegE3 = r3;
127 
128 // Registers used in the GenerateFFIIonExit Disable Activation block.
129 // None of these may be the second scratch register.
130 static constexpr Register AsmJSIonExitRegReturnData = r2;
131 static constexpr Register AsmJSIonExitRegReturnType = r3;
132 static constexpr Register AsmJSIonExitRegD0 = r0;
133 static constexpr Register AsmJSIonExitRegD1 = r1;
134 static constexpr Register AsmJSIonExitRegD2 = r4;
135 
136 static constexpr Register JSReturnReg_Type = r3;
137 static constexpr Register JSReturnReg_Data = r2;
138 
139 static constexpr FloatRegister NANReg = { FloatRegisters::d14, FloatRegisters::Single };
140 // N.B. r8 isn't listed as an aapcs temp register, but we can use it as such because we never
141 // use return-structs.
142 static constexpr Register CallTempNonArgRegs[] = { r8, r9, r10, r11, r12, r13, r14, r15 };
143 static const uint32_t NumCallTempNonArgRegs =
144     mozilla::ArrayLength(CallTempNonArgRegs);
145 
146 static constexpr uint32_t JitStackAlignment = 16;
147 
148 static constexpr uint32_t JitStackValueAlignment = JitStackAlignment / sizeof(Value);
149 static_assert(JitStackAlignment % sizeof(Value) == 0 && JitStackValueAlignment >= 1,
150   "Stack alignment should be a non-zero multiple of sizeof(Value)");
151 
152 // This boolean indicates whether we support SIMD instructions flavoured for
153 // this architecture or not. Rather than a method in the LIRGenerator, it is
154 // here such that it is accessible from the entire codebase. Once full support
155 // for SIMD is reached on all tier-1 platforms, this constant can be deleted.
156 static constexpr bool SupportsSimd = false;
157 static constexpr uint32_t SimdMemoryAlignment = 16;
158 
159 static_assert(CodeAlignment % SimdMemoryAlignment == 0,
160   "Code alignment should be larger than any of the alignments which are used for "
161   "the constant sections of the code buffer.  Thus it should be larger than the "
162   "alignment for SIMD constants.");
163 
164 static const uint32_t AsmJSStackAlignment = SimdMemoryAlignment;
165 static const int32_t AsmJSGlobalRegBias = 1024;
166 
167 class Assembler : public vixl::Assembler
168 {
169   public:
Assembler()170     Assembler()
171       : vixl::Assembler()
172     { }
173 
174     typedef vixl::Condition Condition;
175 
176     void finish();
asmMergeWith(const Assembler & other)177     bool asmMergeWith(const Assembler& other) {
178         MOZ_CRASH("NYI");
179     }
180     void trace(JSTracer* trc);
181 
182     // Emit the jump table, returning the BufferOffset to the first entry in the table.
183     BufferOffset emitExtendedJumpTable();
184     BufferOffset ExtendedJumpTable_;
185     void executableCopy(uint8_t* buffer);
186 
187     BufferOffset immPool(ARMRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
188                          ARMBuffer::PoolEntry* pe = nullptr);
189     BufferOffset immPool64(ARMRegister dest, uint64_t value, ARMBuffer::PoolEntry* pe = nullptr);
190     BufferOffset immPool64Branch(RepatchLabel* label, ARMBuffer::PoolEntry* pe, vixl::Condition c);
191     BufferOffset fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op);
192     BufferOffset fImmPool64(ARMFPRegister dest, double value);
193     BufferOffset fImmPool32(ARMFPRegister dest, float value);
194 
bind(Label * label)195     void bind(Label* label) { bind(label, nextOffset()); }
196     void bind(Label* label, BufferOffset boff);
197     void bind(RepatchLabel* label);
198 
oom()199     bool oom() const {
200         return AssemblerShared::oom() ||
201             armbuffer_.oom() ||
202             jumpRelocations_.oom() ||
203             dataRelocations_.oom() ||
204             preBarriers_.oom();
205     }
206 
copyJumpRelocationTable(uint8_t * dest)207     void copyJumpRelocationTable(uint8_t* dest) const {
208         if (jumpRelocations_.length())
209             memcpy(dest, jumpRelocations_.buffer(), jumpRelocations_.length());
210     }
copyDataRelocationTable(uint8_t * dest)211     void copyDataRelocationTable(uint8_t* dest) const {
212         if (dataRelocations_.length())
213             memcpy(dest, dataRelocations_.buffer(), dataRelocations_.length());
214     }
copyPreBarrierTable(uint8_t * dest)215     void copyPreBarrierTable(uint8_t* dest) const {
216         if (preBarriers_.length())
217             memcpy(dest, preBarriers_.buffer(), preBarriers_.length());
218     }
219 
jumpRelocationTableBytes()220     size_t jumpRelocationTableBytes() const {
221         return jumpRelocations_.length();
222     }
dataRelocationTableBytes()223     size_t dataRelocationTableBytes() const {
224         return dataRelocations_.length();
225     }
preBarrierTableBytes()226     size_t preBarrierTableBytes() const {
227         return preBarriers_.length();
228     }
bytesNeeded()229     size_t bytesNeeded() const {
230         return SizeOfCodeGenerated() +
231             jumpRelocationTableBytes() +
232             dataRelocationTableBytes() +
233             preBarrierTableBytes();
234     }
235 
processCodeLabels(uint8_t * rawCode)236     void processCodeLabels(uint8_t* rawCode) {
237         for (size_t i = 0; i < codeLabels_.length(); i++) {
238             CodeLabel label = codeLabels_[i];
239             Bind(rawCode, label.patchAt(), rawCode + label.target()->offset());
240         }
241     }
242 
Bind(uint8_t * rawCode,CodeOffset * label,const void * address)243     void Bind(uint8_t* rawCode, CodeOffset* label, const void* address) {
244         *reinterpret_cast<const void**>(rawCode + label->offset()) = address;
245     }
246 
247     void retarget(Label* cur, Label* next);
retargetWithOffset(size_t baseOffset,const LabelBase * label,LabelBase * target)248     void retargetWithOffset(size_t baseOffset, const LabelBase* label, LabelBase* target) {
249         MOZ_CRASH("NYI");
250     }
251 
252     // The buffer is about to be linked. Ensure any constant pools or
253     // excess bookkeeping has been flushed to the instruction stream.
flush()254     void flush() {
255         armbuffer_.flushPool();
256     }
257 
actualIndex(int curOffset)258     int actualIndex(int curOffset) {
259         ARMBuffer::PoolEntry pe(curOffset);
260         return armbuffer_.poolEntryOffset(pe);
261     }
labelToPatchOffset(CodeOffset label)262     size_t labelToPatchOffset(CodeOffset label) {
263         return label.offset();
264     }
PatchableJumpAddress(JitCode * code,uint32_t index)265     static uint8_t* PatchableJumpAddress(JitCode* code, uint32_t index) {
266         return code->raw() + index;
267     }
setPrinter(Sprinter * sp)268     void setPrinter(Sprinter* sp) {
269     }
270 
SupportsFloatingPoint()271     static bool SupportsFloatingPoint() { return true; }
SupportsSimd()272     static bool SupportsSimd() { return js::jit::SupportsSimd; }
273 
274     // Tracks a jump that is patchable after finalization.
275     void addJumpRelocation(BufferOffset src, Relocation::Kind reloc);
276 
277   protected:
278     // Add a jump whose target is unknown until finalization.
279     // The jump may not be patched at runtime.
280     void addPendingJump(BufferOffset src, ImmPtr target, Relocation::Kind kind);
281 
282     // Add a jump whose target is unknown until finalization, and may change
283     // thereafter. The jump is patchable at runtime.
284     size_t addPatchableJump(BufferOffset src, Relocation::Kind kind);
285 
286   public:
PatchWrite_NearCallSize()287     static uint32_t PatchWrite_NearCallSize() {
288         return 4;
289     }
290 
NopSize()291     static uint32_t NopSize() {
292         return 4;
293     }
294 
PatchWrite_NearCall(CodeLocationLabel start,CodeLocationLabel toCall)295     static void PatchWrite_NearCall(CodeLocationLabel start, CodeLocationLabel toCall) {
296         Instruction* dest = (Instruction*)start.raw();
297         //printf("patching %p with call to %p\n", start.raw(), toCall.raw());
298         bl(dest, ((Instruction*)toCall.raw() - dest)>>2);
299 
300     }
301     static void PatchDataWithValueCheck(CodeLocationLabel label,
302                                         PatchedImmPtr newValue,
303                                         PatchedImmPtr expected);
304 
305     static void PatchDataWithValueCheck(CodeLocationLabel label,
306                                         ImmPtr newValue,
307                                         ImmPtr expected);
308 
PatchWrite_Imm32(CodeLocationLabel label,Imm32 imm)309     static void PatchWrite_Imm32(CodeLocationLabel label, Imm32 imm) {
310         // Raw is going to be the return address.
311         uint32_t* raw = (uint32_t*)label.raw();
312         // Overwrite the 4 bytes before the return address, which will end up being
313         // the call instruction.
314         *(raw - 1) = imm.value;
315     }
AlignDoubleArg(uint32_t offset)316     static uint32_t AlignDoubleArg(uint32_t offset) {
317         MOZ_CRASH("AlignDoubleArg()");
318     }
GetPointer(uint8_t * ptr)319     static uintptr_t GetPointer(uint8_t* ptr) {
320         Instruction* i = reinterpret_cast<Instruction*>(ptr);
321         uint64_t ret = i->Literal64();
322         return ret;
323     }
324 
325     // Toggle a jmp or cmp emitted by toggledJump().
326     static void ToggleToJmp(CodeLocationLabel inst_);
327     static void ToggleToCmp(CodeLocationLabel inst_);
328     static void ToggleCall(CodeLocationLabel inst_, bool enabled);
329 
330     static void TraceJumpRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
331     static void TraceDataRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
332 
333     static void PatchInstructionImmediate(uint8_t* code, PatchedImmPtr imm);
334 
335     static void FixupNurseryObjects(JSContext* cx, JitCode* code, CompactBufferReader& reader,
336                                     const ObjectVector& nurseryObjects);
337 
338   public:
339     // A Jump table entry is 2 instructions, with 8 bytes of raw data
340     static const size_t SizeOfJumpTableEntry = 16;
341 
342     struct JumpTableEntry
343     {
344         uint32_t ldr;
345         uint32_t br;
346         void* data;
347 
getLdrJumpTableEntry348         Instruction* getLdr() {
349             return reinterpret_cast<Instruction*>(&ldr);
350         }
351     };
352 
353     // Offset of the patchable target for the given entry.
354     static const size_t OffsetOfJumpTableEntryPointer = 8;
355 
356   public:
357     static void UpdateBoundsCheck(uint32_t logHeapSize, Instruction* inst);
358 
writeCodePointer(AbsoluteLabel * absoluteLabel)359     void writeCodePointer(AbsoluteLabel* absoluteLabel) {
360         MOZ_ASSERT(!absoluteLabel->bound());
361         uintptr_t x = LabelBase::INVALID_OFFSET;
362         BufferOffset off = EmitData(&x, sizeof(uintptr_t));
363 
364         // The x86/x64 makes general use of AbsoluteLabel and weaves a linked list
365         // of uses of an AbsoluteLabel through the assembly. ARM only uses labels
366         // for the case statements of switch jump tables. Thus, for simplicity, we
367         // simply treat the AbsoluteLabel as a label and bind it to the offset of
368         // the jump table entry that needs to be patched.
369         LabelBase* label = absoluteLabel;
370         label->bind(off.getOffset());
371     }
372 
verifyHeapAccessDisassembly(uint32_t begin,uint32_t end,const Disassembler::HeapAccess & heapAccess)373     void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end,
374                                      const Disassembler::HeapAccess& heapAccess)
375     {
376         MOZ_CRASH("verifyHeapAccessDisassembly");
377     }
378 
379   protected:
380     // Because jumps may be relocated to a target inaccessible by a short jump,
381     // each relocatable jump must have a unique entry in the extended jump table.
382     // Valid relocatable targets are of type Relocation::JITCODE.
383     struct JumpRelocation
384     {
385         BufferOffset jump; // Offset to the short jump, from the start of the code buffer.
386         uint32_t extendedTableIndex; // Unique index within the extended jump table.
387 
JumpRelocationJumpRelocation388         JumpRelocation(BufferOffset jump, uint32_t extendedTableIndex)
389           : jump(jump), extendedTableIndex(extendedTableIndex)
390         { }
391     };
392 
393     // Structure for fixing up pc-relative loads/jumps when the machine
394     // code gets moved (executable copy, gc, etc.).
395     struct RelativePatch
396     {
397         BufferOffset offset;
398         void* target;
399         Relocation::Kind kind;
400 
RelativePatchRelativePatch401         RelativePatch(BufferOffset offset, void* target, Relocation::Kind kind)
402           : offset(offset), target(target), kind(kind)
403         { }
404     };
405 
406     // List of jumps for which the target is either unknown until finalization,
407     // or cannot be known due to GC. Each entry here requires a unique entry
408     // in the extended jump table, and is patched at finalization.
409     js::Vector<RelativePatch, 8, SystemAllocPolicy> pendingJumps_;
410 
411     // Final output formatters.
412     CompactBufferWriter jumpRelocations_;
413     CompactBufferWriter dataRelocations_;
414     CompactBufferWriter preBarriers_;
415 };
416 
417 static const uint32_t NumIntArgRegs = 8;
418 static const uint32_t NumFloatArgRegs = 8;
419 
420 class ABIArgGenerator
421 {
422   public:
ABIArgGenerator()423     ABIArgGenerator()
424       : intRegIndex_(0),
425         floatRegIndex_(0),
426         stackOffset_(0),
427         current_()
428     { }
429 
430     ABIArg next(MIRType argType);
current()431     ABIArg& current() { return current_; }
stackBytesConsumedSoFar()432     uint32_t stackBytesConsumedSoFar() const { return stackOffset_; }
433 
434   public:
435     static const Register NonArgReturnReg0;
436     static const Register NonArgReturnReg1;
437     static const Register NonVolatileReg;
438     static const Register NonArg_VolatileReg;
439     static const Register NonReturn_VolatileReg0;
440     static const Register NonReturn_VolatileReg1;
441 
442   protected:
443     unsigned intRegIndex_;
444     unsigned floatRegIndex_;
445     uint32_t stackOffset_;
446     ABIArg current_;
447 };
448 
449 static inline bool
GetIntArgReg(uint32_t usedIntArgs,uint32_t usedFloatArgs,Register * out)450 GetIntArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register* out)
451 {
452     if (usedIntArgs >= NumIntArgRegs)
453         return false;
454     *out = Register::FromCode(usedIntArgs);
455     return true;
456 }
457 
458 static inline bool
GetFloatArgReg(uint32_t usedIntArgs,uint32_t usedFloatArgs,FloatRegister * out)459 GetFloatArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, FloatRegister* out)
460 {
461     if (usedFloatArgs >= NumFloatArgRegs)
462         return false;
463     *out = FloatRegister::FromCode(usedFloatArgs);
464     return true;
465 }
466 
467 // Get a register in which we plan to put a quantity that will be used as an
468 // integer argument.  This differs from GetIntArgReg in that if we have no more
469 // actual argument registers to use we will fall back on using whatever
470 // CallTempReg* don't overlap the argument registers, and only fail once those
471 // run out too.
472 static inline bool
GetTempRegForIntArg(uint32_t usedIntArgs,uint32_t usedFloatArgs,Register * out)473 GetTempRegForIntArg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register* out)
474 {
475     if (GetIntArgReg(usedIntArgs, usedFloatArgs, out))
476         return true;
477     // Unfortunately, we have to assume things about the point at which
478     // GetIntArgReg returns false, because we need to know how many registers it
479     // can allocate.
480     usedIntArgs -= NumIntArgRegs;
481     if (usedIntArgs >= NumCallTempNonArgRegs)
482         return false;
483     *out = CallTempNonArgRegs[usedIntArgs];
484     return true;
485 }
486 
487 inline Imm32
firstHalf()488 Imm64::firstHalf() const
489 {
490     return low();
491 }
492 
493 inline Imm32
secondHalf()494 Imm64::secondHalf() const
495 {
496     return hi();
497 }
498 
499 void PatchJump(CodeLocationJump& jump_, CodeLocationLabel label,
500                ReprotectCode reprotect = DontReprotect);
501 
502 static inline void
PatchBackedge(CodeLocationJump & jump_,CodeLocationLabel label,JitRuntime::BackedgeTarget target)503 PatchBackedge(CodeLocationJump& jump_, CodeLocationLabel label, JitRuntime::BackedgeTarget target)
504 {
505     PatchJump(jump_, label);
506 }
507 
508 // Forbids pool generation during a specified interval. Not nestable.
509 class AutoForbidPools
510 {
511     Assembler* asm_;
512 
513   public:
AutoForbidPools(Assembler * asm_,size_t maxInst)514     AutoForbidPools(Assembler* asm_, size_t maxInst)
515       : asm_(asm_)
516     {
517         asm_->enterNoPool(maxInst);
518     }
519 
~AutoForbidPools()520     ~AutoForbidPools() {
521         asm_->leaveNoPool();
522     }
523 };
524 
525 } // namespace jit
526 } // namespace js
527 
528 #endif // A64_ASSEMBLER_A64_H_
529