From 7b52a80a633275ff68e7a7e09baa8f2dde56b18b Mon Sep 17 00:00:00 2001 From: foreman Date: Mon, 17 Aug 2015 16:27:05 -0400 Subject: [PATCH] P4 to Git Change 1181226 by kebai@kebai-lnx1 on 2015/08/17 16:11:30 ECR #304775 - Preparation for kernel caching feature 1. Each device have a separate cache directory 2. It logs caching errors, so we can debug the cache and/or detect collisions 3. Implementeded cache size tracking, so we can evict old data when cache files are too large 4. Added file/path access permission control on both windows and linux 5. Have read/write file lock protection 6. -kcache-disable flag can be used to turn on/off the caching functionality 7. AMD_FORCE_KCACHE_TEST env variable is used for internal testing 8. For the stage we want to cache, call getCacheEntry() followed by makeCacheEntry() if the get fails; otherwise directly return cached data. - After the compiler library code is refactored, getCacheEntry() and makeCacheEntry() will be wrapped into one function call, so that only one call is needed at the place we want to cache TO DO: 1. Use it in the compiler library code - Waiting for the decision on how many stages we want to cache, i) 1-stage caching: source->ISA; or ii) 3-stage caching: source->LLVM IR, LLVM IR->IL, IL->ISA 2. Tracking of timestamps for cache entries - LRU eviction when cache grows too large - Suggestion from Laurent: Regarding tracking timestamps for LRU eviction: Random eviction would probably perform as well as LRU and does not require timestamps. 3. Track cache entries per application ReviewBoardURL = http://ocltc.amd.com/reviews/r/8194/ Affected files ... ... //depot/stg/opencl/drivers/opencl/compiler/lib/backends/common/cache.cpp#3 add ... //depot/stg/opencl/drivers/opencl/compiler/lib/backends/common/cache.hpp#3 add ... //depot/stg/opencl/drivers/opencl/compiler/lib/utils/OPTIONS.def#127 edit --- rocclr/compiler/lib/backends/common/cache.cpp | 959 ++++++++++++++++++ rocclr/compiler/lib/backends/common/cache.hpp | 159 +++ rocclr/compiler/lib/utils/OPTIONS.def | 8 + 3 files changed, 1126 insertions(+) create mode 100644 rocclr/compiler/lib/backends/common/cache.cpp create mode 100644 rocclr/compiler/lib/backends/common/cache.hpp diff --git a/rocclr/compiler/lib/backends/common/cache.cpp b/rocclr/compiler/lib/backends/common/cache.cpp new file mode 100644 index 0000000000..ff8474f401 --- /dev/null +++ b/rocclr/compiler/lib/backends/common/cache.cpp @@ -0,0 +1,959 @@ +#include "cache.hpp" + +// Use srcdata and buildOpts to generate a cache entry +// +// In: +// isCacheReady - Indicate whether cache file strucutre is set up +// srcData - Source data +// buildOpts - Build options +// dstData - Destination data +// +// Out: +// none +// +// Returns: +// true if entry created; false otherwise, check errorMsg for errors +// +bool KernelCache::makeCacheEntry(bool isCacheReady, const KernelCacheData *srcData, + const std::string &buildOpts, const std::string &dstData) +{ + if (!isCacheReady) { + errorMsg = "makeCacheEntry() failed because cache file structure is not set up successfully"; + appendLogToFile(); + return false; + } + errorMsg.clear(); + std::string fileName; + makeFileName(srcData, buildOpts, fileName); + /* Write all info to cache file */ + const size_t buildOptsSize = buildOpts.size(); + const size_t dstDataSize = dstData.size(); +#if _WIN32 + HANDLE cacheFile = CreateFile(fileName.c_str(), GENERIC_WRITE | WRITE_OWNER | READ_CONTROL, + 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (cacheFile == INVALID_HANDLE_VALUE) { + errorMsg = "Error opening file for writing: " + getLastErrorMsg(); + return false; + } +#else + int cacheFile = open(fileName.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (cacheFile == -1) { + errorMsg = "Error opening file for writing: " + getLastErrorMsg(); + return false; + } + // Exclusive write lock for cache file + struct flock fl = {F_WRLCK, SEEK_SET, 0, 0}; + if (fcntl(cacheFile, F_SETLK, &fl) == -1) { + logErrorCloseFile("Error setting file write lock: " + getLastErrorMsg(), cacheFile); + return false; + } +#endif + // Write kernel cache file header, build options, source data size, source data + // and destination data to cache file + KernelCacheFileHeader H = { {'A', 'M', 'D', '\0'}, buildOptsSize, dstDataSize }; + if (!writeFile(cacheFile, &H, sizeof(KernelCacheFileHeader)) || + !writeFile(cacheFile, buildOpts.c_str(), buildOptsSize) || + !writeFile(cacheFile, &(srcData->dataSize), sizeof(srcData->dataSize)) || + !writeFile(cacheFile, srcData->data, srcData->dataSize) || + !writeFile(cacheFile, dstData.c_str(), dstDataSize)) { + removePartiallyWrittenFile(fileName); + return false; + } +#if __linux__ + // Unlock the file + fl.l_type = F_UNLCK; + if (fcntl(cacheFile, F_SETLK, &fl) == -1) { + logErrorCloseFile("Error unlock file write lock: " + getLastErrorMsg(), cacheFile); + return false; + } +#endif + CloseFile(cacheFile); + // Set file to only owner accessible + if (!setAccessPermission(fileName, true)) { + return false; + } + // Update cache info + unsigned int cacheFileSize = sizeof(KernelCacheFileHeader) + buildOptsSize + + sizeof(srcData->dataSize) + srcData->dataSize + + dstDataSize; + if (!setCacheInfo(version, cacheSize + cacheFileSize)) { + errorMsg = "Cache version and size is not updated successfully"; + return false; + } + return true; +} + +// Use srcData and buildOpts to find the corresponding cache entry, if it exists +// +// In: +// deviceName - chip name +// opts - Options object +// srcData - Source data +// buildOpts - Build options +// Msg - message that need to passed for internal cache testing +// +// Out: +// isCacheReady - Indicate whether cache file strucutre is set up +// dstData - Destination data +// +// Returns: +// true if entry found; false otherwise, check errorMsg for errors +// +bool KernelCache::getCacheEntry(bool &isCacheReady, const std::string &deviceName, amd::option::Options *opts, + const KernelCacheData *srcData, const std::string &buildOpts, + std::string &dstData, const std::string &msg) +{ + dstData.clear(); + errorMsg.clear(); + bool kernelCached = false; + if (!opts->oVariables->DisableKernelCaching && opts->oVariables->OptLevel > 0) { + isCacheReady = cacheInit(SC_BUILD_NUMBER, deviceName); + if (!isCacheReady) { + appendLogToFile(); + } else { + kernelCached = getCacheEntry_helper(srcData, buildOpts, dstData); + // For internal kernel cache test only + if (internalKCacheTestSwitch()) { + std::string cacheMsg = msg; + if (kernelCached) { + cacheMsg += " is cached!\n"; + } else { + cacheMsg += " is not cached!\n"; + } + fprintf(stdout, cacheMsg.c_str()); + fflush(stdout); + } + } + } + if (!errorMsg.empty()) { + appendLogToFile(); + } + return kernelCached; +} + +// Use srcData and buildOpts to find the corresponding cache entry, if it exists +// +// In: +// srcData - Source data +// buildOpts - Build options +// +// Out: +// dstData - Destination data +// +// Returns: +// true if entry found; false otherwise, check errorMsg for errors +// +bool KernelCache::getCacheEntry_helper(const KernelCacheData *srcData, const std::string &buildOpts, + std::string &dstData) +{ + std::string fileName; + makeFileName(srcData, buildOpts, fileName); +#if _WIN32 + HANDLE cacheFile = CreateFile(fileName.c_str(), GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (cacheFile == INVALID_HANDLE_VALUE) { + return false; + } +#else + int cacheFile = open(fileName.c_str(), O_RDONLY); + if (cacheFile == -1) { + return false; + } + // Read lock for cache file + struct flock fl = {F_RDLCK, SEEK_SET, 0, 0}; + if (fcntl(cacheFile, F_SETLK, &fl) == -1) { + logErrorCloseFile("Error setting file read lock: " + getLastErrorMsg(), cacheFile); + return false; + } +#endif + // Read kernel cache file header + KernelCacheFileHeader H; + if (!readFile(cacheFile, &H, sizeof(KernelCacheFileHeader))) { + return false; + } + // Compare kernel cache file header + if (!verifyKernelCacheFileHeader(H, buildOpts)) { + CloseFile(cacheFile); + return false; + } + // Read build options + char *fileBuildOpts = (char *)alloca(H.buildOptSize); + if (!readFile(cacheFile, fileBuildOpts, H.buildOptSize)) { + return false; + } + // Compare build options + if (buildOpts.compare(0, H.buildOptSize, fileBuildOpts, H.buildOptSize)) { + logErrorCloseFile("Cache collision: Build options do not match", cacheFile); + return false; + } + // Get source data size + size_t fileSrcDataSize = 0; + if (!readFile(cacheFile, &fileSrcDataSize, sizeof(size_t))) { + return false; + } + // Compare source data size + if (fileSrcDataSize != srcData->dataSize) { + logErrorCloseFile("Cache collision: Data size does not match", cacheFile); + return false; + } + // Get source data + std::unique_ptr fileSrcData(new char [fileSrcDataSize]); + if (!fileSrcData) { + logErrorCloseFile("Out of memory: " + getLastErrorMsg(), cacheFile); + return false; + } + if (!readFile(cacheFile, fileSrcData.get(), fileSrcDataSize)) { + return false; + } + // Compare source data + if (memcmp(fileSrcData.get(), srcData->data, fileSrcDataSize)) { + logErrorCloseFile("Cache collision: Size matches, contents do not", cacheFile); + return false; + } + // Get cached content + std::unique_ptr data(new char [H.dstSize]); + if (!data) { + logErrorCloseFile("Out of memory: " + getLastErrorMsg(), cacheFile); + return false; + } + if (!readFile(cacheFile, data.get(), H.dstSize)) { + return false; + } + dstData.assign(data.get(), H.dstSize); +#if __linux__ + // Unlock the file + fl.l_type = F_UNLCK; + if (fcntl(cacheFile, F_SETLK, &fl) == -1) { + logErrorCloseFile("Error unlock file read lock: " + getLastErrorMsg(), cacheFile); + return false; + } +#endif + CloseFile(cacheFile); + return true; +} + +#if _WIN32 +// Get Sid of account +// +// In: +// userName - accont name +// +// Out: +// none +// +// Return: +// Sid of account if SID is obtained; NULL otherwise +// +std::unique_ptr KernelCache::getSid(TCHAR *username) +{ + if (username == NULL) { + errorMsg = "Invalid user name in getSid mehtod"; + return NULL; + } + // If a buffer is too small, the count parameter will be set to the size needed. + const DWORD initialSize = 32; + SID_NAME_USE sidNameUse; + DWORD cbSid = initialSize, cchDomainName = initialSize; + // Create buffers for the SID and the domain name + std::unique_ptr sid = std::unique_ptr((SID*) new BYTE[initialSize]); + if (!sid) { + errorMsg = "Failed to allocate space for SID: " + getLastErrorMsg(); + return NULL; + } + std::unique_ptr wszDomainName(new TCHAR[initialSize]); + if (!wszDomainName) { + errorMsg = "Failed to allocate space for domain name: " + getLastErrorMsg(); + return NULL; + } + // Obtain the SID for the account name passed + if (LookupAccountName(NULL, username, sid.get(), &cbSid, wszDomainName.get(), &cchDomainName, &sidNameUse)) { + if (IsValidSid(sid.get()) == FALSE) { + errorMsg = "The SID for the account is invalid: " + getLastErrorMsg(); + return NULL; + } + return sid; + } + DWORD dwErrorCode = GetLastError(); + if (dwErrorCode == ERROR_INSUFFICIENT_BUFFER) { + if (cbSid > initialSize) { + // Reallocate memory for the SID buffer + sid = std::unique_ptr((SID*)new BYTE[cbSid]); + if (!sid) { + errorMsg = "Failed to allocate space for SID: " + getLastErrorMsg(); + return NULL; + } + } + if (cchDomainName > initialSize) { + // Reallocate memory for the domain name buffer + wszDomainName = std::unique_ptr(new TCHAR[cchDomainName]); + if (!wszDomainName) { + errorMsg = "Failed to allocate space for domain name: " + getLastErrorMsg(); + return NULL; + } + } + // Obtain the SID for the account name passed again + if (LookupAccountName(NULL, username, sid.get(), &cbSid, wszDomainName.get(), &cchDomainName, &sidNameUse)) { + if (IsValidSid(sid.get()) == FALSE) { + errorMsg = "The SID for the account is invalid: " + getLastErrorMsg(); + return NULL; + } + return sid; + } + } else { + errorMsg = "Failed to get user security identifier for the account: " + getLastErrorMsg(); + return NULL; + } + return sid; +} +#endif + +#if defined(__linux__) +inline bool path_is_directory(const std::string &path) +{ + struct stat s_buf; + if (stat(path.c_str(), &s_buf)) + return false; + return S_ISDIR(s_buf.st_mode); +} + +// Remove all files and subfolders in a dir +// +// In: +// directory_name - folder name +// +// Out: +// none +// +// Returns: +// The number of files that are removed +// +static unsigned long fileCnt = 0; +unsigned long remove_all(const char* directory_name) +{ + DIR *dp; + struct dirent *ep; + char p_buf[PATH_MAX] = {0}; + dp = opendir(directory_name); + while ((ep = readdir(dp)) != NULL) { + if (strcmp(ep->d_name, "..") && strcmp(ep->d_name, ".")) { + snprintf(p_buf, PATH_MAX, "%s/%s", directory_name, ep->d_name); + if (path_is_directory(p_buf)) { + if (remove_all(p_buf) < 0) { + return LONG_MIN; + } + } else { + if (unlink(p_buf) != 0) { + return LONG_MIN; + } else { + fileCnt++; + } + } + } + } + closedir(dp); + return (rmdir(directory_name) == 0) ? fileCnt : LONG_MIN; +} +#endif + +// Wipe the cache folder structure +// +// In: +// none +// +// Out: +// none +// +// Returns: +// true if folder wipe is ok; false otherwise +// +bool KernelCache::wipeCacheFolders() +{ + for (int i = 0; i < 16; ++i) { + std::string dir = rootPath; + std::stringstream ss; + ss << amd::Os::fileSeparator() << std::hex << i; + dir += ss.str(); + if (amd::Os::pathExists(dir)) { +#if _WIN32 + std::tr2::sys::path mDir(dir); + if (remove_all(mDir) < 0) { +#else + if (remove_all(dir.c_str()) < 0) { +#endif + errorMsg = "Error deleting cache directory"; + return false; + } + } + } + return true; +} + +// Setup cache tree structure +// +// In: +// none +// +// Out: +// none +// +// Returns: +// true if folders setup is ok; false otherwise +// +bool KernelCache::setUpCacheFolders() +{ + // Directory structure is distributed as 16 * 16 in order to keep the file count per directory low + for (int i = 0; i < 16; ++i) { + for (int j = 0; j < 16; ++j) { + std::string dir = rootPath; + std::stringstream ss; + ss << amd::Os::fileSeparator() << std::hex << i << amd::Os::fileSeparator() << j; + dir += ss.str(); + if (false == amd::Os::createPath(dir)) { + errorMsg = "Error creating directory in cache"; + return false; + } + // Set folder to only owner accessible + if (!setAccessPermission(rootPath)) { + return false; + } + } + } + return true; +} + +// Return detailed error message as string +// +// In: +// None +// +// Out: +// None +// +// Return: +// Error message in string format. Otherwise, an empty string if there is no error +// +std::string KernelCache::getLastErrorMsg() +{ +#if _WIN32 + // Get the error message, if any. + DWORD errorMessageID = GetLastError(); + if (errorMessageID == 0) { + return std::string(); //No error message has been recorded + } + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + std::string message(messageBuffer, size); + LocalFree(messageBuffer); + return message; +#else + return std::string(strerror(errno)); +#endif +} + +// Set file to only owner accessible +// +// In: +// fileName - Path to file +// isFile - True if fileName is a file, false if it is a path; false by default +// +// Out: +// none +// +// Returns: +// true if access permission is under control; false otherwise +// +bool KernelCache::setAccessPermission(const std::string &fileName, bool isFile) +{ +#if _WIN32 + TCHAR username[UNLEN + 1]; + DWORD username_len = UNLEN + 1; + if (!GetUserName(username, &username_len)) { + errorMsg = "Failed to get user name for the account: " + getLastErrorMsg(); + return false; + } + std::unique_ptr sid = getSid(username); + if (!sid) { + return false; + } + if (SetNamedSecurityInfo((LPTSTR)(fileName.c_str()), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, + sid.get(), NULL, NULL, NULL) != ERROR_SUCCESS ) { + errorMsg = "Failed to set user access permission: " + getLastErrorMsg(); + return false; + } +#else + if (!isFile) { + int ret = chmod(fileName.c_str(), S_IRUSR | S_IWUSR | S_IXUSR); + if (ret < 0) { + errorMsg = "Failed to set user access permission: " + getLastErrorMsg(); + return false; + } + } +#endif + return true; +} + +// Set the cache's root path +// +// In: +// chipName - Chip name +// +// Out: +// none +// +// Returns: +// true if root path of cache is set successfully; false otherwise +// +bool KernelCache::setRootPath(const std::string &chipName) +{ + rootPath.clear(); +#if _WIN32 + // Set root path to \AppData\Local\AMD\CLCache + TCHAR userLocalAppDir[_MAX_PATH]; + // Get path for user specific and non-roaming data + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, userLocalAppDir))) { + rootPath = userLocalAppDir; + } else { + errorMsg = "User's local app dir is not found: " + getLastErrorMsg(); + return false; + } + rootPath += "\\AMD\\CLCache"; +#else + // Set root path to /.AMD/CLCache + struct passwd *pwd = getpwuid(getuid()); + if (pwd == NULL) { + errorMsg = getLastErrorMsg(); + return false; + } + const char *homedir = pwd->pw_dir; + if (homedir == NULL) { + errorMsg = "Failed to get HOME directory: " + getLastErrorMsg(); + return false; + } + rootPath = homedir; + // Verify the path exists + if (!amd::Os::pathExists(rootPath)) { + errorMsg = "User's home directory is not created: " + getLastErrorMsg(); + return false; + } + rootPath += "/.AMD/CLCache"; +#endif + rootPath += amd::Os::fileSeparator() + chipName; + if (!amd::Os::createPath(rootPath)) { + errorMsg = "Failed to create cache root directory"; + return false; + } + // Set folder to only owner accessible + return setAccessPermission(rootPath); +} + +// Set the cache version and size +// +// In: +// newVersion - New version for the cache +// newSize - New size for the cache +// +// Out: +// none +// +// Returns: +// true if successful; false otherwise +// +bool KernelCache::setCacheInfo(unsigned int newVersion, unsigned int newSize) +{ + unsigned int fileData[2]; + fileData[0] = newVersion; + fileData[1] = newSize; + if (!writeFile(indexName, fileData, sizeof(fileData), false)) { + removePartiallyWrittenFile(indexName); + return false; + } + version = newVersion; + cacheSize = newSize; + return true; +} + +// Get the version and size of the cache +// +// In: +// none +// +// Out: +// none +// +// Returns: +// true if successful; false otherwise +// +bool KernelCache::getCacheInfo() +{ + indexName = rootPath; + indexName += amd::Os::fileSeparator(); + indexName += "cacheDir"; +#if _WIN32 + HANDLE cacheFile = CreateFile(indexName.c_str(), GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (cacheFile == INVALID_HANDLE_VALUE) { + return setCacheInfo(-1, 0); + } +#else + int cacheFile = open(indexName.c_str(), O_RDONLY); + if (cacheFile == -1) { + return setCacheInfo(-1, 0); + } + // Read lock for cache file + struct flock fl = {F_RDLCK, SEEK_SET, 0, 0}; + if (fcntl(cacheFile, F_SETLK, &fl) == -1) { + logErrorCloseFile("Error setting file read lock: " + getLastErrorMsg(), cacheFile); + return false; + } +#endif + if (!readFile(cacheFile, &version, sizeof(unsigned int))) { + return false; + } + if (!readFile(cacheFile, &cacheSize, sizeof(unsigned int))) { + return false; + } +#if __linux__ + // Unlock the file + fl.l_type = F_UNLCK; + if (fcntl(cacheFile, F_SETLK, &fl) == -1) { + logErrorCloseFile("Error unlock file read lock: " + getLastErrorMsg(), cacheFile); + return false; + } +#endif + CloseFile(cacheFile); + return true; +} + +// Initialize the cache +// +// In: +// compilerVersion - Compiler version +// +// Out: +// none +// +// Returns: +// true if successful; false otherwise +// +bool KernelCache::cacheInit(unsigned int compilerVersion, const std::string &chipName) +{ + if (!setRootPath(chipName)) { + return false; + } + if (!getCacheInfo()) { + return false; + } + // Limit cache size to default cache size, and wipe out all cache files when it's exceed + // TODO: need to implement cache eviction policy + if (version != compilerVersion || cacheSize > DEFAULT_CACHE_SIZE) { + if (!wipeCacheFolders() || !setCacheInfo(compilerVersion, 0) || !setUpCacheFolders()) { + return false; + } + } + return true; +} + +// Compute the hash value for a buffer of data along with the buildOpts +// +// In: +// data - Data to hash +// buildOpts - Build options +// +// Out: +// none +// +// Returns: +// Hash value computed from the inputs +// +unsigned int KernelCache::computeHash(const KernelCacheData *data, const std::string &buildOpts) +{ + HashType v = { std::string(data->data, data->dataSize), buildOpts }; + std::hash hash_fn; + return hash_fn(v); +} + +// Control kernel cache test +// +// In: +// none +// +// Out: +// none +// +// Returns: +// true if kernel cache test is on; false otherwise +// +bool KernelCache::internalKCacheTestSwitch() { +#ifndef OPENCL_MAINLINE + const char *cache_test_switch = getenv("AMD_FORCE_KCACHE_TEST"); + if (!(cache_test_switch && strcmp(cache_test_switch,"1") == 0)) { + return false; + } else { + return true; + } +#else + return false; +#endif +} + +// Generate file path from a hash value +// +// In: +// hashVal - A hash value +// +// Out: +// pathToFile - Path to the file +// +// Returns: +// none +// +void KernelCache::getFilePathFromHash(const unsigned int hashVal, std::string &pathToFile) +{ + char textHash[9]; + sprintf(textHash, "%08x", hashVal); + std::string fileName = textHash; + pathToFile = rootPath; + pathToFile += amd::Os::fileSeparator(); + // First char determines first dir level + pathToFile += fileName[0]; + pathToFile += amd::Os::fileSeparator(); + // Second char determines second dir level + pathToFile += fileName[1]; + pathToFile += amd::Os::fileSeparator(); + // Rest of file name determines name + pathToFile += fileName.c_str() + 2; +} + +// Use data and buildOpts to generate a file name +// +// In: +// data - Pointer to data +// buildOpts - Build options +// +// Out: +// pathToFile - Path to the file +// +// Returns: +// none +// +void KernelCache::makeFileName(const KernelCacheData *data, const std::string &buildOpts, std::string &pathToFile) +{ + unsigned int hashVal = computeHash(data, buildOpts); + getFilePathFromHash(hashVal, pathToFile); +} + +// Verify whether the file includes the right kernel cache file header +// +// In: +// H - Kernel cache file header +// buildOpts - Build options +// +// Out: +// None +// +// Returns: +// true if the file is the one matched our requirement; false othereise +// +bool KernelCache::verifyKernelCacheFileHeader(KernelCacheFileHeader &H, const std::string &buildOpts) +{ + const char AMD[4] = {'A', 'M', 'D', '\0'}; + if (memcmp(H.AMD, AMD, 4)) { + errorMsg = "Not a valid cache file"; + return false; + } + if (H.buildOptSize != buildOpts.size()) { + errorMsg = "Cache collision: Build option lengths do not match"; + return false; + } + return true; +} + +// Read contents in cacheFile +// +// In: +// cacheFile - cache file to be read +// sizeToRead - total bytes to be read +// +// Out: +// buffer - contains file content +// +// Returns: +// true if file reads succeed; false otherwise +// +bool KernelCache::readFile(FileHandle cacheFile, void *buffer, ssize_t sizeToRead) +{ + // Read content to the buffer +#if _WIN32 + DWORD bytesRead = 0; + if (FALSE == ReadFile(cacheFile, buffer, sizeToRead, &bytesRead, NULL)) { + logErrorCloseFile("Unable to read cache file: " + getLastErrorMsg(), cacheFile); + return false; + } +#else + ssize_t bytesRead = read(cacheFile, buffer, sizeToRead); +#endif + // Check if there is any error in file reading + if (bytesRead != sizeToRead) { + logErrorCloseFile("Error reading cache file: " + getLastErrorMsg(), cacheFile); + return false; + } + return true; +} + +// Write contents to cacheFile +// +// In: +// cacheFile - cache file to be written +// buffer - contains content to be written +// sizeToWriten - total bytes to be written +// +// Out: +// none +// +// Returns: +// true if file writes succeed; false otherwise +// +bool KernelCache::writeFile(FileHandle cacheFile, const void *buffer, ssize_t sizeToWritten) +{ +#if _WIN32 + DWORD bytesWritten = 0; + if (FALSE == WriteFile(cacheFile, buffer, sizeToWritten, &bytesWritten, NULL)) { + logErrorCloseFile("Unable to write to file: " + getLastErrorMsg(), cacheFile); + return false; + } +#else + ssize_t bytesWritten = write(cacheFile, buffer, sizeToWritten); +#endif + // Check if there is any error in file reading + if (bytesWritten != sizeToWritten) { + logErrorCloseFile("Error writing cache file: " + getLastErrorMsg(), cacheFile); + return false; + } + return true; +} + +// Open a file and write its contents +// +// In: +// fileName - Path to file +// data - Pointer to file contents +// size - Data size +// +// Out: +// none +// +// Returns: +// true if the file is written to file successfully; false otherwise +// +bool KernelCache::writeFile(const std::string &fileName, const void *data, size_t size, bool appendable) +{ +#if _WIN32 + DWORD appendAccess = 0; + if (appendable) { + appendAccess = FILE_APPEND_DATA; + } + HANDLE cacheFile = CreateFile(fileName.c_str(), GENERIC_WRITE | WRITE_OWNER | READ_CONTROL | appendAccess, + 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (cacheFile == INVALID_HANDLE_VALUE) { + errorMsg = "Error opening file for writing: " + getLastErrorMsg(); + return false; + } +#else + int cacheFile = -1; + if (appendable) { + cacheFile = open(fileName.c_str(), O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); + } else { + cacheFile = open(fileName.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + } + if (cacheFile == -1) { + errorMsg = "Error opening file for writing: " + getLastErrorMsg(); + return false; + } + // Exclusive write lock for cache file + struct flock fl = {F_WRLCK, SEEK_SET, 0, 0}; + if (fcntl(cacheFile, F_SETLK, &fl) == -1) { + logErrorCloseFile("Error setting file write lock: " + getLastErrorMsg(), cacheFile); + return false; + } +#endif + // Write data to file + if (!writeFile(cacheFile, data, size)) { + removePartiallyWrittenFile(fileName); + return false; + } +#if __linux__ + // Unlock the file + fl.l_type = F_UNLCK; + if (fcntl(cacheFile, F_SETLK, &fl) == -1) { + logErrorCloseFile("Error unlock file write lock: " + getLastErrorMsg(), cacheFile); + return false; + } +#endif + CloseFile(cacheFile); + // Set file to only owner accessible + return setAccessPermission(fileName, true); +} + +// Remove file +// +// In: +// fileName - Path to file +// +// Out: +// none +// +// Returns: +// none +// +void KernelCache::removePartiallyWrittenFile(const std::string &fileName) +{ + errorMsg = getLastErrorMsg(); +#if _WIN32 + if (!DeleteFile(fileName.c_str())) { +#else + if (remove(fileName.c_str())) { +#endif + errorMsg += ", Unable to delete partially written cache file: " + getLastErrorMsg(); + } +} + +// Log caching error messages for debugging the cache and/or detecting collisions +// +// In: +// extraMsg - Extra message +// +// Out: +// none +// +// Returns: +// none +// +void KernelCache::appendLogToFile(std::string extraMsg) { + if (amd::Os::pathExists(rootPath)) { + std::string fileName = rootPath + amd::Os::fileSeparator() + "cacheError.log"; + errorMsg += extraMsg; + if ('\n' != errorMsg[errorMsg.size()-1]) { + errorMsg.append("\n"); + } + writeFile(fileName, errorMsg.c_str(), errorMsg.length(), true); + } +} + +// Log error message and close the file +// +// In: +// errorMsg - Error message +// file - file handle +// +// Out: +// none +// +// Returns: +// none +// +void KernelCache::logErrorCloseFile(const std::string &errorMsg, const FileHandle file) +{ + appendLogToFile(errorMsg); + CloseFile(file); +} diff --git a/rocclr/compiler/lib/backends/common/cache.hpp b/rocclr/compiler/lib/backends/common/cache.hpp new file mode 100644 index 0000000000..a0bc30be2e --- /dev/null +++ b/rocclr/compiler/lib/backends/common/cache.hpp @@ -0,0 +1,159 @@ +#ifndef AMD_KERNEL_CACHE_H_ +#define AMD_KERNEL_CACHE_H_ + +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif +#include "os/os.hpp" +#include "utils/options.hpp" +#include "../../../sc/Interface/SCLib_Ver.h" + +#if _WIN32 +#define CloseFile CloseHandle +#define FileHandle HANDLE +#else +#define CloseFile close +#define FileHandle int +#endif + +typedef struct _KernelCacheData { + char *data; + size_t dataSize; +} KernelCacheData; + +// Specialize std::hash +struct HashType { + std::string data; + std::string buildOption; +}; +namespace std { +template<> +struct hash { +public: + size_t operator()(const HashType &v) const + { + size_t h1 = std::hash()(v.data); + size_t h2 = std::hash()(v.buildOption); + return h1 ^ ( h2 << 1 ); + } +}; +} + +typedef struct _KernelCacheFileHeader { + char AMD[4]; // 'AMD\0' + size_t buildOptSize; + size_t dstSize; +} KernelCacheFileHeader; + +/* Kernel Cache File Contents (listed in order) */ +// BUild options in text format +// Src data size +// Src data +// Dest data + +class KernelCache { +private: + // TODO: the default cache size (512MB) might be changed later + static const unsigned int DEFAULT_CACHE_SIZE = 512 * 1024 * 1024; + + unsigned int version; + unsigned int cacheSize; + std::string rootPath; + std::string indexName; + std::string errorMsg; + + // Set the root path for the cache + bool setRootPath(const std::string &chipName); + + // Wipe the cache folder structure + bool wipeCacheFolders(); + + // Setup cache tree structure + bool setUpCacheFolders(); + + // Get the cache version and size from the index file + bool getCacheInfo(); + + // Set the cache version and size in the index file + bool setCacheInfo(unsigned int newVersion, unsigned int newSize); + + // Compute hash value for chunks of data + unsigned int computeHash(const KernelCacheData *data, const std::string &buildOpts); + + // Computes hash and file name from given data + void makeFileName(const KernelCacheData *data, const std::string &buildOpts, std::string &pathToFile); + + // Finds path to a file from a given hash value + void getFilePathFromHash(unsigned int hash, std::string &pathToFile); + +#if _WIN32 + // Get Sid of account + std::unique_ptr getSid(TCHAR *userName); +#endif + + // Return detailed error message as string + std::string getLastErrorMsg(); + + // Read contents in cacheFile + bool readFile(FileHandle cacheFile, void *buffer, ssize_t size); + + // Write data to a file + bool writeFile(FileHandle cacheFile, const void *buffer, ssize_t sizeToWriten); + bool writeFile(const std::string &fileName, const void *data, size_t size, bool appendable); + + // Set file to only owner accessible + bool setAccessPermission(const std::string &fileName, bool isFile = false); + + // Set up cache file structure + bool cacheInit(unsigned int compilerVersion, const std::string &chipName); + + // Get cache entry corresponding to srcData, if it exists + bool getCacheEntry_helper(const KernelCacheData *srcData, const std::string &buildOpts, + std::string &dstData); + + // Control kernel cache test + bool internalKCacheTestSwitch(); + + // Verify whether the file includes the right kernel cache file header + bool verifyKernelCacheFileHeader(KernelCacheFileHeader &H, const std::string &buildOpts); + + // Remove partially written file + void removePartiallyWrittenFile(const std::string &fileName); + + // Log error message and close the file + void logErrorCloseFile(const std::string &errorMsg, const FileHandle file); +public: + KernelCache() : version(0), cacheSize(0) { rootPath.clear(); indexName.clear(); errorMsg.clear(); } + + // Make cache entry corresponding to srcData, dstData, buildOpts and kernelName + bool makeCacheEntry(bool isCacheReady, const KernelCacheData *srcData, + const std::string &buildOpts, const std::string &dstData); + + // Wrapper function for getCacheEntry + bool getCacheEntry(bool &isCacheReady, const std::string &deviceName, amd::option::Options *opts, + const KernelCacheData *srcData, const std::string &buildOpts, + std::string &dstData, const std::string &msg); + + // Log caching error messages for debugging the cache and/or detecting collisions + void appendLogToFile(std::string extraMsg = ""); +}; +#endif // AMD_KERNEL_CACHE_H_ diff --git a/rocclr/compiler/lib/utils/OPTIONS.def b/rocclr/compiler/lib/utils/OPTIONS.def index b40fa2fc38..788786eb97 100644 --- a/rocclr/compiler/lib/utils/OPTIONS.def +++ b/rocclr/compiler/lib/utils/OPTIONS.def @@ -1040,6 +1040,14 @@ OPTION(OT_BOOL, \ false, 0, 0, NULL, \ "Enable timing for Kernel build.") +// -kcache-disable +OPTION(OT_BOOL, \ + OA_RUNTIME|OVIS_SUPPORT|OVA_DISALLOWED, \ + "kcache-disable", NULL, \ + DisableKernelCaching, \ + false, 0, 0, NULL, \ + "Disable kernel caching functionality.") + // -print-compile-phases OPTION(OT_BOOL, \ OA_RUNTIME|OVIS_SUPPORT|OVA_DISALLOWED, \