#include "rsCpuScriptGroup2.h" #include #include #include #include #include #include #include #include #ifndef RS_COMPATIBILITY_LIB #include "bcc/Config.h" #endif #include "cpu_ref/rsCpuCore.h" #include "rsClosure.h" #include "rsContext.h" #include "rsCpuCore.h" #include "rsCpuExecutable.h" #include "rsCpuScript.h" #include "rsScript.h" #include "rsScriptGroup2.h" #include "rsScriptIntrinsic.h" using std::string; using std::vector; namespace android { namespace renderscript { namespace { const size_t DefaultKernelArgCount = 2; void groupRoot(const RsExpandKernelDriverInfo *kinfo, uint32_t xstart, uint32_t xend, uint32_t outstep) { const List& closures = *(List*)kinfo->usr; RsExpandKernelDriverInfo *mutable_kinfo = const_cast(kinfo); const size_t oldInLen = mutable_kinfo->inLen; decltype(mutable_kinfo->inStride) oldInStride; memcpy(&oldInStride, &mutable_kinfo->inStride, sizeof(oldInStride)); for (CPUClosure* cpuClosure : closures) { const Closure* closure = cpuClosure->mClosure; // There had better be enough space in mutable_kinfo rsAssert(closure->mNumArg <= RS_KERNEL_INPUT_LIMIT); for (size_t i = 0; i < closure->mNumArg; i++) { const void* arg = closure->mArgs[i]; const Allocation* a = (const Allocation*)arg; const uint32_t eStride = a->mHal.state.elementSizeBytes; const uint8_t* ptr = (uint8_t*)(a->mHal.drvState.lod[0].mallocPtr) + eStride * xstart; if (kinfo->dim.y > 1) { ptr += a->mHal.drvState.lod[0].stride * kinfo->current.y; } mutable_kinfo->inPtr[i] = ptr; mutable_kinfo->inStride[i] = eStride; } mutable_kinfo->inLen = closure->mNumArg; const Allocation* out = closure->mReturnValue; const uint32_t ostep = out->mHal.state.elementSizeBytes; const uint8_t* ptr = (uint8_t *)(out->mHal.drvState.lod[0].mallocPtr) + ostep * xstart; if (kinfo->dim.y > 1) { ptr += out->mHal.drvState.lod[0].stride * kinfo->current.y; } mutable_kinfo->outPtr[0] = const_cast(ptr); // The implementation of an intrinsic relies on kinfo->usr being // the "this" pointer to the intrinsic (an RsdCpuScriptIntrinsic object) mutable_kinfo->usr = cpuClosure->mSi; cpuClosure->mFunc(kinfo, xstart, xend, ostep); } mutable_kinfo->inLen = oldInLen; mutable_kinfo->usr = &closures; memcpy(&mutable_kinfo->inStride, &oldInStride, sizeof(oldInStride)); } } // namespace Batch::Batch(CpuScriptGroup2Impl* group, const char* name) : mGroup(group), mFunc(nullptr) { mName = strndup(name, strlen(name)); } Batch::~Batch() { for (CPUClosure* c : mClosures) { delete c; } free(mName); } bool Batch::conflict(CPUClosure* cpuClosure) const { if (mClosures.empty()) { return false; } const Closure* closure = cpuClosure->mClosure; if (!closure->mIsKernel || !mClosures.front()->mClosure->mIsKernel) { // An invoke should be in a batch by itself, so it conflicts with any other // closure. return true; } const auto& globalDeps = closure->mGlobalDeps; const auto& argDeps = closure->mArgDeps; for (CPUClosure* c : mClosures) { const Closure* batched = c->mClosure; if (globalDeps.find(batched) != globalDeps.end()) { return true; } const auto& it = argDeps.find(batched); if (it != argDeps.end()) { const auto& args = (*it).second; for (const auto &p1 : *args) { if (p1.second.get() != nullptr) { return true; } } } } // The compiler fusion pass in bcc expects that kernels chained up through // (1st) input and output. const Closure* lastBatched = mClosures.back()->mClosure; const auto& it = argDeps.find(lastBatched); if (it == argDeps.end()) { return true; } const auto& args = (*it).second; for (const auto &p1 : *args) { if (p1.first == 0 && p1.second.get() == nullptr) { // The new closure depends on the last batched closure's return // value (fieldId being nullptr) for its first argument (argument 0) return false; } } return true; } CpuScriptGroup2Impl::CpuScriptGroup2Impl(RsdCpuReferenceImpl *cpuRefImpl, const ScriptGroupBase *sg) : mCpuRefImpl(cpuRefImpl), mGroup((const ScriptGroup2*)(sg)), mExecutable(nullptr), mScriptObj(nullptr) { rsAssert(!mGroup->mClosures.empty()); mCpuRefImpl->lockMutex(); Batch* batch = new Batch(this, "Batch0"); int i = 0; for (Closure* closure: mGroup->mClosures) { CPUClosure* cc; const IDBase* funcID = closure->mFunctionID.get(); RsdCpuScriptImpl* si = (RsdCpuScriptImpl *)mCpuRefImpl->lookupScript(funcID->mScript); if (closure->mIsKernel) { MTLaunchStructForEach mtls; si->forEachKernelSetup(funcID->mSlot, &mtls); cc = new CPUClosure(closure, si, (ExpandFuncTy)mtls.kernel); } else { cc = new CPUClosure(closure, si); } if (batch->conflict(cc)) { mBatches.push_back(batch); std::stringstream ss; ss << "Batch" << ++i; std::string batchStr(ss.str()); batch = new Batch(this, batchStr.c_str()); } batch->mClosures.push_back(cc); } rsAssert(!batch->mClosures.empty()); mBatches.push_back(batch); #ifndef RS_COMPATIBILITY_LIB compile(mGroup->mCacheDir); if (mScriptObj != nullptr && mExecutable != nullptr) { for (Batch* batch : mBatches) { batch->resolveFuncPtr(mScriptObj); } } #endif // RS_COMPATIBILITY_LIB mCpuRefImpl->unlockMutex(); } void Batch::resolveFuncPtr(void* sharedObj) { std::string funcName(mName); if (mClosures.front()->mClosure->mIsKernel) { funcName.append(".expand"); } mFunc = dlsym(sharedObj, funcName.c_str()); rsAssert (mFunc != nullptr); } CpuScriptGroup2Impl::~CpuScriptGroup2Impl() { for (Batch* batch : mBatches) { delete batch; } delete mExecutable; // TODO: move this dlclose into ~ScriptExecutable(). if (mScriptObj != nullptr) { dlclose(mScriptObj); } } namespace { #ifndef RS_COMPATIBILITY_LIB string getCoreLibPath(Context* context, string* coreLibRelaxedPath) { *coreLibRelaxedPath = ""; // If we're debugging, use the debug library. if (context->getContextType() == RS_CONTEXT_TYPE_DEBUG) { return SYSLIBPATH_BC"/libclcore_debug.bc"; } // Check for a platform specific library #if defined(ARCH_ARM_HAVE_NEON) && !defined(DISABLE_CLCORE_NEON) // NEON-capable ARMv7a devices can use an accelerated math library // for all reduced precision scripts. // ARMv8 does not use NEON, as ASIMD can be used with all precision // levels. *coreLibRelaxedPath = SYSLIBPATH_BC"/libclcore_neon.bc"; #endif #if defined(__i386__) || defined(__x86_64__) // x86 devices will use an optimized library. return SYSLIBPATH_BC"/libclcore_x86.bc"; #else return SYSLIBPATH_BC"/libclcore.bc"; #endif } void setupCompileArguments( const vector& inputs, const vector& kernelBatches, const vector& invokeBatches, const char* outputDir, const char* outputFileName, const char* coreLibPath, const char* coreLibRelaxedPath, const bool emitGlobalInfo, const bool emitGlobalInfoSkipConstant, int optLevel, vector* args) { args->push_back(RsdCpuScriptImpl::BCC_EXE_PATH); args->push_back("-fPIC"); args->push_back("-embedRSInfo"); if (emitGlobalInfo) { args->push_back("-rs-global-info"); if (emitGlobalInfoSkipConstant) { args->push_back("-rs-global-info-skip-constant"); } } args->push_back("-mtriple"); args->push_back(DEFAULT_TARGET_TRIPLE_STRING); args->push_back("-bclib"); args->push_back(coreLibPath); args->push_back("-bclib_relaxed"); args->push_back(coreLibRelaxedPath); for (const char* input : inputs) { args->push_back(input); } for (const string& batch : kernelBatches) { args->push_back("-merge"); args->push_back(batch.c_str()); } for (const string& batch : invokeBatches) { args->push_back("-invoke"); args->push_back(batch.c_str()); } args->push_back("-output_path"); args->push_back(outputDir); args->push_back("-O"); switch (optLevel) { case 0: args->push_back("0"); break; case 3: args->push_back("3"); break; default: ALOGW("Expected optimization level of 0 or 3. Received %d", optLevel); args->push_back("3"); break; } // The output filename has to be the last, in case we need to pop it out and // replace with a different name. args->push_back("-o"); args->push_back(outputFileName); } void generateSourceSlot(RsdCpuReferenceImpl* ctxt, const Closure& closure, const std::vector& inputs, std::stringstream& ss) { const IDBase* funcID = (const IDBase*)closure.mFunctionID.get(); const Script* script = funcID->mScript; rsAssert (!script->isIntrinsic()); const RsdCpuScriptImpl *cpuScript = (const RsdCpuScriptImpl *)ctxt->lookupScript(script); const string& bitcodeFilename = cpuScript->getBitcodeFilePath(); const int index = find(inputs.begin(), inputs.end(), bitcodeFilename) - inputs.begin(); ss << index << "," << funcID->mSlot << "."; } #endif // RS_COMPATIBILTY_LIB } // anonymous namespace // This function is used by the debugger to inspect ScriptGroup // compilations. // // "__attribute__((noinline))" and "__asm__" are used to prevent the // function call from being eliminated as a no-op (see the "noinline" // attribute in gcc documentation). // // "__attribute__((weak))" is used to prevent callers from recognizing // that this is guaranteed to be the function definition, recognizing // that certain arguments are unused, and optimizing away the passing // of those arguments (see the LLVM optimization // DeadArgumentElimination). Theoretically, the compiler could get // aggressive enough with link-time optimization that even marking the // entry point as a weak definition wouldn't solve the problem. // extern __attribute__((noinline)) __attribute__((weak)) void debugHintScriptGroup2(const char* groupName, const uint32_t groupNameSize, const ExpandFuncTy* kernel, const uint32_t kernelCount) { ALOGV("group name: %d:%s\n", groupNameSize, groupName); for (uint32_t i=0; i < kernelCount; ++i) { const char* f1 = (const char*)(kernel[i]); __asm__ __volatile__(""); ALOGV(" closure: %p\n", (const void*)f1); } // do nothing, this is just a hook point for the debugger. return; } void CpuScriptGroup2Impl::compile(const char* cacheDir) { #ifndef RS_COMPATIBILITY_LIB if (mGroup->mClosures.size() < 2) { return; } const int optLevel = getCpuRefImpl()->getContext()->getOptLevel(); if (optLevel == 0) { std::vector kernels; for (const Batch* b : mBatches) for (const CPUClosure* c : b->mClosures) kernels.push_back(c->mFunc); if (kernels.size()) { // pass this information on to the debugger via a hint function. debugHintScriptGroup2(mGroup->mName, strlen(mGroup->mName), kernels.data(), kernels.size()); } // skip script group compilation forcing the driver to use the fallback // execution path which currently has better support for debugging. return; } auto comparator = [](const char* str1, const char* str2) -> bool { return strcmp(str1, str2) < 0; }; std::set inputSet(comparator); for (Closure* closure : mGroup->mClosures) { const Script* script = closure->mFunctionID.get()->mScript; // If any script is an intrinsic, give up trying fusing the kernels. if (script->isIntrinsic()) { return; } const RsdCpuScriptImpl *cpuScript = (const RsdCpuScriptImpl *)mCpuRefImpl->lookupScript(script); const char* bitcodeFilename = cpuScript->getBitcodeFilePath(); inputSet.insert(bitcodeFilename); } std::vector inputs(inputSet.begin(), inputSet.end()); std::vector kernelBatches; std::vector invokeBatches; int i = 0; for (const auto& batch : mBatches) { rsAssert(batch->size() > 0); std::stringstream ss; ss << batch->mName << ":"; if (!batch->mClosures.front()->mClosure->mIsKernel) { rsAssert(batch->size() == 1); generateSourceSlot(mCpuRefImpl, *batch->mClosures.front()->mClosure, inputs, ss); invokeBatches.push_back(ss.str()); } else { for (const auto& cpuClosure : batch->mClosures) { generateSourceSlot(mCpuRefImpl, *cpuClosure->mClosure, inputs, ss); } kernelBatches.push_back(ss.str()); } } rsAssert(cacheDir != nullptr); string objFilePath(cacheDir); objFilePath.append("/"); objFilePath.append(mGroup->mName); objFilePath.append(".o"); const char* resName = mGroup->mName; string coreLibRelaxedPath; const string& coreLibPath = getCoreLibPath(getCpuRefImpl()->getContext(), &coreLibRelaxedPath); vector arguments; bool emitGlobalInfo = getCpuRefImpl()->getEmbedGlobalInfo(); bool emitGlobalInfoSkipConstant = getCpuRefImpl()->getEmbedGlobalInfoSkipConstant(); setupCompileArguments(inputs, kernelBatches, invokeBatches, cacheDir, resName, coreLibPath.c_str(), coreLibRelaxedPath.c_str(), emitGlobalInfo, emitGlobalInfoSkipConstant, optLevel, &arguments); std::unique_ptr cmdLine(rsuJoinStrings(arguments.size() - 1, arguments.data())); inputs.push_back(coreLibPath.c_str()); inputs.push_back(coreLibRelaxedPath.c_str()); uint32_t checksum = constructBuildChecksum(nullptr, 0, cmdLine.get(), inputs.data(), inputs.size()); if (checksum == 0) { return; } std::stringstream ss; ss << std::hex << checksum; std::string checksumStr(ss.str()); //===--------------------------------------------------------------------===// // Try to load a shared lib from code cache matching filename and checksum //===--------------------------------------------------------------------===// bool alreadyLoaded = false; std::string cloneName; const bool useRSDebugContext = (mCpuRefImpl->getContext()->getContextType() == RS_CONTEXT_TYPE_DEBUG); const bool reuse = !is_force_recompile() && !useRSDebugContext; if (reuse) { mScriptObj = SharedLibraryUtils::loadSharedLibrary(cacheDir, resName, nullptr, &alreadyLoaded); } if (mScriptObj != nullptr) { // A shared library named resName is found in code cache directory // cacheDir, and loaded with the handle stored in mScriptObj. mExecutable = ScriptExecutable::createFromSharedObject( mScriptObj, checksum); if (mExecutable != nullptr) { // The loaded shared library in mScriptObj has a matching checksum. // An executable object has been created. return; } ALOGV("Failed to create an executable object from so file due to " "mismatching checksum"); if (alreadyLoaded) { // The shared object found in code cache has already been loaded. // A different file name is needed for the new shared library, to // avoid corrupting the currently loaded instance. cloneName.append(resName); cloneName.append("#"); cloneName.append(SharedLibraryUtils::getRandomString(6).c_str()); // The last element in arguments is the output filename. arguments.pop_back(); arguments.push_back(cloneName.c_str()); } dlclose(mScriptObj); mScriptObj = nullptr; } //===--------------------------------------------------------------------===// // Fuse the input kernels and generate native code in an object file //===--------------------------------------------------------------------===// arguments.push_back("-build-checksum"); arguments.push_back(checksumStr.c_str()); arguments.push_back(nullptr); bool compiled = rsuExecuteCommand(RsdCpuScriptImpl::BCC_EXE_PATH, arguments.size()-1, arguments.data()); if (!compiled) { return; } //===--------------------------------------------------------------------===// // Create and load the shared lib //===--------------------------------------------------------------------===// std::string SOPath; if (!SharedLibraryUtils::createSharedLibrary( getCpuRefImpl()->getContext()->getDriverName(), cacheDir, resName, reuse, &SOPath)) { ALOGE("Failed to link object file '%s'", resName); unlink(objFilePath.c_str()); return; } unlink(objFilePath.c_str()); if (reuse) { mScriptObj = SharedLibraryUtils::loadSharedLibrary(cacheDir, resName); } else { mScriptObj = SharedLibraryUtils::loadAndDeleteSharedLibrary(SOPath.c_str()); } if (mScriptObj == nullptr) { ALOGE("Unable to load '%s'", resName); return; } if (alreadyLoaded) { // Delete the temporary, random-named file that we created to avoid // interfering with an already loaded shared library. string cloneFilePath(cacheDir); cloneFilePath.append("/"); cloneFilePath.append(cloneName.c_str()); cloneFilePath.append(".so"); unlink(cloneFilePath.c_str()); } mExecutable = ScriptExecutable::createFromSharedObject(mScriptObj); #endif // RS_COMPATIBILITY_LIB } void CpuScriptGroup2Impl::execute() { for (auto batch : mBatches) { batch->setGlobalsForBatch(); batch->run(); } } void Batch::setGlobalsForBatch() { for (CPUClosure* cpuClosure : mClosures) { const Closure* closure = cpuClosure->mClosure; const IDBase* funcID = closure->mFunctionID.get(); Script* s = funcID->mScript;; for (const auto& p : closure->mGlobals) { const int64_t value = p.second.first; int size = p.second.second; if (value == 0 && size == 0) { // This indicates the current closure depends on another closure for a // global in their shared module (script). In this case we don't need to // copy the value. For example, an invoke intializes a global variable // which a kernel later reads. continue; } rsAssert(p.first != nullptr); Script* script = p.first->mScript; rsAssert(script == s); RsdCpuReferenceImpl* ctxt = mGroup->getCpuRefImpl(); const RsdCpuScriptImpl *cpuScript = (const RsdCpuScriptImpl *)ctxt->lookupScript(script); int slot = p.first->mSlot; ScriptExecutable* exec = mGroup->getExecutable(); if (exec != nullptr) { const char* varName = cpuScript->getFieldName(slot); void* addr = exec->getFieldAddress(varName); if (size < 0) { rsrSetObject(mGroup->getCpuRefImpl()->getContext(), (rs_object_base*)addr, (ObjectBase*)value); } else { memcpy(addr, (const void*)&value, size); } } else { // We use -1 size to indicate an ObjectBase rather than a primitive type if (size < 0) { s->setVarObj(slot, (ObjectBase*)value); } else { s->setVar(slot, (const void*)&value, size); } } } } } void Batch::run() { if (!mClosures.front()->mClosure->mIsKernel) { rsAssert(mClosures.size() == 1); // This batch contains a single closure for an invoke function CPUClosure* cc = mClosures.front(); const Closure* c = cc->mClosure; if (mFunc != nullptr) { // TODO: Need align pointers for x86_64. // See RsdCpuScriptImpl::invokeFunction in rsCpuScript.cpp ((InvokeFuncTy)mFunc)(c->mParams, c->mParamLength); } else { const ScriptInvokeID* invokeID = (const ScriptInvokeID*)c->mFunctionID.get(); rsAssert(invokeID != nullptr); cc->mSi->invokeFunction(invokeID->mSlot, c->mParams, c->mParamLength); } return; } if (mFunc != nullptr) { MTLaunchStructForEach mtls; const CPUClosure* firstCpuClosure = mClosures.front(); const CPUClosure* lastCpuClosure = mClosures.back(); firstCpuClosure->mSi->forEachMtlsSetup( (const Allocation**)firstCpuClosure->mClosure->mArgs, firstCpuClosure->mClosure->mNumArg, lastCpuClosure->mClosure->mReturnValue, nullptr, 0, nullptr, &mtls); mtls.script = nullptr; mtls.fep.usr = nullptr; mtls.kernel = (ForEachFunc_t)mFunc; mGroup->getCpuRefImpl()->launchForEach( (const Allocation**)firstCpuClosure->mClosure->mArgs, firstCpuClosure->mClosure->mNumArg, lastCpuClosure->mClosure->mReturnValue, nullptr, &mtls); return; } for (CPUClosure* cpuClosure : mClosures) { const Closure* closure = cpuClosure->mClosure; const ScriptKernelID* kernelID = (const ScriptKernelID*)closure->mFunctionID.get(); cpuClosure->mSi->preLaunch(kernelID->mSlot, (const Allocation**)closure->mArgs, closure->mNumArg, closure->mReturnValue, nullptr, 0, nullptr); } const CPUClosure* cpuClosure = mClosures.front(); const Closure* closure = cpuClosure->mClosure; MTLaunchStructForEach mtls; if (cpuClosure->mSi->forEachMtlsSetup((const Allocation**)closure->mArgs, closure->mNumArg, closure->mReturnValue, nullptr, 0, nullptr, &mtls)) { mtls.script = nullptr; mtls.kernel = &groupRoot; mtls.fep.usr = &mClosures; mGroup->getCpuRefImpl()->launchForEach(nullptr, 0, nullptr, nullptr, &mtls); } for (CPUClosure* cpuClosure : mClosures) { const Closure* closure = cpuClosure->mClosure; const ScriptKernelID* kernelID = (const ScriptKernelID*)closure->mFunctionID.get(); cpuClosure->mSi->postLaunch(kernelID->mSlot, (const Allocation**)closure->mArgs, closure->mNumArg, closure->mReturnValue, nullptr, 0, nullptr); } } } // namespace renderscript } // namespace android