rsCpuExecutable.cpp revision 46c93e405b0ad7e8fca12d0b1f9eac3997798e45
1#include "rsCpuExecutable.h" 2#include "rsCppUtils.h" 3 4#include <fstream> 5#include <set> 6#include <memory> 7 8#ifdef RS_COMPATIBILITY_LIB 9#include <stdio.h> 10#include <sys/stat.h> 11#include <unistd.h> 12#else 13#include "bcc/Config/Config.h" 14#endif 15 16#include <dlfcn.h> 17 18namespace android { 19namespace renderscript { 20 21namespace { 22 23// Check if a path exists and attempt to create it if it doesn't. 24static bool ensureCacheDirExists(const char *path) { 25 if (access(path, R_OK | W_OK | X_OK) == 0) { 26 // Done if we can rwx the directory 27 return true; 28 } 29 if (mkdir(path, 0700) == 0) { 30 return true; 31 } 32 return false; 33} 34 35// Copy the file named \p srcFile to \p dstFile. 36// Return 0 on success and -1 if anything wasn't copied. 37static int copyFile(const char *dstFile, const char *srcFile) { 38 std::ifstream srcStream(srcFile); 39 if (!srcStream) { 40 ALOGE("Could not verify or read source file: %s", srcFile); 41 return -1; 42 } 43 std::ofstream dstStream(dstFile); 44 if (!dstStream) { 45 ALOGE("Could not verify or write destination file: %s", dstFile); 46 return -1; 47 } 48 dstStream << srcStream.rdbuf(); 49 if (!dstStream) { 50 ALOGE("Could not write destination file: %s", dstFile); 51 return -1; 52 } 53 54 srcStream.close(); 55 dstStream.close(); 56 57 return 0; 58} 59 60static std::string findSharedObjectName(const char *cacheDir, 61 const char *resName) { 62#ifndef RS_SERVER 63 std::string scriptSOName(cacheDir); 64#if defined(RS_COMPATIBILITY_LIB) && !defined(__LP64__) 65 size_t cutPos = scriptSOName.rfind("cache"); 66 if (cutPos != std::string::npos) { 67 scriptSOName.erase(cutPos); 68 } else { 69 ALOGE("Found peculiar cacheDir (missing \"cache\"): %s", cacheDir); 70 } 71 scriptSOName.append("/lib/librs."); 72#else 73 scriptSOName.append("/librs."); 74#endif // RS_COMPATIBILITY_LIB 75 76#else 77 std::string scriptSOName("lib"); 78#endif // RS_SERVER 79 scriptSOName.append(resName); 80 scriptSOName.append(".so"); 81 82 return scriptSOName; 83} 84 85} // anonymous namespace 86 87const char* SharedLibraryUtils::LD_EXE_PATH = "/system/bin/ld.mc"; 88const char* SharedLibraryUtils::RS_CACHE_DIR = "com.android.renderscript.cache"; 89 90#ifndef RS_COMPATIBILITY_LIB 91 92bool SharedLibraryUtils::createSharedLibrary(const char *driverName, 93 const char *cacheDir, 94 const char *resName) { 95 std::string sharedLibName = findSharedObjectName(cacheDir, resName); 96 std::string objFileName = cacheDir; 97 objFileName.append("/"); 98 objFileName.append(resName); 99 objFileName.append(".o"); 100 // Should be something like "libRSDriver.so". 101 std::string linkDriverName = driverName; 102 // Remove ".so" and replace "lib" with "-l". 103 // This will leave us with "-lRSDriver" instead. 104 linkDriverName.erase(linkDriverName.length() - 3); 105 linkDriverName.replace(0, 3, "-l"); 106 107 const char *compiler_rt = SYSLIBPATH"/libcompiler_rt.so"; 108 const char *mTriple = "-mtriple=" DEFAULT_TARGET_TRIPLE_STRING; 109 const char *libPath = "--library-path=" SYSLIBPATH; 110 const char *vendorLibPath = "--library-path=" SYSLIBPATH_VENDOR; 111 112 std::vector<const char *> args = { 113 LD_EXE_PATH, 114 "-shared", 115 "-nostdlib", 116 compiler_rt, mTriple, vendorLibPath, libPath, 117 linkDriverName.c_str(), "-lm", "-lc", 118 objFileName.c_str(), 119 "-o", sharedLibName.c_str(), 120 nullptr 121 }; 122 123 return rsuExecuteCommand(LD_EXE_PATH, args.size()-1, args.data()); 124 125} 126 127#endif // RS_COMPATIBILITY_LIB 128 129const char* RsdCpuScriptImpl::BCC_EXE_PATH = "/system/bin/bcc"; 130 131void* SharedLibraryUtils::loadSharedLibrary(const char *cacheDir, 132 const char *resName, 133 const char *nativeLibDir, 134 bool* alreadyLoaded) { 135 void *loaded = nullptr; 136 137#if defined(RS_COMPATIBILITY_LIB) && defined(__LP64__) 138 std::string scriptSOName = findSharedObjectName(nativeLibDir, resName); 139#else 140 std::string scriptSOName = findSharedObjectName(cacheDir, resName); 141#endif 142 143 // We should check if we can load the library from the standard app 144 // location for shared libraries first. 145 loaded = loadSOHelper(scriptSOName.c_str(), cacheDir, resName, alreadyLoaded); 146 147 if (loaded == nullptr) { 148 ALOGE("Unable to open shared library (%s): %s", 149 scriptSOName.c_str(), dlerror()); 150 151#ifdef RS_COMPATIBILITY_LIB 152 // One final attempt to find the library in "/system/lib". 153 // We do this to allow bundled applications to use the compatibility 154 // library fallback path. Those applications don't have a private 155 // library path, so they need to install to the system directly. 156 // Note that this is really just a testing path. 157 std::string scriptSONameSystem("/system/lib/librs."); 158 scriptSONameSystem.append(resName); 159 scriptSONameSystem.append(".so"); 160 loaded = loadSOHelper(scriptSONameSystem.c_str(), cacheDir, 161 resName); 162 if (loaded == nullptr) { 163 ALOGE("Unable to open system shared library (%s): %s", 164 scriptSONameSystem.c_str(), dlerror()); 165 } 166#endif 167 } 168 169 return loaded; 170} 171 172String8 SharedLibraryUtils::getRandomString(size_t len) { 173 char buf[len + 1]; 174 for (size_t i = 0; i < len; i++) { 175 uint32_t r = arc4random() & 0xffff; 176 r %= 62; 177 if (r < 26) { 178 // lowercase 179 buf[i] = 'a' + r; 180 } else if (r < 52) { 181 // uppercase 182 buf[i] = 'A' + (r - 26); 183 } else { 184 // Use a number 185 buf[i] = '0' + (r - 52); 186 } 187 } 188 buf[len] = '\0'; 189 return String8(buf); 190} 191 192void* SharedLibraryUtils::loadSOHelper(const char *origName, const char *cacheDir, 193 const char *resName, bool *alreadyLoaded) { 194 // Keep track of which .so libraries have been loaded. Once a library is 195 // in the set (per-process granularity), we must instead make a copy of 196 // the original shared object (randomly named .so file) and load that one 197 // instead. If we don't do this, we end up aliasing global data between 198 // the various Script instances (which are supposed to be completely 199 // independent). 200 static std::set<std::string> LoadedLibraries; 201 202 void *loaded = nullptr; 203 204 // Skip everything if we don't even have the original library available. 205 if (access(origName, F_OK) != 0) { 206 return nullptr; 207 } 208 209 // Common path is that we have not loaded this Script/library before. 210 if (LoadedLibraries.find(origName) == LoadedLibraries.end()) { 211 if (alreadyLoaded != nullptr) { 212 *alreadyLoaded = false; 213 } 214 loaded = dlopen(origName, RTLD_NOW | RTLD_LOCAL); 215 if (loaded) { 216 LoadedLibraries.insert(origName); 217 } 218 return loaded; 219 } 220 221 if (alreadyLoaded != nullptr) { 222 *alreadyLoaded = true; 223 } 224 225 std::string newName(cacheDir); 226 227 // Append RS_CACHE_DIR only if it is not found in cacheDir 228 // In driver mode, RS_CACHE_DIR is already appended to cacheDir. 229 if (newName.find(RS_CACHE_DIR) == std::string::npos) { 230 newName.append("/"); 231 newName.append(RS_CACHE_DIR); 232 newName.append("/"); 233 } 234 235 if (!ensureCacheDirExists(newName.c_str())) { 236 ALOGE("Could not verify or create cache dir: %s", cacheDir); 237 return nullptr; 238 } 239 240 // Construct an appropriately randomized filename for the copy. 241 newName.append("librs."); 242 newName.append(resName); 243 newName.append("#"); 244 newName.append(getRandomString(6).string()); // 62^6 potential filename variants. 245 newName.append(".so"); 246 247 int r = copyFile(newName.c_str(), origName); 248 if (r != 0) { 249 ALOGE("Could not create copy %s -> %s", origName, newName.c_str()); 250 return nullptr; 251 } 252 loaded = dlopen(newName.c_str(), RTLD_NOW | RTLD_LOCAL); 253 r = unlink(newName.c_str()); 254 if (r != 0) { 255 ALOGE("Could not unlink copy %s", newName.c_str()); 256 } 257 if (loaded) { 258 LoadedLibraries.insert(newName.c_str()); 259 } 260 261 return loaded; 262} 263 264#define MAXLINE 500 265#define MAKE_STR_HELPER(S) #S 266#define MAKE_STR(S) MAKE_STR_HELPER(S) 267#define EXPORT_VAR_STR "exportVarCount: " 268#define EXPORT_FUNC_STR "exportFuncCount: " 269#define EXPORT_FOREACH_STR "exportForEachCount: " 270#define EXPORT_REDUCE_STR "exportReduceCount: " 271#define EXPORT_REDUCE_NEW_STR "exportReduceNewCount: " 272#define OBJECT_SLOT_STR "objectSlotCount: " 273#define PRAGMA_STR "pragmaCount: " 274#define THREADABLE_STR "isThreadable: " 275#define CHECKSUM_STR "buildChecksum: " 276 277// Copy up to a newline or size chars from str -> s, updating str 278// Returns s when successful and nullptr when '\0' is finally reached. 279static char* strgets(char *s, int size, const char **ppstr) { 280 if (!ppstr || !*ppstr || **ppstr == '\0' || size < 1) { 281 return nullptr; 282 } 283 284 int i; 285 for (i = 0; i < (size - 1); i++) { 286 s[i] = **ppstr; 287 (*ppstr)++; 288 if (s[i] == '\0') { 289 return s; 290 } else if (s[i] == '\n') { 291 s[i+1] = '\0'; 292 return s; 293 } 294 } 295 296 // size has been exceeded. 297 s[i] = '\0'; 298 299 return s; 300} 301 302ScriptExecutable* ScriptExecutable::createFromSharedObject( 303 Context* RSContext, void* sharedObj, uint32_t expectedChecksum) { 304 char line[MAXLINE]; 305 306 size_t varCount = 0; 307 size_t funcCount = 0; 308 size_t forEachCount = 0; 309 size_t reduceCount = 0; 310 size_t reduceNewCount = 0; 311 size_t objectSlotCount = 0; 312 size_t pragmaCount = 0; 313 bool isThreadable = true; 314 315 void** fieldAddress = nullptr; 316 bool* fieldIsObject = nullptr; 317 char** fieldName = nullptr; 318 InvokeFunc_t* invokeFunctions = nullptr; 319 ForEachFunc_t* forEachFunctions = nullptr; 320 uint32_t* forEachSignatures = nullptr; 321 ReduceFunc_t* reduceFunctions = nullptr; 322 const char ** pragmaKeys = nullptr; 323 const char ** pragmaValues = nullptr; 324 uint32_t checksum = 0; 325 326 const char *rsInfo = (const char *) dlsym(sharedObj, kRsInfo); 327 int numEntries = 0; 328 const int *rsGlobalEntries = (const int *) dlsym(sharedObj, kRsGlobalEntries); 329 const char **rsGlobalNames = (const char **) dlsym(sharedObj, kRsGlobalNames); 330 const void **rsGlobalAddresses = (const void **) dlsym(sharedObj, kRsGlobalAddresses); 331 const size_t *rsGlobalSizes = (const size_t *) dlsym(sharedObj, kRsGlobalSizes); 332 const uint32_t *rsGlobalProperties = (const uint32_t *) dlsym(sharedObj, kRsGlobalProperties); 333 334 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 335 return nullptr; 336 } 337 if (sscanf(line, EXPORT_VAR_STR "%zu", &varCount) != 1) { 338 ALOGE("Invalid export var count!: %s", line); 339 return nullptr; 340 } 341 342 fieldAddress = new void*[varCount]; 343 if (fieldAddress == nullptr) { 344 return nullptr; 345 } 346 347 fieldIsObject = new bool[varCount]; 348 if (fieldIsObject == nullptr) { 349 goto error; 350 } 351 352 fieldName = new char*[varCount]; 353 if (fieldName == nullptr) { 354 goto error; 355 } 356 357 for (size_t i = 0; i < varCount; ++i) { 358 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 359 goto error; 360 } 361 char *c = strrchr(line, '\n'); 362 if (c) { 363 *c = '\0'; 364 } 365 void* addr = dlsym(sharedObj, line); 366 if (addr == nullptr) { 367 ALOGE("Failed to find variable address for %s: %s", 368 line, dlerror()); 369 // Not a critical error if we don't find a global variable. 370 } 371 fieldAddress[i] = addr; 372 fieldIsObject[i] = false; 373 fieldName[i] = new char[strlen(line)+1]; 374 strcpy(fieldName[i], line); 375 } 376 377 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 378 goto error; 379 } 380 if (sscanf(line, EXPORT_FUNC_STR "%zu", &funcCount) != 1) { 381 ALOGE("Invalid export func count!: %s", line); 382 goto error; 383 } 384 385 invokeFunctions = new InvokeFunc_t[funcCount]; 386 if (invokeFunctions == nullptr) { 387 goto error; 388 } 389 390 for (size_t i = 0; i < funcCount; ++i) { 391 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 392 goto error; 393 } 394 char *c = strrchr(line, '\n'); 395 if (c) { 396 *c = '\0'; 397 } 398 399 invokeFunctions[i] = (InvokeFunc_t) dlsym(sharedObj, line); 400 if (invokeFunctions[i] == nullptr) { 401 ALOGE("Failed to get function address for %s(): %s", 402 line, dlerror()); 403 goto error; 404 } 405 } 406 407 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 408 goto error; 409 } 410 if (sscanf(line, EXPORT_FOREACH_STR "%zu", &forEachCount) != 1) { 411 ALOGE("Invalid export forEach count!: %s", line); 412 goto error; 413 } 414 415 forEachFunctions = new ForEachFunc_t[forEachCount]; 416 if (forEachFunctions == nullptr) { 417 goto error; 418 } 419 420 forEachSignatures = new uint32_t[forEachCount]; 421 if (forEachSignatures == nullptr) { 422 goto error; 423 } 424 425 for (size_t i = 0; i < forEachCount; ++i) { 426 unsigned int tmpSig = 0; 427 char tmpName[MAXLINE]; 428 429 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 430 goto error; 431 } 432 if (sscanf(line, "%u - %" MAKE_STR(MAXLINE) "s", 433 &tmpSig, tmpName) != 2) { 434 ALOGE("Invalid export forEach!: %s", line); 435 goto error; 436 } 437 438 // Lookup the expanded ForEach kernel. 439 strncat(tmpName, ".expand", MAXLINE-1-strlen(tmpName)); 440 forEachSignatures[i] = tmpSig; 441 forEachFunctions[i] = 442 (ForEachFunc_t) dlsym(sharedObj, tmpName); 443 if (i != 0 && forEachFunctions[i] == nullptr && 444 strcmp(tmpName, "root.expand")) { 445 // Ignore missing root.expand functions. 446 // root() is always specified at location 0. 447 ALOGE("Failed to find forEach function address for %s(): %s", 448 tmpName, dlerror()); 449 goto error; 450 } 451 } 452 453 // Read simple reduce kernels 454 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 455 goto error; 456 } 457 if (sscanf(line, EXPORT_REDUCE_STR "%zu", &reduceCount) != 1) { 458 ALOGE("Invalid export reduce count!: %s", line); 459 goto error; 460 } 461 462 reduceFunctions = new ReduceFunc_t[reduceCount]; 463 if (reduceFunctions == nullptr) { 464 goto error; 465 } 466 467 for (size_t i = 0; i < reduceCount; ++i) { 468 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 469 goto error; 470 } 471 char *c = strrchr(line, '\n'); 472 if (c) { 473 *c = '\0'; 474 } 475 476 // Lookup the expanded reduce kernel. 477 strncat(line, ".expand", MAXLINE-1-strlen(line)); 478 479 reduceFunctions[i] = 480 reinterpret_cast<ReduceFunc_t>(dlsym(sharedObj, line)); 481 if (reduceFunctions[i] == nullptr) { 482 ALOGE("Failed to get function address for %s(): %s", 483 line, dlerror()); 484 goto error; 485 } 486 } 487 488 // Read general reduce kernels (for now, we expect the count to be zero) 489 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 490 goto error; 491 } 492 if (sscanf(line, EXPORT_REDUCE_NEW_STR "%zu", &reduceNewCount) != 1) { 493 ALOGE("Invalid export reduce new count!: %s", line); 494 goto error; 495 } 496 if (reduceNewCount != 0) { 497 ALOGE("Expected export reduce new count to be zero!: %s", line); 498 goto error; 499 } 500 501 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 502 goto error; 503 } 504 if (sscanf(line, OBJECT_SLOT_STR "%zu", &objectSlotCount) != 1) { 505 ALOGE("Invalid object slot count!: %s", line); 506 goto error; 507 } 508 509 for (size_t i = 0; i < objectSlotCount; ++i) { 510 uint32_t varNum = 0; 511 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 512 goto error; 513 } 514 if (sscanf(line, "%u", &varNum) != 1) { 515 ALOGE("Invalid object slot!: %s", line); 516 goto error; 517 } 518 519 if (varNum < varCount) { 520 fieldIsObject[varNum] = true; 521 } 522 } 523 524#ifndef RS_COMPATIBILITY_LIB 525 // Do not attempt to read pragmas or isThreadable flag in compat lib path. 526 // Neither is applicable for compat lib 527 528 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 529 goto error; 530 } 531 532 if (sscanf(line, PRAGMA_STR "%zu", &pragmaCount) != 1) { 533 ALOGE("Invalid pragma count!: %s", line); 534 goto error; 535 } 536 537 pragmaKeys = new const char*[pragmaCount]; 538 if (pragmaKeys == nullptr) { 539 goto error; 540 } 541 542 pragmaValues = new const char*[pragmaCount]; 543 if (pragmaValues == nullptr) { 544 goto error; 545 } 546 547 bzero(pragmaKeys, sizeof(char*) * pragmaCount); 548 bzero(pragmaValues, sizeof(char*) * pragmaCount); 549 550 for (size_t i = 0; i < pragmaCount; ++i) { 551 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 552 ALOGE("Unable to read pragma at index %zu!", i); 553 goto error; 554 } 555 char key[MAXLINE]; 556 char value[MAXLINE] = ""; // initialize in case value is empty 557 558 // pragmas can just have a key and no value. Only check to make sure 559 // that the key is not empty 560 if (sscanf(line, "%" MAKE_STR(MAXLINE) "s - %" MAKE_STR(MAXLINE) "s", 561 key, value) == 0 || 562 strlen(key) == 0) 563 { 564 ALOGE("Invalid pragma value!: %s", line); 565 566 goto error; 567 } 568 569 char *pKey = new char[strlen(key)+1]; 570 strcpy(pKey, key); 571 pragmaKeys[i] = pKey; 572 573 char *pValue = new char[strlen(value)+1]; 574 strcpy(pValue, value); 575 pragmaValues[i] = pValue; 576 //ALOGE("Pragma %zu: Key: '%s' Value: '%s'", i, pKey, pValue); 577 } 578 579 if (strgets(line, MAXLINE, &rsInfo) == nullptr) { 580 goto error; 581 } 582 583 char tmpFlag[4]; 584 if (sscanf(line, THREADABLE_STR "%4s", tmpFlag) != 1) { 585 ALOGE("Invalid threadable flag!: %s", line); 586 goto error; 587 } 588 if (strcmp(tmpFlag, "yes") == 0) { 589 isThreadable = true; 590 } else if (strcmp(tmpFlag, "no") == 0) { 591 isThreadable = false; 592 } else { 593 ALOGE("Invalid threadable flag!: %s", tmpFlag); 594 goto error; 595 } 596 597 if (strgets(line, MAXLINE, &rsInfo) != nullptr) { 598 if (sscanf(line, CHECKSUM_STR "%08x", &checksum) != 1) { 599 ALOGE("Invalid checksum flag!: %s", line); 600 goto error; 601 } 602 } else { 603 ALOGE("Missing checksum in shared obj file"); 604 goto error; 605 } 606 607 if (expectedChecksum != 0 && checksum != expectedChecksum) { 608 ALOGE("Found invalid checksum. Expected %08x, got %08x\n", 609 expectedChecksum, checksum); 610 goto error; 611 } 612 613#endif // RS_COMPATIBILITY_LIB 614 615 // Read in information about mutable global variables provided by bcc's 616 // RSGlobalInfoPass 617 if (rsGlobalEntries) { 618 numEntries = *rsGlobalEntries; 619 if (numEntries > 0) { 620 rsAssert(rsGlobalNames); 621 rsAssert(rsGlobalAddresses); 622 rsAssert(rsGlobalSizes); 623 rsAssert(rsGlobalProperties); 624 } 625 } else { 626 ALOGD("Missing .rs.global_entries from shared object"); 627 } 628 629 return new ScriptExecutable( 630 RSContext, fieldAddress, fieldIsObject, fieldName, varCount, 631 invokeFunctions, funcCount, 632 forEachFunctions, forEachSignatures, forEachCount, 633 reduceFunctions, reduceCount, 634 pragmaKeys, pragmaValues, pragmaCount, 635 rsGlobalNames, rsGlobalAddresses, rsGlobalSizes, rsGlobalProperties, 636 numEntries, isThreadable, checksum); 637 638error: 639 640#ifndef RS_COMPATIBILITY_LIB 641 642 for (size_t idx = 0; idx < pragmaCount; ++idx) { 643 delete [] pragmaKeys[idx]; 644 delete [] pragmaValues[idx]; 645 } 646 647 delete[] pragmaValues; 648 delete[] pragmaKeys; 649#endif // RS_COMPATIBILITY_LIB 650 651 delete[] reduceFunctions; 652 653 delete[] forEachSignatures; 654 delete[] forEachFunctions; 655 656 delete[] invokeFunctions; 657 658 for (size_t i = 0; i < varCount; i++) { 659 delete[] fieldName[i]; 660 } 661 delete[] fieldName; 662 delete[] fieldIsObject; 663 delete[] fieldAddress; 664 665 return nullptr; 666} 667 668void* ScriptExecutable::getFieldAddress(const char* name) const { 669 // TODO: improve this by using a hash map. 670 for (size_t i = 0; i < mExportedVarCount; i++) { 671 if (strcmp(name, mFieldName[i]) == 0) { 672 return mFieldAddress[i]; 673 } 674 } 675 return nullptr; 676} 677 678bool ScriptExecutable::dumpGlobalInfo() const { 679 ALOGE("Globals: %p %p %p", mGlobalAddresses, mGlobalSizes, mGlobalNames); 680 ALOGE("P - Pointer"); 681 ALOGE(" C - Constant"); 682 ALOGE(" S - Static"); 683 for (int i = 0; i < mGlobalEntries; i++) { 684 ALOGE("Global[%d]: %p %zu %s", i, mGlobalAddresses[i], mGlobalSizes[i], 685 mGlobalNames[i]); 686 uint32_t properties = mGlobalProperties[i]; 687 ALOGE("%c%c%c Type: %u", 688 isGlobalPointer(properties) ? 'P' : ' ', 689 isGlobalConstant(properties) ? 'C' : ' ', 690 isGlobalStatic(properties) ? 'S' : ' ', 691 getGlobalRsType(properties)); 692 } 693 return true; 694} 695 696} // namespace renderscript 697} // namespace android 698