/* * Copyright 2010-2014, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "slang_rs_reflect_utils.h" #include #include #include #include #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "os_sep.h" #include "slang_assert.h" namespace slang { using std::string; string RSSlangReflectUtils::GetFileNameStem(const char *fileName) { const char *dot = fileName + strlen(fileName); const char *slash = dot - 1; while (slash >= fileName) { if (*slash == OS_PATH_SEPARATOR) { break; } if ((*slash == '.') && (*dot == 0)) { dot = slash; } --slash; } ++slash; return string(slash, dot - slash); } string RSSlangReflectUtils::ComputePackagedPath(const char *prefixPath, const char *packageName) { string packaged_path(prefixPath); if (!packaged_path.empty() && (packaged_path[packaged_path.length() - 1] != OS_PATH_SEPARATOR)) { packaged_path += OS_PATH_SEPARATOR_STR; } size_t s = packaged_path.length(); packaged_path += packageName; while (s < packaged_path.length()) { if (packaged_path[s] == '.') { packaged_path[s] = OS_PATH_SEPARATOR; } ++s; } return packaged_path; } static string InternalFileNameConvert(const char *rsFileName, bool toLower) { const char *dot = rsFileName + strlen(rsFileName); const char *slash = dot - 1; while (slash >= rsFileName) { if (*slash == OS_PATH_SEPARATOR) { break; } if ((*slash == '.') && (*dot == 0)) { dot = slash; } --slash; } ++slash; char ret[256]; int i = 0; for (; (i < 255) && (slash < dot); ++slash) { if (isalnum(*slash) || *slash == '_') { if (toLower) { ret[i] = tolower(*slash); } else { ret[i] = *slash; } ++i; } } ret[i] = 0; return string(ret); } std::string RSSlangReflectUtils::JavaClassNameFromRSFileName(const char *rsFileName) { return InternalFileNameConvert(rsFileName, false); } std::string RootNameFromRSFileName(const std::string &rsFileName) { return InternalFileNameConvert(rsFileName.c_str(), false); } std::string RSSlangReflectUtils::BCFileNameFromRSFileName(const char *rsFileName) { return InternalFileNameConvert(rsFileName, true); } std::string RSSlangReflectUtils::JavaBitcodeClassNameFromRSFileName( const char *rsFileName) { std::string tmp(InternalFileNameConvert(rsFileName, false)); return tmp.append("BitCode"); } static bool GenerateAccessorMethod( const RSSlangReflectUtils::BitCodeAccessorContext &context, int bitwidth, GeneratedFile &out) { // the prototype of the accessor method out.indent() << "// return byte array representation of the " << bitwidth << "-bit bitcode.\n"; out.indent() << "public static byte[] getBitCode" << bitwidth << "()"; out.startBlock(); out.indent() << "return getBitCode" << bitwidth << "Internal();\n"; out.endBlock(true); return true; } // Java method size must not exceed 64k, // so we have to split the bitcode into multiple segments. static bool GenerateSegmentMethod(const char *buff, int blen, int bitwidth, int seg_num, GeneratedFile &out) { out.indent() << "private static byte[] getSegment" << bitwidth << "_" << seg_num << "()"; out.startBlock(); out.indent() << "byte[] data = {"; out.increaseIndent(); const int kEntriesPerLine = 16; int position = kEntriesPerLine; // We start with a new line and indent. for (int written = 0; written < blen; written++) { if (++position >= kEntriesPerLine) { out << "\n"; out.indent(); position = 0; } else { out << " "; } out << std::setw(4) << static_cast(buff[written]) << ","; } out << "\n"; out.decreaseIndent(); out.indent() << "};\n"; out.indent() << "return data;\n"; out.endBlock(); return true; } static bool GenerateJavaCodeAccessorMethodForBitwidth( const RSSlangReflectUtils::BitCodeAccessorContext &context, int bitwidth, GeneratedFile &out) { std::string filename(context.bc32FileName); if (bitwidth == 64) { filename = context.bc64FileName; } FILE *pfin = fopen(filename.c_str(), "rb"); if (pfin == nullptr) { fprintf(stderr, "Error: could not read file %s\n", filename.c_str()); return false; } // start the accessor method GenerateAccessorMethod(context, bitwidth, out); // output the data // make sure the generated function for a segment won't break the Javac // size limitation (64K). static const int SEG_SIZE = 0x2000; char *buff = new char[SEG_SIZE]; int read_length; int seg_num = 0; int total_length = 0; while ((read_length = fread(buff, 1, SEG_SIZE, pfin)) > 0) { GenerateSegmentMethod(buff, read_length, bitwidth, seg_num, out); ++seg_num; total_length += read_length; } delete[] buff; fclose(pfin); // output the internal accessor method out.indent() << "private static int bitCode" << bitwidth << "Length = " << total_length << ";\n\n"; out.indent() << "private static byte[] getBitCode" << bitwidth << "Internal()"; out.startBlock(); out.indent() << "byte[] bc = new byte[bitCode" << bitwidth << "Length];\n"; out.indent() << "int offset = 0;\n"; out.indent() << "byte[] seg;\n"; for (int i = 0; i < seg_num; ++i) { out.indent() << "seg = getSegment" << bitwidth << "_" << i << "();\n"; out.indent() << "System.arraycopy(seg, 0, bc, offset, seg.length);\n"; out.indent() << "offset += seg.length;\n"; } out.indent() << "return bc;\n"; out.endBlock(); return true; } static bool GenerateJavaCodeAccessorMethod( const RSSlangReflectUtils::BitCodeAccessorContext &context, GeneratedFile &out) { if (!GenerateJavaCodeAccessorMethodForBitwidth(context, 32, out)) { slangAssert(false && "Couldn't generate 32-bit embedded bitcode!"); return false; } if (!GenerateJavaCodeAccessorMethodForBitwidth(context, 64, out)) { slangAssert(false && "Couldn't generate 64-bit embedded bitcode!"); return false; } return true; } static bool GenerateAccessorClass( const RSSlangReflectUtils::BitCodeAccessorContext &context, const char *clazz_name, GeneratedFile &out) { // begin the class. out << "/**\n"; out << " * @hide\n"; out << " */\n"; out << "public class " << clazz_name; out.startBlock(); bool ret = true; switch (context.bcStorage) { case BCST_APK_RESOURCE: slangAssert(false && "Invalid generation of bitcode accessor with resource"); break; case BCST_JAVA_CODE: ret = GenerateJavaCodeAccessorMethod(context, out); break; default: ret = false; } // end the class. out.endBlock(); return ret; } bool RSSlangReflectUtils::GenerateJavaBitCodeAccessor( const BitCodeAccessorContext &context) { string output_path = ComputePackagedPath(context.reflectPath, context.packageName); if (std::error_code EC = llvm::sys::fs::create_directories( llvm::sys::path::parent_path(output_path))) { fprintf(stderr, "Error: could not create dir %s: %s\n", output_path.c_str(), EC.message().c_str()); return false; } string clazz_name(JavaBitcodeClassNameFromRSFileName(context.rsFileName)); string filename(clazz_name); filename += ".java"; GeneratedFile out; if (!out.startFile(output_path, filename, context.rsFileName, context.licenseNote, true, context.verbose)) { return false; } out << "package " << context.packageName << ";\n\n"; bool ret = GenerateAccessorClass(context, clazz_name.c_str(), out); out.closeFile(); return ret; } std::string JoinPath(const std::string &path1, const std::string &path2) { if (path1.empty()) { return path2; } if (path2.empty()) { return path1; } std::string fullPath = path1; if (fullPath[fullPath.length() - 1] != OS_PATH_SEPARATOR) { fullPath += OS_PATH_SEPARATOR; } if (path2[0] == OS_PATH_SEPARATOR) { fullPath += path2.substr(1, string::npos); } else { fullPath += path2; } return fullPath; } // Replace all instances of "\" with "\\" in a single string to prevent // formatting errors. In Java, this can happen even within comments, as // Java processes \u before the comments are stripped. E.g. if the generated // file in Windows contains the note: // /* Do not modify! Generated from \Users\MyName\MyDir\foo.cs */ // Java will think that \U tells of a Unicode character. static void SanitizeString(std::string *s) { size_t p = 0; while ((p = s->find('\\', p)) != std::string::npos) { s->replace(p, 1, "\\\\"); p += 2; } } static const char *const gApacheLicenseNote = "/*\n" " * Copyright (C) 2011-2014 The Android Open Source Project\n" " *\n" " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" " * you may not use this file except in compliance with the License.\n" " * You may obtain a copy of the License at\n" " *\n" " * http://www.apache.org/licenses/LICENSE-2.0\n" " *\n" " * Unless required by applicable law or agreed to in writing, software\n" " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or " "implied.\n" " * See the License for the specific language governing permissions and\n" " * limitations under the License.\n" " */\n" "\n"; bool GeneratedFile::startFile(const string &outDirectory, const string &outFileName, const string &sourceFileName, const string *optionalLicense, bool isJava, bool verbose) { if (verbose) { printf("Generating %s\n", outFileName.c_str()); } // Create the parent directories. if (!outDirectory.empty()) { if (std::error_code EC = llvm::sys::fs::create_directories( llvm::sys::path::parent_path(outDirectory))) { fprintf(stderr, "Error: %s\n", EC.message().c_str()); return false; } } std::string FilePath = JoinPath(outDirectory, outFileName); // Open the file. open(FilePath.c_str()); if (!good()) { fprintf(stderr, "Error: could not write file %s\n", outFileName.c_str()); return false; } // Write the license. if (optionalLicense != nullptr) { *this << *optionalLicense; } else { *this << gApacheLicenseNote; } // Write a notice that this is a generated file. std::string source(sourceFileName); if (isJava) { SanitizeString(&source); } *this << "/*\n" << " * This file is auto-generated. DO NOT MODIFY!\n" << " * The source Renderscript file: " << source << "\n" << " */\n\n"; return true; } void GeneratedFile::closeFile() { close(); } void GeneratedFile::increaseIndent() { mIndent.append(" "); } void GeneratedFile::decreaseIndent() { slangAssert(!mIndent.empty() && "No indent"); mIndent.erase(0, 4); } void GeneratedFile::comment(const std::string &s) { indent() << "/* "; // +3 for the " * " starting each line. std::size_t indentLength = mIndent.length() + 3; std::size_t lengthOfCommentOnLine = 0; const std::size_t maxPerLine = 80; for (std::size_t start = 0, length = s.length(), nextStart = 0; start < length; start = nextStart) { std::size_t p = s.find_first_of(" \n", start); std::size_t toCopy = 1; bool forceBreak = false; if (p == std::string::npos) { toCopy = length - start; nextStart = length; } else { toCopy = p - start; nextStart = p + 1; forceBreak = s[p] == '\n'; } if (lengthOfCommentOnLine > 0) { if (indentLength + lengthOfCommentOnLine + toCopy >= maxPerLine) { *this << "\n"; indent() << " * "; lengthOfCommentOnLine = 0; } else { *this << " "; } } *this << s.substr(start, toCopy); if (forceBreak) { lengthOfCommentOnLine = maxPerLine; } else { lengthOfCommentOnLine += toCopy; } } *this << "\n"; indent() << " */\n"; } } // namespace slang