JITCompilerLoader.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // Copyright (c) 2016-2016 Josh Blum
  2. // SPDX-License-Identifier: BSL-1.0
  3. #include <Pothos/Plugin.hpp>
  4. #include <Pothos/System.hpp>
  5. #include <Pothos/Util/Compiler.hpp>
  6. #include <Pothos/Util/FileLock.hpp>
  7. #include <Pothos/Util/BlockDescription.hpp>
  8. #include <Poco/Logger.h>
  9. #include <Poco/Path.h>
  10. #include <Poco/File.h>
  11. #include <Poco/StringTokenizer.h>
  12. #include <Poco/SharedLibrary.h>
  13. #include <memory>
  14. #include <mutex>
  15. #include <cctype>
  16. #include <map>
  17. static Poco::Logger &sourceLoaderLogger(void)
  18. {
  19. static Poco::Logger &logger(Poco::Logger::get("Pothos.JITCompiler"));
  20. return logger;
  21. }
  22. /*!
  23. * Shared data structure for a JIT entry:
  24. * An entry has multiple factories but only one compilation unit.
  25. */
  26. struct RegistryJITResult
  27. {
  28. std::mutex mutex; //!< process-wide mutex for this compilation unit
  29. Pothos::PluginModule pluginModule; //!< plugin module holds result
  30. std::vector<Pothos::PluginPath> factories; //!< factories created by this module
  31. std::exception_ptr exception; //!< saved exception thrown while compiling
  32. Pothos::Util::CompilerArgs compilerArgs; //! Compiler arguments
  33. std::string target; //!< unique target name
  34. void removeRegistrations(void)
  35. {
  36. for (const auto &factoryPath : this->factories)
  37. {
  38. Pothos::PluginRegistry::remove(factoryPath);
  39. }
  40. factories.clear();
  41. }
  42. ~RegistryJITResult(void)
  43. {
  44. this->removeRegistrations();
  45. }
  46. };
  47. /***********************************************************************
  48. * Helper to manage recompile
  49. **********************************************************************/
  50. static void compilationHelper(
  51. RegistryJITResult *handle,
  52. const Poco::File &outFile)
  53. {
  54. //check if we need to recompile
  55. bool recompile = false;
  56. if (outFile.exists() and outFile.getSize() != 0)
  57. {
  58. const auto lastTimeCompiled = outFile.getLastModified();
  59. const auto devLib = Pothos::System::getPothosDevLibraryPath();
  60. if (Poco::File(devLib).getLastModified() > lastTimeCompiled) recompile = true;
  61. for (const auto &source : handle->compilerArgs.sources)
  62. {
  63. if (Poco::File(source).getLastModified() > lastTimeCompiled) recompile = true;
  64. }
  65. }
  66. else recompile = true;
  67. if (not recompile) return;
  68. //compiler instance
  69. const auto compiler = Pothos::Util::Compiler::make();
  70. //compile
  71. sourceLoaderLogger().information("Compile sources for %s...", handle->target);
  72. const auto tmpOutput = compiler->compileCppModule(handle->compilerArgs);
  73. Poco::File(tmpOutput).moveTo(outFile.path());
  74. sourceLoaderLogger().information("Wrote %s", outFile.path());
  75. }
  76. /***********************************************************************
  77. * The JIT compiler factory compiles sources on demand
  78. * and replaces its plugin registration with the compiled on
  79. **********************************************************************/
  80. static Pothos::Object opaqueJITCompilerFactory(
  81. RegistryJITResult *handle,
  82. const Pothos::PluginPath &pluginPath,
  83. const Pothos::Object *args,
  84. const size_t numArgs)
  85. {
  86. //local mutex lock for the registry
  87. std::lock_guard<std::mutex> lock(handle->mutex);
  88. //re-throw if a previous call failed
  89. if (handle->exception) std::rethrow_exception(handle->exception);
  90. //determine output file
  91. Poco::Path outPath(Pothos::System::getUserDataPath());
  92. outPath.append("modules");
  93. Poco::File(outPath).createDirectories();
  94. outPath.append(handle->target + Poco::SharedLibrary::suffix());
  95. //file lock for compilation atomicity across processes
  96. Pothos::Util::FileLock outputFileLock(outPath.toString());
  97. std::lock_guard<Pothos::Util::FileLock> fileLock(outputFileLock);
  98. //compile if changed
  99. try
  100. {
  101. compilationHelper(handle, outPath);
  102. }
  103. catch (const Pothos::Exception &ex)
  104. {
  105. handle->exception = std::current_exception();
  106. sourceLoaderLogger().error(ex.message());
  107. throw;
  108. }
  109. //load the module, only once for all registered entries
  110. if (not handle->pluginModule)
  111. {
  112. //remove all registrations before loading
  113. handle->removeRegistrations();
  114. //load the newly compiled library with plugin module
  115. //the plugin module now owns the factory path entries
  116. handle->pluginModule = Pothos::PluginModule(outPath.toString());
  117. }
  118. //the actual function from the compiled module
  119. const auto plugin = Pothos::PluginRegistry::get(pluginPath);
  120. const auto &call = plugin.getObject().extract<Pothos::Callable>();
  121. return call.opaqueCall(args, numArgs);
  122. }
  123. /***********************************************************************
  124. * Register factory functions that will compile the source
  125. **********************************************************************/
  126. static std::vector<Pothos::PluginPath> JITCompilerLoader(const std::map<std::string, std::string> &config)
  127. {
  128. std::vector<Pothos::PluginPath> entries;
  129. const auto tokOptions = Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM;
  130. const std::string tokSep(" \t");
  131. //create handle to hold shared compilation result
  132. std::shared_ptr<RegistryJITResult> handle(new RegistryJITResult());
  133. //config file path set by caller
  134. const auto confFilePathIt = config.find("confFilePath");
  135. if (confFilePathIt == config.end() or confFilePathIt->second.empty())
  136. throw Pothos::Exception("missing confFilePath");
  137. const auto rootDir = Poco::Path(confFilePathIt->second).makeParent();
  138. //get the target (config basename unless specified)
  139. const auto targetIt = config.find("target");
  140. if (targetIt != config.end()) handle->target = targetIt->second;
  141. else handle->target = Poco::Path(confFilePathIt->second).getBaseName();
  142. if (handle->target.empty()) throw Pothos::Exception("target empty");
  143. //load the compiler args
  144. handle->compilerArgs = Pothos::Util::CompilerArgs::defaultDevEnv();
  145. //load the includes: make absolute to the config dir
  146. const auto includesIt = config.find("includes");
  147. if (includesIt != config.end()) for (const auto &include :
  148. Poco::StringTokenizer(includesIt->second, tokSep, tokOptions))
  149. {
  150. const auto absPath = Poco::Path(include).makeAbsolute(rootDir);
  151. handle->compilerArgs.includes.push_back(absPath.toString());
  152. }
  153. //load the libraries: make absolute to the config dir
  154. const auto librariesIt = config.find("libraries");
  155. if (librariesIt != config.end()) for (const auto &library :
  156. Poco::StringTokenizer(librariesIt->second, tokSep, tokOptions))
  157. {
  158. const auto absPath = Poco::Path(library).makeAbsolute(rootDir);
  159. handle->compilerArgs.libraries.push_back(absPath.toString());
  160. }
  161. //load the sources: make absolute to the config dir
  162. const auto sourcesIt = config.find("sources");
  163. if (sourcesIt != config.end()) for (const auto &source :
  164. Poco::StringTokenizer(sourcesIt->second, tokSep, tokOptions))
  165. {
  166. const auto absPath = Poco::Path(source).makeAbsolute(rootDir);
  167. handle->compilerArgs.sources.push_back(absPath.toString());
  168. }
  169. //load the flags:
  170. const auto flagsIt = config.find("flags");
  171. if (flagsIt != config.end()) for (const auto &flag :
  172. Poco::StringTokenizer(flagsIt->second, tokSep, tokOptions))
  173. {
  174. handle->compilerArgs.flags.push_back(flag);
  175. }
  176. //doc sources: scan sources unless doc sources are specified
  177. std::vector<std::string> docSources;
  178. const auto docSourcesIt = config.find("doc_sources");
  179. if (docSourcesIt != config.end()) for (const auto &docSource :
  180. Poco::StringTokenizer(docSourcesIt->second, tokSep, tokOptions))
  181. {
  182. const auto absPath = Poco::Path(docSource).makeAbsolute(rootDir);
  183. docSources.push_back(absPath.toString());
  184. }
  185. else docSources = handle->compilerArgs.sources;
  186. //load the factories: use this when providing no block description
  187. const auto factoriesIt = config.find("factories");
  188. if (factoriesIt != config.end()) for (const auto &factory :
  189. Poco::StringTokenizer(factoriesIt->second, tokSep, tokOptions))
  190. {
  191. handle->factories.push_back(Pothos::PluginPath("/blocks", factory));
  192. }
  193. //generate JSON block descriptions
  194. Pothos::Util::BlockDescriptionParser parser;
  195. for (const auto &source : docSources) parser.feedFilePath(source);
  196. //store block paths in handle, and store doc paths
  197. for (const auto &factory : parser.listFactories())
  198. {
  199. const auto pluginPath = Pothos::PluginPath("/blocks/docs", factory);
  200. Pothos::PluginRegistry::add(pluginPath, parser.getJSONObject(factory));
  201. entries.push_back(pluginPath);
  202. handle->factories.push_back(Pothos::PluginPath("/blocks", factory));
  203. }
  204. //register for all factory paths
  205. //the handle retains ownership of the factory calls
  206. for (const auto &pluginPath : handle->factories)
  207. {
  208. const auto factory = Pothos::Callable(&opaqueJITCompilerFactory)
  209. .bind(handle.get(), 0)
  210. .bind(pluginPath, 1);
  211. Pothos::PluginRegistry::addCall(pluginPath, factory);
  212. }
  213. //store the handle in the registry
  214. const auto pluginPath = Pothos::PluginPath("/framework/conf_loader/jit_compiler/handles").join(handle->target);
  215. Pothos::PluginRegistry::add(pluginPath, handle);
  216. entries.push_back(pluginPath);
  217. return entries;
  218. }
  219. /***********************************************************************
  220. * loader registration
  221. **********************************************************************/
  222. pothos_static_block(pothosFrameworkRegisterJITCompilerLoader)
  223. {
  224. Pothos::PluginRegistry::addCall("/framework/conf_loader/jit_compiler", &JITCompilerLoader);
  225. }