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