blob: ff814c8a6b9e463c0ac78f1ce5f2d2877b1107b3 [file] [log] [blame]
Brian Carlstrom7940e442013-07-12 13:46:57 -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#include "calling_convention.h"
18
Andreas Gampe57943812017-12-06 21:39:13 -080019#include <android-base/logging.h>
Alex Light50fa9932015-08-10 15:30:07 -070020
21#ifdef ART_ENABLE_CODEGEN_arm
Brian Carlstrom7940e442013-07-12 13:46:57 -070022#include "jni/quick/arm/calling_convention_arm.h"
Alex Light50fa9932015-08-10 15:30:07 -070023#endif
24
25#ifdef ART_ENABLE_CODEGEN_arm64
Stuart Monteithb95a5342014-03-12 13:32:32 +000026#include "jni/quick/arm64/calling_convention_arm64.h"
Alex Light50fa9932015-08-10 15:30:07 -070027#endif
28
29#ifdef ART_ENABLE_CODEGEN_mips
Brian Carlstrom7940e442013-07-12 13:46:57 -070030#include "jni/quick/mips/calling_convention_mips.h"
Alex Light50fa9932015-08-10 15:30:07 -070031#endif
32
33#ifdef ART_ENABLE_CODEGEN_mips64
Maja Gagic6ea651f2015-02-24 16:55:04 +010034#include "jni/quick/mips64/calling_convention_mips64.h"
Alex Light50fa9932015-08-10 15:30:07 -070035#endif
36
37#ifdef ART_ENABLE_CODEGEN_x86
Brian Carlstrom7940e442013-07-12 13:46:57 -070038#include "jni/quick/x86/calling_convention_x86.h"
Alex Light50fa9932015-08-10 15:30:07 -070039#endif
40
41#ifdef ART_ENABLE_CODEGEN_x86_64
Dmitry Petrochenkofca82202014-03-21 11:21:37 +070042#include "jni/quick/x86_64/calling_convention_x86_64.h"
Alex Light50fa9932015-08-10 15:30:07 -070043#endif
Brian Carlstrom7940e442013-07-12 13:46:57 -070044
45namespace art {
46
Brian Carlstrom7940e442013-07-12 13:46:57 -070047// Managed runtime calling convention
48
Vladimir Marko93205e32016-04-13 11:59:46 +010049std::unique_ptr<ManagedRuntimeCallingConvention> ManagedRuntimeCallingConvention::Create(
Vladimir Markoe764d2e2017-10-05 14:35:55 +010050 ArenaAllocator* allocator,
Vladimir Marko93205e32016-04-13 11:59:46 +010051 bool is_static,
52 bool is_synchronized,
53 const char* shorty,
54 InstructionSet instruction_set) {
Brian Carlstrom7940e442013-07-12 13:46:57 -070055 switch (instruction_set) {
Alex Light50fa9932015-08-10 15:30:07 -070056#ifdef ART_ENABLE_CODEGEN_arm
Vladimir Marko33bff252017-11-01 14:35:42 +000057 case InstructionSet::kArm:
58 case InstructionSet::kThumb2:
Vladimir Marko93205e32016-04-13 11:59:46 +010059 return std::unique_ptr<ManagedRuntimeCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +010060 new (allocator) arm::ArmManagedRuntimeCallingConvention(
61 is_static, is_synchronized, shorty));
Alex Light50fa9932015-08-10 15:30:07 -070062#endif
63#ifdef ART_ENABLE_CODEGEN_arm64
Vladimir Marko33bff252017-11-01 14:35:42 +000064 case InstructionSet::kArm64:
Vladimir Marko93205e32016-04-13 11:59:46 +010065 return std::unique_ptr<ManagedRuntimeCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +010066 new (allocator) arm64::Arm64ManagedRuntimeCallingConvention(
Vladimir Marko93205e32016-04-13 11:59:46 +010067 is_static, is_synchronized, shorty));
Alex Light50fa9932015-08-10 15:30:07 -070068#endif
69#ifdef ART_ENABLE_CODEGEN_mips
Vladimir Marko33bff252017-11-01 14:35:42 +000070 case InstructionSet::kMips:
Vladimir Marko93205e32016-04-13 11:59:46 +010071 return std::unique_ptr<ManagedRuntimeCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +010072 new (allocator) mips::MipsManagedRuntimeCallingConvention(
Vladimir Marko93205e32016-04-13 11:59:46 +010073 is_static, is_synchronized, shorty));
Alex Light50fa9932015-08-10 15:30:07 -070074#endif
75#ifdef ART_ENABLE_CODEGEN_mips64
Vladimir Marko33bff252017-11-01 14:35:42 +000076 case InstructionSet::kMips64:
Vladimir Marko93205e32016-04-13 11:59:46 +010077 return std::unique_ptr<ManagedRuntimeCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +010078 new (allocator) mips64::Mips64ManagedRuntimeCallingConvention(
Vladimir Marko93205e32016-04-13 11:59:46 +010079 is_static, is_synchronized, shorty));
Alex Light50fa9932015-08-10 15:30:07 -070080#endif
81#ifdef ART_ENABLE_CODEGEN_x86
Vladimir Marko33bff252017-11-01 14:35:42 +000082 case InstructionSet::kX86:
Vladimir Marko93205e32016-04-13 11:59:46 +010083 return std::unique_ptr<ManagedRuntimeCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +010084 new (allocator) x86::X86ManagedRuntimeCallingConvention(
85 is_static, is_synchronized, shorty));
Alex Light50fa9932015-08-10 15:30:07 -070086#endif
87#ifdef ART_ENABLE_CODEGEN_x86_64
Vladimir Marko33bff252017-11-01 14:35:42 +000088 case InstructionSet::kX86_64:
Vladimir Marko93205e32016-04-13 11:59:46 +010089 return std::unique_ptr<ManagedRuntimeCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +010090 new (allocator) x86_64::X86_64ManagedRuntimeCallingConvention(
Vladimir Marko93205e32016-04-13 11:59:46 +010091 is_static, is_synchronized, shorty));
Alex Light50fa9932015-08-10 15:30:07 -070092#endif
Brian Carlstrom7940e442013-07-12 13:46:57 -070093 default:
94 LOG(FATAL) << "Unknown InstructionSet: " << instruction_set;
Vladimir Marko93205e32016-04-13 11:59:46 +010095 UNREACHABLE();
Brian Carlstrom7940e442013-07-12 13:46:57 -070096 }
97}
98
99bool ManagedRuntimeCallingConvention::HasNext() {
100 return itr_args_ < NumArgs();
101}
102
103void ManagedRuntimeCallingConvention::Next() {
104 CHECK(HasNext());
105 if (IsCurrentArgExplicit() && // don't query parameter type of implicit args
106 IsParamALongOrDouble(itr_args_)) {
107 itr_longs_and_doubles_++;
108 itr_slots_++;
109 }
Dmitry Petrochenkofca82202014-03-21 11:21:37 +0700110 if (IsParamAFloatOrDouble(itr_args_)) {
111 itr_float_and_doubles_++;
112 }
Brian Carlstrom7940e442013-07-12 13:46:57 -0700113 if (IsCurrentParamAReference()) {
114 itr_refs_++;
115 }
116 itr_args_++;
117 itr_slots_++;
118}
119
120bool ManagedRuntimeCallingConvention::IsCurrentArgExplicit() {
121 // Static methods have no implicit arguments, others implicitly pass this
122 return IsStatic() || (itr_args_ != 0);
123}
124
125bool ManagedRuntimeCallingConvention::IsCurrentArgPossiblyNull() {
126 return IsCurrentArgExplicit(); // any user parameter may be null
127}
128
129size_t ManagedRuntimeCallingConvention::CurrentParamSize() {
130 return ParamSize(itr_args_);
131}
132
133bool ManagedRuntimeCallingConvention::IsCurrentParamAReference() {
134 return IsParamAReference(itr_args_);
135}
136
Dmitry Petrochenkofca82202014-03-21 11:21:37 +0700137bool ManagedRuntimeCallingConvention::IsCurrentParamAFloatOrDouble() {
138 return IsParamAFloatOrDouble(itr_args_);
139}
140
Serban Constantinescu75b91132014-04-09 18:39:10 +0100141bool ManagedRuntimeCallingConvention::IsCurrentParamADouble() {
142 return IsParamADouble(itr_args_);
143}
144
145bool ManagedRuntimeCallingConvention::IsCurrentParamALong() {
146 return IsParamALong(itr_args_);
147}
148
Brian Carlstrom7940e442013-07-12 13:46:57 -0700149// JNI calling convention
150
Vladimir Markoe764d2e2017-10-05 14:35:55 +0100151std::unique_ptr<JniCallingConvention> JniCallingConvention::Create(ArenaAllocator* allocator,
Vladimir Marko93205e32016-04-13 11:59:46 +0100152 bool is_static,
153 bool is_synchronized,
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700154 bool is_critical_native,
Vladimir Marko93205e32016-04-13 11:59:46 +0100155 const char* shorty,
156 InstructionSet instruction_set) {
Brian Carlstrom7940e442013-07-12 13:46:57 -0700157 switch (instruction_set) {
Alex Light50fa9932015-08-10 15:30:07 -0700158#ifdef ART_ENABLE_CODEGEN_arm
Vladimir Marko33bff252017-11-01 14:35:42 +0000159 case InstructionSet::kArm:
160 case InstructionSet::kThumb2:
Vladimir Marko93205e32016-04-13 11:59:46 +0100161 return std::unique_ptr<JniCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +0100162 new (allocator) arm::ArmJniCallingConvention(
163 is_static, is_synchronized, is_critical_native, shorty));
Alex Light50fa9932015-08-10 15:30:07 -0700164#endif
165#ifdef ART_ENABLE_CODEGEN_arm64
Vladimir Marko33bff252017-11-01 14:35:42 +0000166 case InstructionSet::kArm64:
Vladimir Marko93205e32016-04-13 11:59:46 +0100167 return std::unique_ptr<JniCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +0100168 new (allocator) arm64::Arm64JniCallingConvention(
169 is_static, is_synchronized, is_critical_native, shorty));
Alex Light50fa9932015-08-10 15:30:07 -0700170#endif
171#ifdef ART_ENABLE_CODEGEN_mips
Vladimir Marko33bff252017-11-01 14:35:42 +0000172 case InstructionSet::kMips:
Vladimir Marko93205e32016-04-13 11:59:46 +0100173 return std::unique_ptr<JniCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +0100174 new (allocator) mips::MipsJniCallingConvention(
175 is_static, is_synchronized, is_critical_native, shorty));
Alex Light50fa9932015-08-10 15:30:07 -0700176#endif
177#ifdef ART_ENABLE_CODEGEN_mips64
Vladimir Marko33bff252017-11-01 14:35:42 +0000178 case InstructionSet::kMips64:
Vladimir Marko93205e32016-04-13 11:59:46 +0100179 return std::unique_ptr<JniCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +0100180 new (allocator) mips64::Mips64JniCallingConvention(
181 is_static, is_synchronized, is_critical_native, shorty));
Alex Light50fa9932015-08-10 15:30:07 -0700182#endif
183#ifdef ART_ENABLE_CODEGEN_x86
Vladimir Marko33bff252017-11-01 14:35:42 +0000184 case InstructionSet::kX86:
Vladimir Marko93205e32016-04-13 11:59:46 +0100185 return std::unique_ptr<JniCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +0100186 new (allocator) x86::X86JniCallingConvention(
187 is_static, is_synchronized, is_critical_native, shorty));
Alex Light50fa9932015-08-10 15:30:07 -0700188#endif
189#ifdef ART_ENABLE_CODEGEN_x86_64
Vladimir Marko33bff252017-11-01 14:35:42 +0000190 case InstructionSet::kX86_64:
Vladimir Marko93205e32016-04-13 11:59:46 +0100191 return std::unique_ptr<JniCallingConvention>(
Vladimir Markoe764d2e2017-10-05 14:35:55 +0100192 new (allocator) x86_64::X86_64JniCallingConvention(
193 is_static, is_synchronized, is_critical_native, shorty));
Alex Light50fa9932015-08-10 15:30:07 -0700194#endif
Brian Carlstrom7940e442013-07-12 13:46:57 -0700195 default:
196 LOG(FATAL) << "Unknown InstructionSet: " << instruction_set;
Vladimir Marko93205e32016-04-13 11:59:46 +0100197 UNREACHABLE();
Brian Carlstrom7940e442013-07-12 13:46:57 -0700198 }
199}
200
201size_t JniCallingConvention::ReferenceCount() const {
202 return NumReferenceArgs() + (IsStatic() ? 1 : 0);
203}
204
205FrameOffset JniCallingConvention::SavedLocalReferenceCookieOffset() const {
Mathieu Chartiereb8167a2014-05-07 15:43:14 -0700206 size_t references_size = handle_scope_pointer_size_ * ReferenceCount(); // size excluding header
Mathieu Chartiere401d142015-04-22 13:56:20 -0700207 return FrameOffset(HandleReferencesOffset().Int32Value() + references_size);
Brian Carlstrom7940e442013-07-12 13:46:57 -0700208}
209
210FrameOffset JniCallingConvention::ReturnValueSaveLocation() const {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700211 if (LIKELY(HasHandleScope())) {
212 // Initial offset already includes the displacement.
213 // -- Remove the additional local reference cookie offset if we don't have a handle scope.
214 const size_t saved_local_reference_cookie_offset =
215 SavedLocalReferenceCookieOffset().Int32Value();
216 // Segment state is 4 bytes long
217 const size_t segment_state_size = 4;
218 return FrameOffset(saved_local_reference_cookie_offset + segment_state_size);
219 } else {
220 // Include only the initial Method* as part of the offset.
221 CHECK_LT(displacement_.SizeValue(),
222 static_cast<size_t>(std::numeric_limits<int32_t>::max()));
223 return FrameOffset(displacement_.Int32Value() + static_cast<size_t>(frame_pointer_size_));
224 }
Brian Carlstrom7940e442013-07-12 13:46:57 -0700225}
226
227bool JniCallingConvention::HasNext() {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700228 if (IsCurrentArgExtraForJni()) {
Brian Carlstrom7940e442013-07-12 13:46:57 -0700229 return true;
230 } else {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700231 unsigned int arg_pos = GetIteratorPositionWithinShorty();
Brian Carlstrom7940e442013-07-12 13:46:57 -0700232 return arg_pos < NumArgs();
233 }
234}
235
236void JniCallingConvention::Next() {
237 CHECK(HasNext());
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700238 if (IsCurrentParamALong() || IsCurrentParamADouble()) {
239 itr_longs_and_doubles_++;
240 itr_slots_++;
Brian Carlstrom7940e442013-07-12 13:46:57 -0700241 }
Dmitry Petrochenkofca82202014-03-21 11:21:37 +0700242 if (IsCurrentParamAFloatOrDouble()) {
243 itr_float_and_doubles_++;
244 }
Brian Carlstrom7940e442013-07-12 13:46:57 -0700245 if (IsCurrentParamAReference()) {
246 itr_refs_++;
247 }
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700248 // This default/fallthrough case also covers the extra JNIEnv* argument,
249 // as well as any other single-slot primitives.
Brian Carlstrom7940e442013-07-12 13:46:57 -0700250 itr_args_++;
251 itr_slots_++;
252}
253
254bool JniCallingConvention::IsCurrentParamAReference() {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700255 bool return_value;
256 if (SwitchExtraJniArguments(itr_args_,
257 false, // JNIEnv*
258 true, // jobject or jclass
259 /* out parameters */
260 &return_value)) {
261 return return_value;
262 } else {
263 int arg_pos = GetIteratorPositionWithinShorty();
264 return IsParamAReference(arg_pos);
Brian Carlstrom7940e442013-07-12 13:46:57 -0700265 }
266}
267
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700268
Serban Constantinescu75b91132014-04-09 18:39:10 +0100269bool JniCallingConvention::IsCurrentParamJniEnv() {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700270 if (UNLIKELY(!HasJniEnv())) {
271 return false;
272 }
Serban Constantinescu75b91132014-04-09 18:39:10 +0100273 return (itr_args_ == kJniEnv);
274}
275
Dmitry Petrochenkofca82202014-03-21 11:21:37 +0700276bool JniCallingConvention::IsCurrentParamAFloatOrDouble() {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700277 bool return_value;
278 if (SwitchExtraJniArguments(itr_args_,
279 false, // jnienv*
280 false, // jobject or jclass
281 /* out parameters */
282 &return_value)) {
283 return return_value;
284 } else {
285 int arg_pos = GetIteratorPositionWithinShorty();
286 return IsParamAFloatOrDouble(arg_pos);
Dmitry Petrochenkofca82202014-03-21 11:21:37 +0700287 }
288}
289
Serban Constantinescu75b91132014-04-09 18:39:10 +0100290bool JniCallingConvention::IsCurrentParamADouble() {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700291 bool return_value;
292 if (SwitchExtraJniArguments(itr_args_,
293 false, // jnienv*
294 false, // jobject or jclass
295 /* out parameters */
296 &return_value)) {
297 return return_value;
298 } else {
299 int arg_pos = GetIteratorPositionWithinShorty();
300 return IsParamADouble(arg_pos);
Serban Constantinescu75b91132014-04-09 18:39:10 +0100301 }
302}
303
304bool JniCallingConvention::IsCurrentParamALong() {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700305 bool return_value;
306 if (SwitchExtraJniArguments(itr_args_,
307 false, // jnienv*
308 false, // jobject or jclass
309 /* out parameters */
310 &return_value)) {
311 return return_value;
312 } else {
313 int arg_pos = GetIteratorPositionWithinShorty();
314 return IsParamALong(arg_pos);
Serban Constantinescu75b91132014-04-09 18:39:10 +0100315 }
316}
317
Mathieu Chartiereb8167a2014-05-07 15:43:14 -0700318// Return position of handle scope entry holding reference at the current iterator
Brian Carlstrom7940e442013-07-12 13:46:57 -0700319// position
Mathieu Chartiereb8167a2014-05-07 15:43:14 -0700320FrameOffset JniCallingConvention::CurrentParamHandleScopeEntryOffset() {
Brian Carlstrom7940e442013-07-12 13:46:57 -0700321 CHECK(IsCurrentParamAReference());
Mathieu Chartiereb8167a2014-05-07 15:43:14 -0700322 CHECK_LT(HandleScopeLinkOffset(), HandleScopeNumRefsOffset());
Mathieu Chartiere401d142015-04-22 13:56:20 -0700323 int result = HandleReferencesOffset().Int32Value() + itr_refs_ * handle_scope_pointer_size_;
Mathieu Chartiereb8167a2014-05-07 15:43:14 -0700324 CHECK_GT(result, HandleScopeNumRefsOffset().Int32Value());
Brian Carlstrom7940e442013-07-12 13:46:57 -0700325 return FrameOffset(result);
326}
327
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700328size_t JniCallingConvention::CurrentParamSize() const {
329 if (IsCurrentArgExtraForJni()) {
Andreas Gampe542451c2016-07-26 09:02:02 -0700330 return static_cast<size_t>(frame_pointer_size_); // JNIEnv or jobject/jclass
Brian Carlstrom7940e442013-07-12 13:46:57 -0700331 } else {
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700332 int arg_pos = GetIteratorPositionWithinShorty();
Brian Carlstrom7940e442013-07-12 13:46:57 -0700333 return ParamSize(arg_pos);
334 }
335}
336
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700337size_t JniCallingConvention::NumberOfExtraArgumentsForJni() const {
338 if (LIKELY(HasExtraArgumentsForJni())) {
339 // The first argument is the JNIEnv*.
340 // Static methods have an extra argument which is the jclass.
341 return IsStatic() ? 2 : 1;
342 } else {
343 // Critical natives exclude the JNIEnv and the jclass/this parameters.
344 return 0;
345 }
Brian Carlstrom7940e442013-07-12 13:46:57 -0700346}
347
Igor Murashkin367f3dd2016-09-01 17:00:24 -0700348bool JniCallingConvention::HasHandleScope() const {
349 // Exclude HandleScope for @CriticalNative methods for optimization speed.
350 return is_critical_native_ == false;
351}
352
353bool JniCallingConvention::HasLocalReferenceSegmentState() const {
354 // Exclude local reference segment states for @CriticalNative methods for optimization speed.
355 return is_critical_native_ == false;
356}
357
358bool JniCallingConvention::HasJniEnv() const {
359 // Exclude "JNIEnv*" parameter for @CriticalNative methods.
360 return HasExtraArgumentsForJni();
361}
362
363bool JniCallingConvention::HasSelfClass() const {
364 if (!IsStatic()) {
365 // Virtual functions: There is never an implicit jclass parameter.
366 return false;
367 } else {
368 // Static functions: There is an implicit jclass parameter unless it's @CriticalNative.
369 return HasExtraArgumentsForJni();
370 }
371}
372
373bool JniCallingConvention::HasExtraArgumentsForJni() const {
374 // @CriticalNative jni implementations exclude both JNIEnv* and the jclass/jobject parameters.
375 return is_critical_native_ == false;
376}
377
378unsigned int JniCallingConvention::GetIteratorPositionWithinShorty() const {
379 // We need to subtract out the extra JNI arguments if we want to use this iterator position
380 // with the inherited CallingConvention member functions, which rely on scanning the shorty.
381 // Note that our shorty does *not* include the JNIEnv, jclass/jobject parameters.
382 DCHECK_GE(itr_args_, NumberOfExtraArgumentsForJni());
383 return itr_args_ - NumberOfExtraArgumentsForJni();
384}
385
386bool JniCallingConvention::IsCurrentArgExtraForJni() const {
387 if (UNLIKELY(!HasExtraArgumentsForJni())) {
388 return false; // If there are no extra args, we can never be an extra.
389 }
390 // Only parameters kJniEnv and kObjectOrClass are considered extra.
391 return itr_args_ <= kObjectOrClass;
392}
393
394bool JniCallingConvention::SwitchExtraJniArguments(size_t switch_value,
395 bool case_jni_env,
396 bool case_object_or_class,
397 /* out parameters */
398 bool* return_value) const {
399 DCHECK(return_value != nullptr);
400 if (UNLIKELY(!HasExtraArgumentsForJni())) {
401 return false;
402 }
403
404 switch (switch_value) {
405 case kJniEnv:
406 *return_value = case_jni_env;
407 return true;
408 case kObjectOrClass:
409 *return_value = case_object_or_class;
410 return true;
411 default:
412 return false;
413 }
414}
415
416
Brian Carlstrom7940e442013-07-12 13:46:57 -0700417} // namespace art