1 /*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "codegen_mips.h"
18 #include "dex/compiler_internals.h"
19 #include "dex/quick/mir_to_lir-inl.h"
20 #include "mips_lir.h"
21
22 #include <string>
23
24 namespace art {
25
26 static int core_regs[] = {r_ZERO, r_AT, r_V0, r_V1, r_A0, r_A1, r_A2, r_A3,
27 r_T0, r_T1, r_T2, r_T3, r_T4, r_T5, r_T6, r_T7,
28 r_S0, r_S1, r_S2, r_S3, r_S4, r_S5, r_S6, r_S7, r_T8,
29 r_T9, r_K0, r_K1, r_GP, r_SP, r_FP, r_RA};
30 static int ReservedRegs[] = {r_ZERO, r_AT, r_S0, r_S1, r_K0, r_K1, r_GP, r_SP,
31 r_RA};
32 static int core_temps[] = {r_V0, r_V1, r_A0, r_A1, r_A2, r_A3, r_T0, r_T1, r_T2,
33 r_T3, r_T4, r_T5, r_T6, r_T7, r_T8};
34 static int FpRegs[] = {r_F0, r_F1, r_F2, r_F3, r_F4, r_F5, r_F6, r_F7,
35 r_F8, r_F9, r_F10, r_F11, r_F12, r_F13, r_F14, r_F15};
36 static int fp_temps[] = {r_F0, r_F1, r_F2, r_F3, r_F4, r_F5, r_F6, r_F7,
37 r_F8, r_F9, r_F10, r_F11, r_F12, r_F13, r_F14, r_F15};
38
LocCReturn()39 RegLocation MipsMir2Lir::LocCReturn() {
40 RegLocation res = MIPS_LOC_C_RETURN;
41 return res;
42 }
43
LocCReturnWide()44 RegLocation MipsMir2Lir::LocCReturnWide() {
45 RegLocation res = MIPS_LOC_C_RETURN_WIDE;
46 return res;
47 }
48
LocCReturnFloat()49 RegLocation MipsMir2Lir::LocCReturnFloat() {
50 RegLocation res = MIPS_LOC_C_RETURN_FLOAT;
51 return res;
52 }
53
LocCReturnDouble()54 RegLocation MipsMir2Lir::LocCReturnDouble() {
55 RegLocation res = MIPS_LOC_C_RETURN_DOUBLE;
56 return res;
57 }
58
59 // Return a target-dependent special register.
TargetReg(SpecialTargetRegister reg)60 int MipsMir2Lir::TargetReg(SpecialTargetRegister reg) {
61 int res = INVALID_REG;
62 switch (reg) {
63 case kSelf: res = rMIPS_SELF; break;
64 case kSuspend: res = rMIPS_SUSPEND; break;
65 case kLr: res = rMIPS_LR; break;
66 case kPc: res = rMIPS_PC; break;
67 case kSp: res = rMIPS_SP; break;
68 case kArg0: res = rMIPS_ARG0; break;
69 case kArg1: res = rMIPS_ARG1; break;
70 case kArg2: res = rMIPS_ARG2; break;
71 case kArg3: res = rMIPS_ARG3; break;
72 case kFArg0: res = rMIPS_FARG0; break;
73 case kFArg1: res = rMIPS_FARG1; break;
74 case kFArg2: res = rMIPS_FARG2; break;
75 case kFArg3: res = rMIPS_FARG3; break;
76 case kRet0: res = rMIPS_RET0; break;
77 case kRet1: res = rMIPS_RET1; break;
78 case kInvokeTgt: res = rMIPS_INVOKE_TGT; break;
79 case kCount: res = rMIPS_COUNT; break;
80 }
81 return res;
82 }
83
84 // Create a double from a pair of singles.
S2d(int low_reg,int high_reg)85 int MipsMir2Lir::S2d(int low_reg, int high_reg) {
86 return MIPS_S2D(low_reg, high_reg);
87 }
88
89 // Return mask to strip off fp reg flags and bias.
FpRegMask()90 uint32_t MipsMir2Lir::FpRegMask() {
91 return MIPS_FP_REG_MASK;
92 }
93
94 // True if both regs single, both core or both double.
SameRegType(int reg1,int reg2)95 bool MipsMir2Lir::SameRegType(int reg1, int reg2) {
96 return (MIPS_REGTYPE(reg1) == MIPS_REGTYPE(reg2));
97 }
98
99 /*
100 * Decode the register id.
101 */
GetRegMaskCommon(int reg)102 uint64_t MipsMir2Lir::GetRegMaskCommon(int reg) {
103 uint64_t seed;
104 int shift;
105 int reg_id;
106
107
108 reg_id = reg & 0x1f;
109 /* Each double register is equal to a pair of single-precision FP registers */
110 seed = MIPS_DOUBLEREG(reg) ? 3 : 1;
111 /* FP register starts at bit position 16 */
112 shift = MIPS_FPREG(reg) ? kMipsFPReg0 : 0;
113 /* Expand the double register id into single offset */
114 shift += reg_id;
115 return (seed << shift);
116 }
117
GetPCUseDefEncoding()118 uint64_t MipsMir2Lir::GetPCUseDefEncoding() {
119 return ENCODE_MIPS_REG_PC;
120 }
121
122
SetupTargetResourceMasks(LIR * lir)123 void MipsMir2Lir::SetupTargetResourceMasks(LIR* lir) {
124 DCHECK_EQ(cu_->instruction_set, kMips);
125
126 // Mips-specific resource map setup here.
127 uint64_t flags = MipsMir2Lir::EncodingMap[lir->opcode].flags;
128
129 if (flags & REG_DEF_SP) {
130 lir->def_mask |= ENCODE_MIPS_REG_SP;
131 }
132
133 if (flags & REG_USE_SP) {
134 lir->use_mask |= ENCODE_MIPS_REG_SP;
135 }
136
137 if (flags & REG_DEF_LR) {
138 lir->def_mask |= ENCODE_MIPS_REG_LR;
139 }
140 }
141
142 /* For dumping instructions */
143 #define MIPS_REG_COUNT 32
144 static const char *mips_reg_name[MIPS_REG_COUNT] = {
145 "zero", "at", "v0", "v1", "a0", "a1", "a2", "a3",
146 "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
147 "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
148 "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra"
149 };
150
151 /*
152 * Interpret a format string and build a string no longer than size
153 * See format key in Assemble.c.
154 */
BuildInsnString(const char * fmt,LIR * lir,unsigned char * base_addr)155 std::string MipsMir2Lir::BuildInsnString(const char *fmt, LIR *lir, unsigned char* base_addr) {
156 std::string buf;
157 int i;
158 const char *fmt_end = &fmt[strlen(fmt)];
159 char tbuf[256];
160 char nc;
161 while (fmt < fmt_end) {
162 int operand;
163 if (*fmt == '!') {
164 fmt++;
165 DCHECK_LT(fmt, fmt_end);
166 nc = *fmt++;
167 if (nc == '!') {
168 strcpy(tbuf, "!");
169 } else {
170 DCHECK_LT(fmt, fmt_end);
171 DCHECK_LT(static_cast<unsigned>(nc-'0'), 4u);
172 operand = lir->operands[nc-'0'];
173 switch (*fmt++) {
174 case 'b':
175 strcpy(tbuf, "0000");
176 for (i = 3; i >= 0; i--) {
177 tbuf[i] += operand & 1;
178 operand >>= 1;
179 }
180 break;
181 case 's':
182 sprintf(tbuf, "$f%d", operand & MIPS_FP_REG_MASK);
183 break;
184 case 'S':
185 DCHECK_EQ(((operand & MIPS_FP_REG_MASK) & 1), 0);
186 sprintf(tbuf, "$f%d", operand & MIPS_FP_REG_MASK);
187 break;
188 case 'h':
189 sprintf(tbuf, "%04x", operand);
190 break;
191 case 'M':
192 case 'd':
193 sprintf(tbuf, "%d", operand);
194 break;
195 case 'D':
196 sprintf(tbuf, "%d", operand+1);
197 break;
198 case 'E':
199 sprintf(tbuf, "%d", operand*4);
200 break;
201 case 'F':
202 sprintf(tbuf, "%d", operand*2);
203 break;
204 case 't':
205 sprintf(tbuf, "0x%08x (L%p)", reinterpret_cast<uintptr_t>(base_addr) + lir->offset + 4 +
206 (operand << 2), lir->target);
207 break;
208 case 'T':
209 sprintf(tbuf, "0x%08x", operand << 2);
210 break;
211 case 'u': {
212 int offset_1 = lir->operands[0];
213 int offset_2 = NEXT_LIR(lir)->operands[0];
214 uintptr_t target =
215 (((reinterpret_cast<uintptr_t>(base_addr) + lir->offset + 4) & ~3) +
216 (offset_1 << 21 >> 9) + (offset_2 << 1)) & 0xfffffffc;
217 sprintf(tbuf, "%p", reinterpret_cast<void*>(target));
218 break;
219 }
220
221 /* Nothing to print for BLX_2 */
222 case 'v':
223 strcpy(tbuf, "see above");
224 break;
225 case 'r':
226 DCHECK(operand >= 0 && operand < MIPS_REG_COUNT);
227 strcpy(tbuf, mips_reg_name[operand]);
228 break;
229 case 'N':
230 // Placeholder for delay slot handling
231 strcpy(tbuf, "; nop");
232 break;
233 default:
234 strcpy(tbuf, "DecodeError");
235 break;
236 }
237 buf += tbuf;
238 }
239 } else {
240 buf += *fmt++;
241 }
242 }
243 return buf;
244 }
245
246 // FIXME: need to redo resource maps for MIPS - fix this at that time
DumpResourceMask(LIR * mips_lir,uint64_t mask,const char * prefix)247 void MipsMir2Lir::DumpResourceMask(LIR *mips_lir, uint64_t mask, const char *prefix) {
248 char buf[256];
249 buf[0] = 0;
250
251 if (mask == ENCODE_ALL) {
252 strcpy(buf, "all");
253 } else {
254 char num[8];
255 int i;
256
257 for (i = 0; i < kMipsRegEnd; i++) {
258 if (mask & (1ULL << i)) {
259 sprintf(num, "%d ", i);
260 strcat(buf, num);
261 }
262 }
263
264 if (mask & ENCODE_CCODE) {
265 strcat(buf, "cc ");
266 }
267 if (mask & ENCODE_FP_STATUS) {
268 strcat(buf, "fpcc ");
269 }
270 /* Memory bits */
271 if (mips_lir && (mask & ENCODE_DALVIK_REG)) {
272 sprintf(buf + strlen(buf), "dr%d%s", mips_lir->alias_info & 0xffff,
273 (mips_lir->alias_info & 0x80000000) ? "(+1)" : "");
274 }
275 if (mask & ENCODE_LITERAL) {
276 strcat(buf, "lit ");
277 }
278
279 if (mask & ENCODE_HEAP_REF) {
280 strcat(buf, "heap ");
281 }
282 if (mask & ENCODE_MUST_NOT_ALIAS) {
283 strcat(buf, "noalias ");
284 }
285 }
286 if (buf[0]) {
287 LOG(INFO) << prefix << ": " << buf;
288 }
289 }
290
291 /*
292 * TUNING: is true leaf? Can't just use METHOD_IS_LEAF to determine as some
293 * instructions might call out to C/assembly helper functions. Until
294 * machinery is in place, always spill lr.
295 */
296
AdjustSpillMask()297 void MipsMir2Lir::AdjustSpillMask() {
298 core_spill_mask_ |= (1 << r_RA);
299 num_core_spills_++;
300 }
301
302 /*
303 * Mark a callee-save fp register as promoted. Note that
304 * vpush/vpop uses contiguous register lists so we must
305 * include any holes in the mask. Associate holes with
306 * Dalvik register INVALID_VREG (0xFFFFU).
307 */
MarkPreservedSingle(int s_reg,int reg)308 void MipsMir2Lir::MarkPreservedSingle(int s_reg, int reg) {
309 LOG(FATAL) << "No support yet for promoted FP regs";
310 }
311
FlushRegWide(int reg1,int reg2)312 void MipsMir2Lir::FlushRegWide(int reg1, int reg2) {
313 RegisterInfo* info1 = GetRegInfo(reg1);
314 RegisterInfo* info2 = GetRegInfo(reg2);
315 DCHECK(info1 && info2 && info1->pair && info2->pair &&
316 (info1->partner == info2->reg) &&
317 (info2->partner == info1->reg));
318 if ((info1->live && info1->dirty) || (info2->live && info2->dirty)) {
319 if (!(info1->is_temp && info2->is_temp)) {
320 /* Should not happen. If it does, there's a problem in eval_loc */
321 LOG(FATAL) << "Long half-temp, half-promoted";
322 }
323
324 info1->dirty = false;
325 info2->dirty = false;
326 if (mir_graph_->SRegToVReg(info2->s_reg) < mir_graph_->SRegToVReg(info1->s_reg))
327 info1 = info2;
328 int v_reg = mir_graph_->SRegToVReg(info1->s_reg);
329 StoreBaseDispWide(rMIPS_SP, VRegOffset(v_reg), info1->reg, info1->partner);
330 }
331 }
332
FlushReg(int reg)333 void MipsMir2Lir::FlushReg(int reg) {
334 RegisterInfo* info = GetRegInfo(reg);
335 if (info->live && info->dirty) {
336 info->dirty = false;
337 int v_reg = mir_graph_->SRegToVReg(info->s_reg);
338 StoreBaseDisp(rMIPS_SP, VRegOffset(v_reg), reg, kWord);
339 }
340 }
341
342 /* Give access to the target-dependent FP register encoding to common code */
IsFpReg(int reg)343 bool MipsMir2Lir::IsFpReg(int reg) {
344 return MIPS_FPREG(reg);
345 }
346
347 /* Clobber all regs that might be used by an external C call */
ClobberCalleeSave()348 void MipsMir2Lir::ClobberCalleeSave() {
349 Clobber(r_ZERO);
350 Clobber(r_AT);
351 Clobber(r_V0);
352 Clobber(r_V1);
353 Clobber(r_A0);
354 Clobber(r_A1);
355 Clobber(r_A2);
356 Clobber(r_A3);
357 Clobber(r_T0);
358 Clobber(r_T1);
359 Clobber(r_T2);
360 Clobber(r_T3);
361 Clobber(r_T4);
362 Clobber(r_T5);
363 Clobber(r_T6);
364 Clobber(r_T7);
365 Clobber(r_T8);
366 Clobber(r_T9);
367 Clobber(r_K0);
368 Clobber(r_K1);
369 Clobber(r_GP);
370 Clobber(r_FP);
371 Clobber(r_RA);
372 Clobber(r_F0);
373 Clobber(r_F1);
374 Clobber(r_F2);
375 Clobber(r_F3);
376 Clobber(r_F4);
377 Clobber(r_F5);
378 Clobber(r_F6);
379 Clobber(r_F7);
380 Clobber(r_F8);
381 Clobber(r_F9);
382 Clobber(r_F10);
383 Clobber(r_F11);
384 Clobber(r_F12);
385 Clobber(r_F13);
386 Clobber(r_F14);
387 Clobber(r_F15);
388 }
389
GetReturnWideAlt()390 RegLocation MipsMir2Lir::GetReturnWideAlt() {
391 UNIMPLEMENTED(FATAL) << "No GetReturnWideAlt for MIPS";
392 RegLocation res = LocCReturnWide();
393 return res;
394 }
395
GetReturnAlt()396 RegLocation MipsMir2Lir::GetReturnAlt() {
397 UNIMPLEMENTED(FATAL) << "No GetReturnAlt for MIPS";
398 RegLocation res = LocCReturn();
399 return res;
400 }
401
GetRegInfo(int reg)402 MipsMir2Lir::RegisterInfo* MipsMir2Lir::GetRegInfo(int reg) {
403 return MIPS_FPREG(reg) ? ®_pool_->FPRegs[reg & MIPS_FP_REG_MASK]
404 : ®_pool_->core_regs[reg];
405 }
406
407 /* To be used when explicitly managing register use */
LockCallTemps()408 void MipsMir2Lir::LockCallTemps() {
409 LockTemp(rMIPS_ARG0);
410 LockTemp(rMIPS_ARG1);
411 LockTemp(rMIPS_ARG2);
412 LockTemp(rMIPS_ARG3);
413 }
414
415 /* To be used when explicitly managing register use */
FreeCallTemps()416 void MipsMir2Lir::FreeCallTemps() {
417 FreeTemp(rMIPS_ARG0);
418 FreeTemp(rMIPS_ARG1);
419 FreeTemp(rMIPS_ARG2);
420 FreeTemp(rMIPS_ARG3);
421 }
422
GenMemBarrier(MemBarrierKind barrier_kind)423 void MipsMir2Lir::GenMemBarrier(MemBarrierKind barrier_kind) {
424 #if ANDROID_SMP != 0
425 NewLIR1(kMipsSync, 0 /* Only stype currently supported */);
426 #endif
427 }
428
429 /*
430 * Alloc a pair of core registers, or a double. Low reg in low byte,
431 * high reg in next byte.
432 */
AllocTypedTempPair(bool fp_hint,int reg_class)433 int MipsMir2Lir::AllocTypedTempPair(bool fp_hint,
434 int reg_class) {
435 int high_reg;
436 int low_reg;
437 int res = 0;
438
439 if (((reg_class == kAnyReg) && fp_hint) || (reg_class == kFPReg)) {
440 low_reg = AllocTempDouble();
441 high_reg = low_reg + 1;
442 res = (low_reg & 0xff) | ((high_reg & 0xff) << 8);
443 return res;
444 }
445
446 low_reg = AllocTemp();
447 high_reg = AllocTemp();
448 res = (low_reg & 0xff) | ((high_reg & 0xff) << 8);
449 return res;
450 }
451
AllocTypedTemp(bool fp_hint,int reg_class)452 int MipsMir2Lir::AllocTypedTemp(bool fp_hint, int reg_class) {
453 if (((reg_class == kAnyReg) && fp_hint) || (reg_class == kFPReg)) {
454 return AllocTempFloat();
455 }
456 return AllocTemp();
457 }
458
CompilerInitializeRegAlloc()459 void MipsMir2Lir::CompilerInitializeRegAlloc() {
460 int num_regs = sizeof(core_regs)/sizeof(*core_regs);
461 int num_reserved = sizeof(ReservedRegs)/sizeof(*ReservedRegs);
462 int num_temps = sizeof(core_temps)/sizeof(*core_temps);
463 int num_fp_regs = sizeof(FpRegs)/sizeof(*FpRegs);
464 int num_fp_temps = sizeof(fp_temps)/sizeof(*fp_temps);
465 reg_pool_ = static_cast<RegisterPool*>(arena_->Alloc(sizeof(*reg_pool_),
466 ArenaAllocator::kAllocRegAlloc));
467 reg_pool_->num_core_regs = num_regs;
468 reg_pool_->core_regs = static_cast<RegisterInfo*>
469 (arena_->Alloc(num_regs * sizeof(*reg_pool_->core_regs), ArenaAllocator::kAllocRegAlloc));
470 reg_pool_->num_fp_regs = num_fp_regs;
471 reg_pool_->FPRegs = static_cast<RegisterInfo*>
472 (arena_->Alloc(num_fp_regs * sizeof(*reg_pool_->FPRegs), ArenaAllocator::kAllocRegAlloc));
473 CompilerInitPool(reg_pool_->core_regs, core_regs, reg_pool_->num_core_regs);
474 CompilerInitPool(reg_pool_->FPRegs, FpRegs, reg_pool_->num_fp_regs);
475 // Keep special registers from being allocated
476 for (int i = 0; i < num_reserved; i++) {
477 if (NO_SUSPEND && (ReservedRegs[i] == rMIPS_SUSPEND)) {
478 // To measure cost of suspend check
479 continue;
480 }
481 MarkInUse(ReservedRegs[i]);
482 }
483 // Mark temp regs - all others not in use can be used for promotion
484 for (int i = 0; i < num_temps; i++) {
485 MarkTemp(core_temps[i]);
486 }
487 for (int i = 0; i < num_fp_temps; i++) {
488 MarkTemp(fp_temps[i]);
489 }
490 }
491
FreeRegLocTemps(RegLocation rl_keep,RegLocation rl_free)492 void MipsMir2Lir::FreeRegLocTemps(RegLocation rl_keep, RegLocation rl_free) {
493 if ((rl_free.low_reg != rl_keep.low_reg) && (rl_free.low_reg != rl_keep.high_reg) &&
494 (rl_free.high_reg != rl_keep.low_reg) && (rl_free.high_reg != rl_keep.high_reg)) {
495 // No overlap, free both
496 FreeTemp(rl_free.low_reg);
497 FreeTemp(rl_free.high_reg);
498 }
499 }
500 /*
501 * In the Arm code a it is typical to use the link register
502 * to hold the target address. However, for Mips we must
503 * ensure that all branch instructions can be restarted if
504 * there is a trap in the shadow. Allocate a temp register.
505 */
LoadHelper(ThreadOffset offset)506 int MipsMir2Lir::LoadHelper(ThreadOffset offset) {
507 LoadWordDisp(rMIPS_SELF, offset.Int32Value(), r_T9);
508 return r_T9;
509 }
510
SpillCoreRegs()511 void MipsMir2Lir::SpillCoreRegs() {
512 if (num_core_spills_ == 0) {
513 return;
514 }
515 uint32_t mask = core_spill_mask_;
516 int offset = num_core_spills_ * 4;
517 OpRegImm(kOpSub, rMIPS_SP, offset);
518 for (int reg = 0; mask; mask >>= 1, reg++) {
519 if (mask & 0x1) {
520 offset -= 4;
521 StoreWordDisp(rMIPS_SP, offset, reg);
522 }
523 }
524 }
525
UnSpillCoreRegs()526 void MipsMir2Lir::UnSpillCoreRegs() {
527 if (num_core_spills_ == 0) {
528 return;
529 }
530 uint32_t mask = core_spill_mask_;
531 int offset = frame_size_;
532 for (int reg = 0; mask; mask >>= 1, reg++) {
533 if (mask & 0x1) {
534 offset -= 4;
535 LoadWordDisp(rMIPS_SP, offset, reg);
536 }
537 }
538 OpRegImm(kOpAdd, rMIPS_SP, frame_size_);
539 }
540
IsUnconditionalBranch(LIR * lir)541 bool MipsMir2Lir::IsUnconditionalBranch(LIR* lir) {
542 return (lir->opcode == kMipsB);
543 }
544
MipsMir2Lir(CompilationUnit * cu,MIRGraph * mir_graph,ArenaAllocator * arena)545 MipsMir2Lir::MipsMir2Lir(CompilationUnit* cu, MIRGraph* mir_graph, ArenaAllocator* arena)
546 : Mir2Lir(cu, mir_graph, arena) {
547 for (int i = 0; i < kMipsLast; i++) {
548 if (MipsMir2Lir::EncodingMap[i].opcode != i) {
549 LOG(FATAL) << "Encoding order for " << MipsMir2Lir::EncodingMap[i].name
550 << " is wrong: expecting " << i << ", seeing "
551 << static_cast<int>(MipsMir2Lir::EncodingMap[i].opcode);
552 }
553 }
554 }
555
MipsCodeGenerator(CompilationUnit * const cu,MIRGraph * const mir_graph,ArenaAllocator * const arena)556 Mir2Lir* MipsCodeGenerator(CompilationUnit* const cu, MIRGraph* const mir_graph,
557 ArenaAllocator* const arena) {
558 return new MipsMir2Lir(cu, mir_graph, arena);
559 }
560
GetTargetInstFlags(int opcode)561 uint64_t MipsMir2Lir::GetTargetInstFlags(int opcode) {
562 return MipsMir2Lir::EncodingMap[opcode].flags;
563 }
564
GetTargetInstName(int opcode)565 const char* MipsMir2Lir::GetTargetInstName(int opcode) {
566 return MipsMir2Lir::EncodingMap[opcode].name;
567 }
568
GetTargetInstFmt(int opcode)569 const char* MipsMir2Lir::GetTargetInstFmt(int opcode) {
570 return MipsMir2Lir::EncodingMap[opcode].fmt;
571 }
572
573 } // namespace art
574