blob: 44cae0fc28c75fe09329925f0ad35c91113b74d7 [file] [log] [blame]
buzbee67bf8852011-08-17 17:51:35 -07001/*
2 * Copyright (C) 2011 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/*
18 * This file contains codegen for the Thumb2 ISA and is intended to be
19 * includes by:
20 *
21 * Codegen-$(TARGET_ARCH_VARIANT).c
22 *
23 */
24
Logan Chien4dd96f52012-02-29 01:26:58 +080025#include "oat_compilation_unit.h"
Ian Rogers57b86d42012-03-27 16:05:41 -070026#include "oat/runtime/oat_support_entrypoints.h"
Logan Chien4dd96f52012-02-29 01:26:58 +080027
Elliott Hughes11d1b0c2012-01-23 16:57:47 -080028namespace art {
29
buzbeee62076c2012-03-21 14:26:16 -070030
31/* Return the position of an ssa name within the argument list */
32int inPosition(CompilationUnit* cUnit, int sReg)
buzbee16da88c2012-03-20 10:38:17 -070033{
buzbeee62076c2012-03-21 14:26:16 -070034 int vReg = SRegToVReg(cUnit, sReg);
35 return vReg - cUnit->numRegs;
36}
37
38/*
39 * Describe an argument. If it's already in an arg register, just leave it
40 * there. NOTE: all live arg registers must be locked prior to this call
41 * to avoid having them allocated as a temp by downstream utilities.
42 */
43RegLocation argLoc(CompilationUnit* cUnit, RegLocation loc)
44{
45 int argNum = inPosition(cUnit, loc.sRegLow);
buzbee16da88c2012-03-20 10:38:17 -070046 if (loc.wide) {
buzbeee62076c2012-03-21 14:26:16 -070047 if (argNum == 2) {
48 // Bad case - half in register, half in frame. Just punt
49 loc.location = kLocInvalid;
50 } else if (argNum < 2) {
51 loc.lowReg = rARG1 + argNum;
52 loc.highReg = loc.lowReg + 1;
53 loc.location = kLocPhysReg;
54 } else {
55 loc.location = kLocDalvikFrame;
56 }
buzbee16da88c2012-03-20 10:38:17 -070057 } else {
buzbeee62076c2012-03-21 14:26:16 -070058 if (argNum < 3) {
59 loc.lowReg = rARG1 + argNum;
60 loc.location = kLocPhysReg;
61 } else {
62 loc.location = kLocDalvikFrame;
63 }
buzbee16da88c2012-03-20 10:38:17 -070064 }
65 return loc;
66}
67
buzbeee62076c2012-03-21 14:26:16 -070068/*
69 * Load an argument. If already in a register, just return. If in
70 * the frame, we can't use the normal loadValue() because it assumed
71 * a proper frame - and we're frameless.
72 */
73RegLocation loadArg(CompilationUnit* cUnit, RegLocation loc)
74{
75 if (loc.location == kLocDalvikFrame) {
76 int start = (inPosition(cUnit, loc.sRegLow) + 1) * sizeof(uint32_t);
77 loc.lowReg = oatAllocTemp(cUnit);
78 loadWordDisp(cUnit, rSP, start, loc.lowReg);
79 if (loc.wide) {
80 loc.highReg = oatAllocTemp(cUnit);
81 loadWordDisp(cUnit, rSP, start + sizeof(uint32_t), loc.highReg);
82 }
83 loc.location = kLocPhysReg;
84 }
85 return loc;
86}
87
88/* Lock any referenced arguments that arrive in registers */
89void lockLiveArgs(CompilationUnit* cUnit, MIR* mir)
90{
91 int firstIn = cUnit->numRegs;
92 const int numArgRegs = 3; // TODO: generalize & move to RegUtil.cc
93 for (int i = 0; i < mir->ssaRep->numUses; i++) {
94 int vReg = SRegToVReg(cUnit, mir->ssaRep->uses[i]);
95 int inPosition = vReg - firstIn;
96 if (inPosition < numArgRegs) {
97 oatLockTemp(cUnit, rARG1 + inPosition);
98 }
99 }
100}
101
buzbee16da88c2012-03-20 10:38:17 -0700102/* Find the next MIR, which may be in a following basic block */
103MIR* getNextMir(CompilationUnit* cUnit, BasicBlock** pBb, MIR* mir)
104{
105 BasicBlock* bb = *pBb;
106 MIR* origMir = mir;
107 while (bb != NULL) {
108 if (mir != NULL) {
109 mir = mir->next;
110 }
111 if (mir != NULL) {
112 return mir;
113 } else {
114 bb = bb->fallThrough;
115 *pBb = bb;
116 if (bb) {
117 mir = bb->firstMIRInsn;
118 if (mir != NULL) {
119 return mir;
120 }
121 }
122 }
123 }
124 return origMir;
125}
126
127/* Used for the "printMe" listing */
128void genPrintLabel(CompilationUnit *cUnit, MIR* mir)
129{
130 LIR* boundaryLIR;
131 /* Mark the beginning of a Dalvik instruction for line tracking */
132 char* instStr = cUnit->printMe ?
133 oatGetDalvikDisassembly(cUnit, mir->dalvikInsn, "") : NULL;
134 boundaryLIR = newLIR1(cUnit, kPseudoDalvikByteCodeBoundary,
135 (intptr_t) instStr);
136 cUnit->boundaryMap.insert(std::make_pair(mir->offset,
137 (LIR*)boundaryLIR));
138 /* Don't generate the SSA annotation unless verbose mode is on */
139 if (cUnit->printMe && mir->ssaRep) {
140 char* ssaString = oatGetSSAString(cUnit, mir->ssaRep);
141 newLIR1(cUnit, kPseudoSSARep, (int) ssaString);
142 }
143}
144
145MIR* specialIGet(CompilationUnit* cUnit, BasicBlock** bb, MIR* mir,
146 OpSize size, bool longOrDouble, bool isObject)
147{
148 int fieldOffset;
149 bool isVolatile;
150 uint32_t fieldIdx = mir->dalvikInsn.vC;
151 bool fastPath = fastInstance(cUnit, fieldIdx, fieldOffset, isVolatile,
152 false);
buzbee97df07f2012-03-27 16:13:20 -0700153 if (!fastPath || !(mir->optimizationFlags & MIR_IGNORE_NULL_CHECK)) {
buzbee16da88c2012-03-20 10:38:17 -0700154 return NULL;
155 }
buzbee16da88c2012-03-20 10:38:17 -0700156 RegLocation rlObj = oatGetSrc(cUnit, mir, 0);
buzbeee62076c2012-03-21 14:26:16 -0700157 lockLiveArgs(cUnit, mir);
158 rlObj = argLoc(cUnit, rlObj);
buzbee16da88c2012-03-20 10:38:17 -0700159 RegLocation rlDest;
160 if (longOrDouble) {
161 rlDest = oatGetReturnWide(cUnit, false);
162 } else {
163 rlDest = oatGetReturn(cUnit, false);
164 }
buzbeee62076c2012-03-21 14:26:16 -0700165 // Point of no return - no aborts after this
buzbeee62076c2012-03-21 14:26:16 -0700166 genPrintLabel(cUnit, mir);
167 rlObj = loadArg(cUnit, rlObj);
buzbee16da88c2012-03-20 10:38:17 -0700168 genIGet(cUnit, mir, size, rlDest, rlObj, longOrDouble, isObject);
169 return getNextMir(cUnit, bb, mir);
170}
171
172MIR* specialIPut(CompilationUnit* cUnit, BasicBlock** bb, MIR* mir,
173 OpSize size, bool longOrDouble, bool isObject)
174{
175 int fieldOffset;
176 bool isVolatile;
177 uint32_t fieldIdx = mir->dalvikInsn.vC;
178 bool fastPath = fastInstance(cUnit, fieldIdx, fieldOffset, isVolatile,
179 false);
buzbee97df07f2012-03-27 16:13:20 -0700180 if (!fastPath || !(mir->optimizationFlags & MIR_IGNORE_NULL_CHECK)) {
buzbee16da88c2012-03-20 10:38:17 -0700181 return NULL;
182 }
buzbee16da88c2012-03-20 10:38:17 -0700183 RegLocation rlSrc;
184 RegLocation rlObj;
buzbeee62076c2012-03-21 14:26:16 -0700185 lockLiveArgs(cUnit, mir);
buzbee16da88c2012-03-20 10:38:17 -0700186 if (longOrDouble) {
187 rlSrc = oatGetSrcWide(cUnit, mir, 0, 1);
188 rlObj = oatGetSrc(cUnit, mir, 2);
buzbee16da88c2012-03-20 10:38:17 -0700189 } else {
190 rlSrc = oatGetSrc(cUnit, mir, 0);
191 rlObj = oatGetSrc(cUnit, mir, 1);
buzbee16da88c2012-03-20 10:38:17 -0700192 }
buzbeee62076c2012-03-21 14:26:16 -0700193 rlSrc = argLoc(cUnit, rlSrc);
194 rlObj = argLoc(cUnit, rlObj);
buzbee97df07f2012-03-27 16:13:20 -0700195 // Reject if source is split across registers & frame
196 if (rlObj.location == kLocInvalid) {
buzbeee62076c2012-03-21 14:26:16 -0700197 oatResetRegPool(cUnit);
198 return NULL;
199 }
200 // Point of no return - no aborts after this
buzbeee62076c2012-03-21 14:26:16 -0700201 genPrintLabel(cUnit, mir);
202 rlObj = loadArg(cUnit, rlObj);
203 rlSrc = loadArg(cUnit, rlSrc);
buzbee16da88c2012-03-20 10:38:17 -0700204 genIPut(cUnit, mir, size, rlSrc, rlObj, longOrDouble, isObject);
205 return getNextMir(cUnit, bb, mir);
206}
207
buzbeee62076c2012-03-21 14:26:16 -0700208MIR* specialIdentity(CompilationUnit* cUnit, MIR* mir)
209{
210 RegLocation rlSrc;
211 RegLocation rlDest;
212 bool wide = (mir->ssaRep->numUses == 2);
213 if (wide) {
214 rlSrc = oatGetSrcWide(cUnit, mir, 0, 1);
215 rlDest = oatGetReturnWide(cUnit, false);
216 } else {
217 rlSrc = oatGetSrc(cUnit, mir, 0);
218 rlDest = oatGetReturn(cUnit, false);
219 }
220 lockLiveArgs(cUnit, mir);
221 rlSrc = argLoc(cUnit, rlSrc);
222 if (rlSrc.location == kLocInvalid) {
223 oatResetRegPool(cUnit);
224 return NULL;
225 }
226 // Point of no return - no aborts after this
227 genPrintLabel(cUnit, mir);
228 rlSrc = loadArg(cUnit, rlSrc);
229 if (wide) {
230 storeValueWide(cUnit, rlDest, rlSrc);
231 } else {
232 storeValue(cUnit, rlDest, rlSrc);
233 }
234 return mir;
235}
236
buzbee16da88c2012-03-20 10:38:17 -0700237/*
238 * Special-case code genration for simple non-throwing leaf methods.
239 */
240void genSpecialCase(CompilationUnit* cUnit, BasicBlock* bb, MIR* mir,
241 SpecialCaseHandler specialCase)
242{
243 cUnit->currentDalvikOffset = mir->offset;
244 MIR* nextMir = NULL;
Elliott Hughesb25c3f62012-03-26 16:35:06 -0700245 switch (specialCase) {
buzbee16da88c2012-03-20 10:38:17 -0700246 case kNullMethod:
247 DCHECK(mir->dalvikInsn.opcode == Instruction::RETURN_VOID);
248 nextMir = mir;
249 break;
250 case kConstFunction:
251 genPrintLabel(cUnit, mir);
252 loadConstant(cUnit, rRET0, mir->dalvikInsn.vB);
253 nextMir = getNextMir(cUnit, &bb, mir);
254 break;
255 case kIGet:
256 nextMir = specialIGet(cUnit, &bb, mir, kWord, false, false);
buzbeee62076c2012-03-21 14:26:16 -0700257 break;
buzbee16da88c2012-03-20 10:38:17 -0700258 case kIGetBoolean:
259 case kIGetByte:
260 nextMir = specialIGet(cUnit, &bb, mir, kUnsignedByte, false, false);
buzbeee62076c2012-03-21 14:26:16 -0700261 break;
buzbee16da88c2012-03-20 10:38:17 -0700262 case kIGetObject:
263 nextMir = specialIGet(cUnit, &bb, mir, kWord, false, true);
buzbeee62076c2012-03-21 14:26:16 -0700264 break;
buzbee16da88c2012-03-20 10:38:17 -0700265 case kIGetChar:
266 nextMir = specialIGet(cUnit, &bb, mir, kUnsignedHalf, false, false);
buzbeee62076c2012-03-21 14:26:16 -0700267 break;
buzbee16da88c2012-03-20 10:38:17 -0700268 case kIGetShort:
269 nextMir = specialIGet(cUnit, &bb, mir, kSignedHalf, false, false);
buzbeee62076c2012-03-21 14:26:16 -0700270 break;
buzbee16da88c2012-03-20 10:38:17 -0700271 case kIGetWide:
272 nextMir = specialIGet(cUnit, &bb, mir, kLong, true, false);
buzbeee62076c2012-03-21 14:26:16 -0700273 break;
buzbee16da88c2012-03-20 10:38:17 -0700274 case kIPut:
275 nextMir = specialIPut(cUnit, &bb, mir, kWord, false, false);
buzbeee62076c2012-03-21 14:26:16 -0700276 break;
buzbee16da88c2012-03-20 10:38:17 -0700277 case kIPutBoolean:
278 case kIPutByte:
279 nextMir = specialIPut(cUnit, &bb, mir, kUnsignedByte, false, false);
buzbeee62076c2012-03-21 14:26:16 -0700280 break;
buzbee16da88c2012-03-20 10:38:17 -0700281 case kIPutObject:
282 nextMir = specialIPut(cUnit, &bb, mir, kWord, false, true);
buzbeee62076c2012-03-21 14:26:16 -0700283 break;
buzbee16da88c2012-03-20 10:38:17 -0700284 case kIPutChar:
285 nextMir = specialIPut(cUnit, &bb, mir, kUnsignedHalf, false, false);
buzbeee62076c2012-03-21 14:26:16 -0700286 break;
buzbee16da88c2012-03-20 10:38:17 -0700287 case kIPutShort:
288 nextMir = specialIPut(cUnit, &bb, mir, kSignedHalf, false, false);
buzbeee62076c2012-03-21 14:26:16 -0700289 break;
buzbee16da88c2012-03-20 10:38:17 -0700290 case kIPutWide:
291 nextMir = specialIPut(cUnit, &bb, mir, kLong, true, false);
buzbeee62076c2012-03-21 14:26:16 -0700292 break;
293 case kIdentity:
294 nextMir = specialIdentity(cUnit, mir);
295 break;
buzbee16da88c2012-03-20 10:38:17 -0700296 default:
297 return;
298 }
299 if (nextMir != NULL) {
300 cUnit->currentDalvikOffset = nextMir->offset;
buzbeee62076c2012-03-21 14:26:16 -0700301 if (specialCase != kIdentity) {
302 genPrintLabel(cUnit, nextMir);
303 }
buzbee16da88c2012-03-20 10:38:17 -0700304 newLIR1(cUnit, kThumbBx, rLR);
305 cUnit->coreSpillMask = 0;
306 cUnit->numCoreSpills = 0;
307 cUnit->fpSpillMask = 0;
308 cUnit->numFPSpills = 0;
309 cUnit->frameSize = 0;
310 cUnit->coreVmapTable.clear();
311 cUnit->fpVmapTable.clear();
312 }
313}
buzbee67bf8852011-08-17 17:51:35 -0700314
315/*
316 * Generate a Thumb2 IT instruction, which can nullify up to
317 * four subsequent instructions based on a condition and its
318 * inverse. The condition applies to the first instruction, which
319 * is executed if the condition is met. The string "guide" consists
320 * of 0 to 3 chars, and applies to the 2nd through 4th instruction.
321 * A "T" means the instruction is executed if the condition is
322 * met, and an "E" means the instruction is executed if the condition
323 * is not met.
324 */
buzbee82488f52012-03-02 08:20:26 -0800325LIR* opIT(CompilationUnit* cUnit, ArmConditionCode code, const char* guide)
buzbee67bf8852011-08-17 17:51:35 -0700326{
327 int mask;
328 int condBit = code & 1;
329 int altBit = condBit ^ 1;
330 int mask3 = 0;
331 int mask2 = 0;
332 int mask1 = 0;
333
334 //Note: case fallthroughs intentional
Elliott Hughesb25c3f62012-03-26 16:35:06 -0700335 switch (strlen(guide)) {
buzbee67bf8852011-08-17 17:51:35 -0700336 case 3:
337 mask1 = (guide[2] == 'T') ? condBit : altBit;
338 case 2:
339 mask2 = (guide[1] == 'T') ? condBit : altBit;
340 case 1:
341 mask3 = (guide[0] == 'T') ? condBit : altBit;
342 break;
343 case 0:
344 break;
345 default:
buzbee82488f52012-03-02 08:20:26 -0800346 LOG(FATAL) << "OAT: bad case in opIT";
buzbee67bf8852011-08-17 17:51:35 -0700347 }
348 mask = (mask3 << 3) | (mask2 << 2) | (mask1 << 1) |
349 (1 << (3 - strlen(guide)));
350 return newLIR2(cUnit, kThumb2It, code, mask);
351}
352
353/*
buzbee67bf8852011-08-17 17:51:35 -0700354 * The sparse table in the literal pool is an array of <key,displacement>
355 * pairs. For each set, we'll load them as a pair using ldmia.
356 * This means that the register number of the temp we use for the key
357 * must be lower than the reg for the displacement.
358 *
359 * The test loop will look something like:
360 *
361 * adr rBase, <table>
362 * ldr rVal, [rSP, vRegOff]
363 * mov rIdx, #tableSize
364 * lp:
365 * ldmia rBase!, {rKey, rDisp}
366 * sub rIdx, #1
367 * cmp rVal, rKey
368 * ifeq
369 * add rPC, rDisp ; This is the branch from which we compute displacement
370 * cbnz rIdx, lp
371 */
Ian Rogers55bd45f2012-04-04 17:31:20 -0700372void genSparseSwitch(CompilationUnit* cUnit, MIR* mir, RegLocation rlSrc,
373 LIR* labelList)
buzbee67bf8852011-08-17 17:51:35 -0700374{
375 const u2* table = cUnit->insns + mir->offset + mir->dalvikInsn.vB;
376 if (cUnit->printMe) {
377 dumpSparseSwitchTable(table);
378 }
379 // Add the table to the list - we'll process it later
buzbeeba938cb2012-02-03 14:47:55 -0800380 SwitchTable *tabRec = (SwitchTable *)oatNew(cUnit, sizeof(SwitchTable),
buzbee5abfa3e2012-01-31 17:01:43 -0800381 true, kAllocData);
buzbee67bf8852011-08-17 17:51:35 -0700382 tabRec->table = table;
383 tabRec->vaddr = mir->offset;
384 int size = table[1];
buzbee31a4a6f2012-02-28 15:36:15 -0800385 tabRec->targets = (LIR* *)oatNew(cUnit, size * sizeof(LIR*), true,
386 kAllocLIR);
buzbeeba938cb2012-02-03 14:47:55 -0800387 oatInsertGrowableList(cUnit, &cUnit->switchTables, (intptr_t)tabRec);
buzbee67bf8852011-08-17 17:51:35 -0700388
389 // Get the switch value
390 rlSrc = loadValue(cUnit, rlSrc, kCoreReg);
391 int rBase = oatAllocTemp(cUnit);
392 /* Allocate key and disp temps */
393 int rKey = oatAllocTemp(cUnit);
394 int rDisp = oatAllocTemp(cUnit);
395 // Make sure rKey's register number is less than rDisp's number for ldmia
396 if (rKey > rDisp) {
397 int tmp = rDisp;
398 rDisp = rKey;
399 rKey = tmp;
400 }
401 // Materialize a pointer to the switch table
buzbee03fa2632011-09-20 17:10:57 -0700402 newLIR3(cUnit, kThumb2Adr, rBase, 0, (intptr_t)tabRec);
buzbee67bf8852011-08-17 17:51:35 -0700403 // Set up rIdx
404 int rIdx = oatAllocTemp(cUnit);
405 loadConstant(cUnit, rIdx, size);
406 // Establish loop branch target
buzbee31a4a6f2012-02-28 15:36:15 -0800407 LIR* target = newLIR0(cUnit, kPseudoTargetLabel);
buzbee67bf8852011-08-17 17:51:35 -0700408 // Load next key/disp
409 newLIR2(cUnit, kThumb2LdmiaWB, rBase, (1 << rKey) | (1 << rDisp));
410 opRegReg(cUnit, kOpCmp, rKey, rlSrc.lowReg);
411 // Go if match. NOTE: No instruction set switch here - must stay Thumb2
buzbee82488f52012-03-02 08:20:26 -0800412 opIT(cUnit, kArmCondEq, "");
buzbee31a4a6f2012-02-28 15:36:15 -0800413 LIR* switchBranch = newLIR1(cUnit, kThumb2AddPCR, rDisp);
buzbeec5159d52012-03-03 11:48:39 -0800414 tabRec->anchor = switchBranch;
buzbee67bf8852011-08-17 17:51:35 -0700415 // Needs to use setflags encoding here
416 newLIR3(cUnit, kThumb2SubsRRI12, rIdx, rIdx, 1);
buzbee82488f52012-03-02 08:20:26 -0800417 opCondBranch(cUnit, kCondNe, target);
buzbee67bf8852011-08-17 17:51:35 -0700418}
419
420
buzbee31a4a6f2012-02-28 15:36:15 -0800421void genPackedSwitch(CompilationUnit* cUnit, MIR* mir, RegLocation rlSrc)
buzbee67bf8852011-08-17 17:51:35 -0700422{
423 const u2* table = cUnit->insns + mir->offset + mir->dalvikInsn.vB;
424 if (cUnit->printMe) {
425 dumpPackedSwitchTable(table);
426 }
427 // Add the table to the list - we'll process it later
buzbeeba938cb2012-02-03 14:47:55 -0800428 SwitchTable *tabRec = (SwitchTable *)oatNew(cUnit, sizeof(SwitchTable),
buzbee5abfa3e2012-01-31 17:01:43 -0800429 true, kAllocData);
buzbee67bf8852011-08-17 17:51:35 -0700430 tabRec->table = table;
431 tabRec->vaddr = mir->offset;
432 int size = table[1];
buzbee31a4a6f2012-02-28 15:36:15 -0800433 tabRec->targets = (LIR* *)oatNew(cUnit, size * sizeof(LIR*), true,
buzbee5abfa3e2012-01-31 17:01:43 -0800434 kAllocLIR);
buzbeeba938cb2012-02-03 14:47:55 -0800435 oatInsertGrowableList(cUnit, &cUnit->switchTables, (intptr_t)tabRec);
buzbee67bf8852011-08-17 17:51:35 -0700436
437 // Get the switch value
438 rlSrc = loadValue(cUnit, rlSrc, kCoreReg);
439 int tableBase = oatAllocTemp(cUnit);
440 // Materialize a pointer to the switch table
buzbee03fa2632011-09-20 17:10:57 -0700441 newLIR3(cUnit, kThumb2Adr, tableBase, 0, (intptr_t)tabRec);
buzbee67bf8852011-08-17 17:51:35 -0700442 int lowKey = s4FromSwitchData(&table[2]);
443 int keyReg;
444 // Remove the bias, if necessary
445 if (lowKey == 0) {
446 keyReg = rlSrc.lowReg;
447 } else {
448 keyReg = oatAllocTemp(cUnit);
449 opRegRegImm(cUnit, kOpSub, keyReg, rlSrc.lowReg, lowKey);
450 }
451 // Bounds check - if < 0 or >= size continue following switch
452 opRegImm(cUnit, kOpCmp, keyReg, size-1);
buzbee82488f52012-03-02 08:20:26 -0800453 LIR* branchOver = opCondBranch(cUnit, kCondHi, NULL);
buzbee67bf8852011-08-17 17:51:35 -0700454
455 // Load the displacement from the switch table
456 int dispReg = oatAllocTemp(cUnit);
457 loadBaseIndexed(cUnit, tableBase, keyReg, dispReg, 2, kWord);
458
459 // ..and go! NOTE: No instruction set switch here - must stay Thumb2
buzbee31a4a6f2012-02-28 15:36:15 -0800460 LIR* switchBranch = newLIR1(cUnit, kThumb2AddPCR, dispReg);
buzbeec5159d52012-03-03 11:48:39 -0800461 tabRec->anchor = switchBranch;
buzbee67bf8852011-08-17 17:51:35 -0700462
463 /* branchOver target here */
buzbee31a4a6f2012-02-28 15:36:15 -0800464 LIR* target = newLIR0(cUnit, kPseudoTargetLabel);
buzbee31a4a6f2012-02-28 15:36:15 -0800465 branchOver->target = (LIR*)target;
buzbee67bf8852011-08-17 17:51:35 -0700466}
467
468/*
469 * Array data table format:
470 * ushort ident = 0x0300 magic value
471 * ushort width width of each element in the table
472 * uint size number of elements in the table
473 * ubyte data[size*width] table of data values (may contain a single-byte
474 * padding at the end)
475 *
476 * Total size is 4+(width * size + 1)/2 16-bit code units.
477 */
buzbee31a4a6f2012-02-28 15:36:15 -0800478void genFillArrayData(CompilationUnit* cUnit, MIR* mir, RegLocation rlSrc)
buzbee67bf8852011-08-17 17:51:35 -0700479{
480 const u2* table = cUnit->insns + mir->offset + mir->dalvikInsn.vB;
481 // Add the table to the list - we'll process it later
482 FillArrayData *tabRec = (FillArrayData *)
buzbeeba938cb2012-02-03 14:47:55 -0800483 oatNew(cUnit, sizeof(FillArrayData), true, kAllocData);
buzbee67bf8852011-08-17 17:51:35 -0700484 tabRec->table = table;
485 tabRec->vaddr = mir->offset;
486 u2 width = tabRec->table[1];
487 u4 size = tabRec->table[2] | (((u4)tabRec->table[3]) << 16);
488 tabRec->size = (size * width) + 8;
489
buzbeeba938cb2012-02-03 14:47:55 -0800490 oatInsertGrowableList(cUnit, &cUnit->fillArrayData, (intptr_t)tabRec);
buzbee67bf8852011-08-17 17:51:35 -0700491
492 // Making a call - use explicit registers
493 oatFlushAllRegs(cUnit); /* Everything to home location */
494 loadValueDirectFixed(cUnit, rlSrc, r0);
495 loadWordDisp(cUnit, rSELF,
Ian Rogers57b86d42012-03-27 16:05:41 -0700496 ENTRYPOINT_OFFSET(pHandleFillArrayDataFromCode), rLR);
buzbeee6d61962011-08-27 11:58:19 -0700497 // Materialize a pointer to the fill data image
buzbee03fa2632011-09-20 17:10:57 -0700498 newLIR3(cUnit, kThumb2Adr, r1, 0, (intptr_t)tabRec);
Ian Rogersab2b55d2012-03-18 00:06:11 -0700499 oatClobberCalleeSave(cUnit);
500 opReg(cUnit, kOpBlx, rLR);
buzbee67bf8852011-08-17 17:51:35 -0700501}
502
buzbee31a4a6f2012-02-28 15:36:15 -0800503void genNegFloat(CompilationUnit* cUnit, RegLocation rlDest, RegLocation rlSrc)
buzbee67bf8852011-08-17 17:51:35 -0700504{
505 RegLocation rlResult;
506 rlSrc = loadValue(cUnit, rlSrc, kFPReg);
507 rlResult = oatEvalLoc(cUnit, rlDest, kFPReg, true);
508 newLIR2(cUnit, kThumb2Vnegs, rlResult.lowReg, rlSrc.lowReg);
509 storeValue(cUnit, rlDest, rlResult);
510}
511
buzbee31a4a6f2012-02-28 15:36:15 -0800512void genNegDouble(CompilationUnit* cUnit, RegLocation rlDest, RegLocation rlSrc)
buzbee67bf8852011-08-17 17:51:35 -0700513{
514 RegLocation rlResult;
515 rlSrc = loadValueWide(cUnit, rlSrc, kFPReg);
516 rlResult = oatEvalLoc(cUnit, rlDest, kFPReg, true);
517 newLIR2(cUnit, kThumb2Vnegd, S2D(rlResult.lowReg, rlResult.highReg),
518 S2D(rlSrc.lowReg, rlSrc.highReg));
519 storeValueWide(cUnit, rlDest, rlResult);
520}
521
buzbee67bf8852011-08-17 17:51:35 -0700522/*
523 * Handle simple case (thin lock) inline. If it's complicated, bail
524 * out to the heavyweight lock/unlock routines. We'll use dedicated
525 * registers here in order to be in the right position in case we
526 * to bail to dvm[Lock/Unlock]Object(self, object)
527 *
528 * r0 -> self pointer [arg0 for dvm[Lock/Unlock]Object
529 * r1 -> object [arg1 for dvm[Lock/Unlock]Object
530 * r2 -> intial contents of object->lock, later result of strex
531 * r3 -> self->threadId
532 * r12 -> allow to be used by utilities as general temp
533 *
534 * The result of the strex is 0 if we acquire the lock.
535 *
536 * See comments in Sync.c for the layout of the lock word.
537 * Of particular interest to this code is the test for the
538 * simple case - which we handle inline. For monitor enter, the
539 * simple case is thin lock, held by no-one. For monitor exit,
540 * the simple case is thin lock, held by the unlocking thread with
541 * a recurse count of 0.
542 *
543 * A minor complication is that there is a field in the lock word
544 * unrelated to locking: the hash state. This field must be ignored, but
545 * preserved.
546 *
547 */
buzbee31a4a6f2012-02-28 15:36:15 -0800548void genMonitorEnter(CompilationUnit* cUnit, MIR* mir, RegLocation rlSrc)
buzbee67bf8852011-08-17 17:51:35 -0700549{
buzbee67bf8852011-08-17 17:51:35 -0700550 oatFlushAllRegs(cUnit);
Elliott Hughes5f791332011-09-15 17:45:30 -0700551 DCHECK_EQ(LW_SHAPE_THIN, 0);
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700552 loadValueDirectFixed(cUnit, rlSrc, r0); // Get obj
buzbee2e748f32011-08-29 21:02:19 -0700553 oatLockCallTemps(cUnit); // Prepare for explicit register usage
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700554 genNullCheck(cUnit, rlSrc.sRegLow, r0, mir);
555 loadWordDisp(cUnit, rSELF, Thread::ThinLockIdOffset().Int32Value(), r2);
556 newLIR3(cUnit, kThumb2Ldrex, r1, r0,
Ian Rogers0cfe1fb2011-08-26 03:29:44 -0700557 Object::MonitorOffset().Int32Value() >> 2); // Get object->lock
buzbeec143c552011-08-20 17:38:58 -0700558 // Align owner
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700559 opRegImm(cUnit, kOpLsl, r2, LW_LOCK_OWNER_SHIFT);
buzbee67bf8852011-08-17 17:51:35 -0700560 // Is lock unheld on lock or held by us (==threadId) on unlock?
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700561 newLIR4(cUnit, kThumb2Bfi, r2, r1, 0, LW_LOCK_OWNER_SHIFT - 1);
562 newLIR3(cUnit, kThumb2Bfc, r1, LW_HASH_STATE_SHIFT, LW_LOCK_OWNER_SHIFT - 1);
buzbee05eba362012-03-10 20:11:27 -0800563 opRegImm(cUnit, kOpCmp, r1, 0);
564 opIT(cUnit, kArmCondEq, "");
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700565 newLIR4(cUnit, kThumb2Strex, r1, r2, r0,
Ian Rogers0cfe1fb2011-08-26 03:29:44 -0700566 Object::MonitorOffset().Int32Value() >> 2);
buzbee05eba362012-03-10 20:11:27 -0800567 opRegImm(cUnit, kOpCmp, r1, 0);
568 opIT(cUnit, kArmCondNe, "T");
buzbee1b4c8592011-08-31 10:43:51 -0700569 // Go expensive route - artLockObjectFromCode(self, obj);
Ian Rogers57b86d42012-03-27 16:05:41 -0700570 loadWordDisp(cUnit, rSELF, ENTRYPOINT_OFFSET(pLockObjectFromCode),
buzbee67bf8852011-08-17 17:51:35 -0700571 rLR);
Ian Rogersab2b55d2012-03-18 00:06:11 -0700572 oatClobberCalleeSave(cUnit);
573 opReg(cUnit, kOpBlx, rLR);
buzbee05eba362012-03-10 20:11:27 -0800574 oatGenMemBarrier(cUnit, kSY);
buzbee67bf8852011-08-17 17:51:35 -0700575}
576
577/*
578 * For monitor unlock, we don't have to use ldrex/strex. Once
579 * we've determined that the lock is thin and that we own it with
580 * a zero recursion count, it's safe to punch it back to the
581 * initial, unlock thin state with a store word.
582 */
buzbee31a4a6f2012-02-28 15:36:15 -0800583void genMonitorExit(CompilationUnit* cUnit, MIR* mir, RegLocation rlSrc)
buzbee67bf8852011-08-17 17:51:35 -0700584{
Elliott Hughes5f791332011-09-15 17:45:30 -0700585 DCHECK_EQ(LW_SHAPE_THIN, 0);
buzbee67bf8852011-08-17 17:51:35 -0700586 oatFlushAllRegs(cUnit);
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700587 loadValueDirectFixed(cUnit, rlSrc, r0); // Get obj
buzbee2e748f32011-08-29 21:02:19 -0700588 oatLockCallTemps(cUnit); // Prepare for explicit register usage
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700589 genNullCheck(cUnit, rlSrc.sRegLow, r0, mir);
590 loadWordDisp(cUnit, r0, Object::MonitorOffset().Int32Value(), r1); // Get lock
591 loadWordDisp(cUnit, rSELF, Thread::ThinLockIdOffset().Int32Value(), r2);
buzbee67bf8852011-08-17 17:51:35 -0700592 // Is lock unheld on lock or held by us (==threadId) on unlock?
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700593 opRegRegImm(cUnit, kOpAnd, r3, r1, (LW_HASH_STATE_MASK << LW_HASH_STATE_SHIFT));
buzbeec143c552011-08-20 17:38:58 -0700594 // Align owner
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700595 opRegImm(cUnit, kOpLsl, r2, LW_LOCK_OWNER_SHIFT);
596 newLIR3(cUnit, kThumb2Bfc, r1, LW_HASH_STATE_SHIFT, LW_LOCK_OWNER_SHIFT - 1);
597 opRegReg(cUnit, kOpSub, r1, r2);
buzbee05eba362012-03-10 20:11:27 -0800598 opIT(cUnit, kArmCondEq, "EE");
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700599 storeWordDisp(cUnit, r0, Object::MonitorOffset().Int32Value(), r3);
Ian Rogers4f0d07c2011-10-06 23:38:47 -0700600 // Go expensive route - UnlockObjectFromCode(obj);
Ian Rogers57b86d42012-03-27 16:05:41 -0700601 loadWordDisp(cUnit, rSELF, ENTRYPOINT_OFFSET(pUnlockObjectFromCode),
buzbee67bf8852011-08-17 17:51:35 -0700602 rLR);
Ian Rogersab2b55d2012-03-18 00:06:11 -0700603 oatClobberCalleeSave(cUnit);
604 opReg(cUnit, kOpBlx, rLR);
buzbee05eba362012-03-10 20:11:27 -0800605 oatGenMemBarrier(cUnit, kSY);
buzbee67bf8852011-08-17 17:51:35 -0700606}
607
608/*
609 * 64-bit 3way compare function.
610 * mov rX, #-1
611 * cmp op1hi, op2hi
612 * blt done
613 * bgt flip
614 * sub rX, op1lo, op2lo (treat as unsigned)
615 * beq done
616 * ite hi
617 * mov(hi) rX, #-1
618 * mov(!hi) rX, #1
619 * flip:
620 * neg rX
621 * done:
622 */
buzbee31a4a6f2012-02-28 15:36:15 -0800623void genCmpLong(CompilationUnit* cUnit, MIR* mir, RegLocation rlDest,
624 RegLocation rlSrc1, RegLocation rlSrc2)
buzbee67bf8852011-08-17 17:51:35 -0700625{
buzbee31a4a6f2012-02-28 15:36:15 -0800626 LIR* target1;
627 LIR* target2;
buzbee67bf8852011-08-17 17:51:35 -0700628 rlSrc1 = loadValueWide(cUnit, rlSrc1, kCoreReg);
629 rlSrc2 = loadValueWide(cUnit, rlSrc2, kCoreReg);
buzbeeb29e4d12011-09-26 15:05:48 -0700630 int tReg = oatAllocTemp(cUnit);
631 loadConstant(cUnit, tReg, -1);
buzbee67bf8852011-08-17 17:51:35 -0700632 opRegReg(cUnit, kOpCmp, rlSrc1.highReg, rlSrc2.highReg);
buzbee82488f52012-03-02 08:20:26 -0800633 LIR* branch1 = opCondBranch(cUnit, kCondLt, NULL);
634 LIR* branch2 = opCondBranch(cUnit, kCondGt, NULL);
buzbeeb29e4d12011-09-26 15:05:48 -0700635 opRegRegReg(cUnit, kOpSub, tReg, rlSrc1.lowReg, rlSrc2.lowReg);
buzbee82488f52012-03-02 08:20:26 -0800636 LIR* branch3 = opCondBranch(cUnit, kCondEq, NULL);
buzbee67bf8852011-08-17 17:51:35 -0700637
buzbee82488f52012-03-02 08:20:26 -0800638 opIT(cUnit, kArmCondHi, "E");
buzbeeb29e4d12011-09-26 15:05:48 -0700639 newLIR2(cUnit, kThumb2MovImmShift, tReg, modifiedImmediate(-1));
640 loadConstant(cUnit, tReg, 1);
buzbee67bf8852011-08-17 17:51:35 -0700641 genBarrier(cUnit);
642
buzbee31a4a6f2012-02-28 15:36:15 -0800643 target2 = newLIR0(cUnit, kPseudoTargetLabel);
buzbeeb29e4d12011-09-26 15:05:48 -0700644 opRegReg(cUnit, kOpNeg, tReg, tReg);
buzbee67bf8852011-08-17 17:51:35 -0700645
buzbee31a4a6f2012-02-28 15:36:15 -0800646 target1 = newLIR0(cUnit, kPseudoTargetLabel);
buzbee67bf8852011-08-17 17:51:35 -0700647
buzbeeb29e4d12011-09-26 15:05:48 -0700648 RegLocation rlTemp = LOC_C_RETURN; // Just using as template, will change
649 rlTemp.lowReg = tReg;
buzbee67bf8852011-08-17 17:51:35 -0700650 storeValue(cUnit, rlDest, rlTemp);
buzbeeb29e4d12011-09-26 15:05:48 -0700651 oatFreeTemp(cUnit, tReg);
buzbee67bf8852011-08-17 17:51:35 -0700652
buzbee31a4a6f2012-02-28 15:36:15 -0800653 branch1->target = (LIR*)target1;
654 branch2->target = (LIR*)target2;
655 branch3->target = branch1->target;
buzbee67bf8852011-08-17 17:51:35 -0700656}
657
buzbee84fd6932012-03-29 16:44:16 -0700658void genFusedLongCmpBranch(CompilationUnit* cUnit, BasicBlock* bb, MIR* mir)
659{
660 LIR* labelList = (LIR*)cUnit->blockLabelList;
661 LIR* taken = &labelList[bb->taken->id];
buzbeea2e39d92012-03-30 09:11:45 -0700662 LIR* notTaken = &labelList[bb->fallThrough->id];
buzbee84fd6932012-03-29 16:44:16 -0700663 RegLocation rlSrc1 = oatGetSrcWide(cUnit, mir, 0, 1);
664 RegLocation rlSrc2 = oatGetSrcWide(cUnit, mir, 2, 3);
665 rlSrc1 = loadValueWide(cUnit, rlSrc1, kCoreReg);
666 rlSrc2 = loadValueWide(cUnit, rlSrc2, kCoreReg);
667 ConditionCode ccode = static_cast<ConditionCode>(mir->dalvikInsn.arg[0]);
buzbee84fd6932012-03-29 16:44:16 -0700668 opRegReg(cUnit, kOpCmp, rlSrc1.highReg, rlSrc2.highReg);
669 switch(ccode) {
670 case kCondEq:
671 opCondBranch(cUnit, kCondNe, notTaken);
672 break;
673 case kCondNe:
674 opCondBranch(cUnit, kCondNe, taken);
675 break;
676 case kCondLt:
677 opCondBranch(cUnit, kCondLt, taken);
678 opCondBranch(cUnit, kCondGt, notTaken);
buzbeea2e39d92012-03-30 09:11:45 -0700679 ccode = kCondCc;
buzbee84fd6932012-03-29 16:44:16 -0700680 break;
681 case kCondLe:
682 opCondBranch(cUnit, kCondLt, taken);
683 opCondBranch(cUnit, kCondGt, notTaken);
buzbeea2e39d92012-03-30 09:11:45 -0700684 ccode = kCondLs;
buzbee84fd6932012-03-29 16:44:16 -0700685 break;
686 case kCondGt:
687 opCondBranch(cUnit, kCondGt, taken);
688 opCondBranch(cUnit, kCondLt, notTaken);
buzbeea2e39d92012-03-30 09:11:45 -0700689 ccode = kCondHi;
buzbee84fd6932012-03-29 16:44:16 -0700690 break;
691 case kCondGe:
692 opCondBranch(cUnit, kCondGt, taken);
693 opCondBranch(cUnit, kCondLt, notTaken);
buzbeea2e39d92012-03-30 09:11:45 -0700694 ccode = kCondCs;
buzbee84fd6932012-03-29 16:44:16 -0700695 break;
696 default:
697 LOG(FATAL) << "Unexpected ccode: " << (int)ccode;
698 }
699 opRegReg(cUnit, kOpCmp, rlSrc1.lowReg, rlSrc2.lowReg);
700 opCondBranch(cUnit, ccode, taken);
buzbee84fd6932012-03-29 16:44:16 -0700701}
702
buzbee67bf8852011-08-17 17:51:35 -0700703/*
buzbee31a4a6f2012-02-28 15:36:15 -0800704 * Generate a register comparison to an immediate and branch. Caller
705 * is responsible for setting branch target field.
buzbee67bf8852011-08-17 17:51:35 -0700706 */
buzbee82488f52012-03-02 08:20:26 -0800707LIR* opCmpImmBranch(CompilationUnit* cUnit, ConditionCode cond, int reg,
708 int checkValue, LIR* target)
buzbee67bf8852011-08-17 17:51:35 -0700709{
buzbee31a4a6f2012-02-28 15:36:15 -0800710 LIR* branch;
711 int modImm;
712 ArmConditionCode armCond = oatArmConditionEncoding(cond);
713 if ((LOWREG(reg)) && (checkValue == 0) &&
714 ((armCond == kArmCondEq) || (armCond == kArmCondNe))) {
715 branch = newLIR2(cUnit,
716 (armCond == kArmCondEq) ? kThumb2Cbz : kThumb2Cbnz,
717 reg, 0);
buzbee67bf8852011-08-17 17:51:35 -0700718 } else {
buzbee31a4a6f2012-02-28 15:36:15 -0800719 modImm = modifiedImmediate(checkValue);
720 if (LOWREG(reg) && ((checkValue & 0xff) == checkValue)) {
721 newLIR2(cUnit, kThumbCmpRI8, reg, checkValue);
722 } else if (modImm >= 0) {
723 newLIR2(cUnit, kThumb2CmpRI8, reg, modImm);
buzbee67bf8852011-08-17 17:51:35 -0700724 } else {
buzbee58f92742011-10-01 11:22:17 -0700725 int tReg = oatAllocTemp(cUnit);
buzbee31a4a6f2012-02-28 15:36:15 -0800726 loadConstant(cUnit, tReg, checkValue);
727 opRegReg(cUnit, kOpCmp, reg, tReg);
buzbee58f92742011-10-01 11:22:17 -0700728 }
buzbee31a4a6f2012-02-28 15:36:15 -0800729 branch = newLIR2(cUnit, kThumbBCond, 0, armCond);
buzbee67bf8852011-08-17 17:51:35 -0700730 }
buzbee82488f52012-03-02 08:20:26 -0800731 branch->target = target;
buzbee31a4a6f2012-02-28 15:36:15 -0800732 return branch;
733}
buzbee82488f52012-03-02 08:20:26 -0800734LIR* opRegCopyNoInsert(CompilationUnit* cUnit, int rDest, int rSrc)
buzbee31a4a6f2012-02-28 15:36:15 -0800735{
736 LIR* res;
737 ArmOpcode opcode;
738 if (FPREG(rDest) || FPREG(rSrc))
739 return fpRegCopy(cUnit, rDest, rSrc);
buzbee31a4a6f2012-02-28 15:36:15 -0800740 if (LOWREG(rDest) && LOWREG(rSrc))
741 opcode = kThumbMovRR;
742 else if (!LOWREG(rDest) && !LOWREG(rSrc))
743 opcode = kThumbMovRR_H2H;
744 else if (LOWREG(rDest))
745 opcode = kThumbMovRR_H2L;
746 else
747 opcode = kThumbMovRR_L2H;
buzbeea2ebdd72012-03-04 14:57:06 -0800748 res = rawLIR(cUnit, cUnit->currentDalvikOffset, opcode, rDest, rSrc);
buzbee86a4bce2012-03-06 18:15:00 -0800749 if (!(cUnit->disableOpt & (1 << kSafeOptimizations)) && rDest == rSrc) {
buzbee31a4a6f2012-02-28 15:36:15 -0800750 res->flags.isNop = true;
751 }
752 return res;
buzbee67bf8852011-08-17 17:51:35 -0700753}
754
buzbee82488f52012-03-02 08:20:26 -0800755LIR* opRegCopy(CompilationUnit* cUnit, int rDest, int rSrc)
buzbee67bf8852011-08-17 17:51:35 -0700756{
buzbee82488f52012-03-02 08:20:26 -0800757 LIR* res = opRegCopyNoInsert(cUnit, rDest, rSrc);
buzbee31a4a6f2012-02-28 15:36:15 -0800758 oatAppendLIR(cUnit, (LIR*)res);
759 return res;
760}
buzbee67bf8852011-08-17 17:51:35 -0700761
buzbee82488f52012-03-02 08:20:26 -0800762void opRegCopyWide(CompilationUnit* cUnit, int destLo, int destHi,
buzbee31a4a6f2012-02-28 15:36:15 -0800763 int srcLo, int srcHi)
764{
765 bool destFP = FPREG(destLo) && FPREG(destHi);
766 bool srcFP = FPREG(srcLo) && FPREG(srcHi);
767 DCHECK_EQ(FPREG(srcLo), FPREG(srcHi));
768 DCHECK_EQ(FPREG(destLo), FPREG(destHi));
769 if (destFP) {
770 if (srcFP) {
buzbee82488f52012-03-02 08:20:26 -0800771 opRegCopy(cUnit, S2D(destLo, destHi), S2D(srcLo, srcHi));
buzbee67bf8852011-08-17 17:51:35 -0700772 } else {
buzbee31a4a6f2012-02-28 15:36:15 -0800773 newLIR3(cUnit, kThumb2Fmdrr, S2D(destLo, destHi), srcLo, srcHi);
774 }
775 } else {
776 if (srcFP) {
777 newLIR3(cUnit, kThumb2Fmrrd, destLo, destHi, S2D(srcLo, srcHi));
778 } else {
779 // Handle overlap
780 if (srcHi == destLo) {
buzbee82488f52012-03-02 08:20:26 -0800781 opRegCopy(cUnit, destHi, srcHi);
782 opRegCopy(cUnit, destLo, srcLo);
buzbee67bf8852011-08-17 17:51:35 -0700783 } else {
buzbee82488f52012-03-02 08:20:26 -0800784 opRegCopy(cUnit, destLo, srcLo);
785 opRegCopy(cUnit, destHi, srcHi);
buzbee67bf8852011-08-17 17:51:35 -0700786 }
787 }
788 }
buzbee67bf8852011-08-17 17:51:35 -0700789}
Elliott Hughes11d1b0c2012-01-23 16:57:47 -0800790
buzbee31a4a6f2012-02-28 15:36:15 -0800791
Elliott Hughes11d1b0c2012-01-23 16:57:47 -0800792} // namespace art