方舟编译器学习笔记18 Compiler及其子类
前文方舟编译器学习笔记15 分析MapleCombCompiler::Compile调用了DriverRunner::Run(),CompilerFactory::Compile调用了MapleCombCompiler::Compile。其实CompilerFactory::Compile不仅仅调用了MapleCombCompiler::Compile,还调用了Jbc2MplCompiler和MplcgCompiler的Compile方法。
本篇将对Compiler体系进行分析:
Jbc2MplCompiler、MapleCombCompiler和MplcgCompiler这三个类,都是Compiler的子类。根据它们在src/maple_driver/defs/supported_compilers.def中的内容:
ADD_COMPILER("jbc2mpl" ,Jbc2MplCompiler)ADD_COMPILER("me" ,MapleCombCompiler)ADD_COMPILER("mpl2mpl" ,MapleCombCompiler)ADD_COMPILER("mplcg" ,MplcgCompiler)
它们对应着名为jbc2mpl、me、mpl2mpl和mplcg的编译器。上述几个Compiler的类的定义都在src/maple_driver/include/compiler.h文件中。其具体的实现则位于src/maple_driver/src/目录之下;Compiler类的实现位于compiler.cpp,Jbc2MplCompiler类的实现位于jbc2mpl_compiler.cpp,MapleCombCompiler的实现位于maple_comb_compiler.cpp,MplcgCompiler的实现位于mplcg_compiler.cpp。
Compiler及其三个子类,都有一个重要的方法Compile()。CompilerFactory::Compile就是根据选项挨个调用Compiler三个子类的Compile方法。但是,我们仔细查看代码,会发现只有MapleCombCompiler重写了Compile方法:
ErrorCode MapleCombCompiler::Compile(const MplOptions &options, MIRModulePtr &theModule) {MemPool *optMp = mempoolctrler.NewMemPool("maplecomb mempool");std::string fileName = GetInputFileName(options);theModule = new MIRModule(fileName.c_str());int nErr = 0;MeOptions *meOptions = nullptr;Options *mpl2mplOptions = nullptr;auto iterMe = std::find(options.runningExes.begin(), options.runningExes.end(), kBinNameMe);if (iterMe != options.runningExes.end()) {meOptions = MakeMeOptions(options, optMp);}auto iterMpl2Mpl = std::find(options.runningExes.begin(), options.runningExes.end(), kBinNameMpl2mpl);if (iterMpl2Mpl != options.runningExes.end()) {mpl2mplOptions = MakeMpl2MplOptions(options, optMp);}LogInfo::MapleLogger() << "Starting mpl2mpl&mplme" << std::endl;PrintCommand(options);DriverRunner runner(theModule, options.runningExes, mpl2mplOptions, fileName, meOptions, fileName, fileName, optMp,options.timePhases, options.genMemPl);nErr = runner.Run();if (mpl2mplOptions != nullptr) {delete mpl2mplOptions;mpl2mplOptions = nullptr;}if (meOptions != nullptr) {delete meOptions;meOptions = nullptr;}mempoolctrler.DeleteMemPool(optMp);return nErr == 0 ? ErrorCode::kErrorNoError : ErrorCode::kErrorCompileFail;}
这就是我们分析它调用了DriverRunner::Run()的地方。也就是说MapleCombCompiler的具体执行,就是我们前面分析过的通过DriverRunner::Run(),然后最终调用各个phase的过程。
Jbc2MplCompiler和MplcgCompiler自身本没有实现Compiler方法,。Jbc2MplCompiler实现了GetBinName、GetBinNames、GetDefaultOptions、GetTmpFilesToDelete和GetFinalOutputs方法。MplcgCompiler实现了GetDefaultOptions、GetBinName、GetBinNames和GetInputFileName方法。那么,CompilerFactory::Compile在去调用Jbc2MplCompiler和MplcgCompiler的Compile方法的时候,就会去调用其父类Compiler的Compile方法。Compiler的Compile方法的实现为:
ErrorCode Compiler::Compile(const MplOptions &options, MIRModulePtr &theModule) {MPLTimer timer = MPLTimer();LogInfo::MapleLogger() << "Starting " << this->GetName() << std::endl;timer.Start();std::string strOption = this->MakeOption(options);if (strOption.empty()) {return ErrorCode::kErrorInvalidParameter;}if (this->Exe(options, strOption) != 0) {return ErrorCode::kErrorCompileFail;}timer.Stop();LogInfo::MapleLogger() << this->GetName() + " consumed " << timer.Elapsed() << "s" << std::endl;return ErrorCode::kErrorNoError;}
其核心的执行代码,是通过
if (this->Exe(options, strOption) != 0)
去执行Exe方法。Exe的具体实现为:
const int Compiler::Exe(const MplOptions &mplOptions, const std::string &options) const {const std::string binPath = FileUtils::ConvertPathIfNeeded(this->GetBinPath(mplOptions) + this->GetBinName());return SafeExe::Exe(binPath, options);}
SafeExe::Exe是专门在src/maple_driver/include/safe_exe.h里定义的实现:
class SafeExe {public:/*** Current tool is for linux only*/static int Exe(const std::string &cmd, const std::string &args) {LogInfo::MapleLogger() << "Starting:" << cmd << args << '\n';int ret = ErrorCode::kErrorNoError;if (StringUtils::HasCommandInjectionChar(cmd) || StringUtils::HasCommandInjectionChar(args)) {LogInfo::MapleLogger() << "Error while Exe, cmd: " << cmd << " args: " << args << '\n';return -1;}std::vector<std::string> tmpArgs;StringUtils::Split(args, tmpArgs, ' ');// remove ' ' in vectorfor (auto iter = tmpArgs.begin(); iter != tmpArgs.end();) {if (*iter == " " || *iter == "") {iter = tmpArgs.erase(iter);} else {iter++;}}tmpArgs.insert(tmpArgs.begin(), cmd);// extra space for exe name and argsconst char **argv = new const char* [tmpArgs.size() + 1];// argv[0] is program nameargv[0] = cmd.c_str();// copy argsfor (int j = 0; j < tmpArgs.size(); ++j) {argv[j] = tmpArgs[j].c_str();}// end of arguments sentinel is NULLargv[tmpArgs.size()] = NULL;pid_t pid = fork();if (pid == 0) {// child processfflush(NULL);if (execv(cmd.c_str(), (char **)argv) < 0) {delete [] argv;exit(1);}} else {// parent processint status = -1;waitpid(pid, &status, 0);if (!WIFEXITED(status)) {LogInfo::MapleLogger() << "Error while Exe, cmd: " << cmd << " args: " << args << '\n';ret = ErrorCode::kErrorCompileFail;} else if (WEXITSTATUS(status) != 0) {LogInfo::MapleLogger() << "Error while Exe, cmd: " << cmd << " args: " << args << '\n';ret = ErrorCode::kErrorCompileFail;}}delete [] argv;return ret;return -1;}};
我们通过这些代码其实可以看明白,这是通过
if (execv(cmd.c_str(), (char **)argv) < 0) {
去执行可执行文件了。这也就解释了,我们之前的疑惑,既然可执行文件maple的run选项可以添加jbc2mpl选项去调用jbc2mpl,为什么还有个独立的jbc2mpl。因为前者的jbc2mpl绕来绕去,最后还是调用的单独的可执行文件jbc2mpl。mplcg也是同样的情况,maple中的对mplcg的调用,最终还是调用了独立的可执行文件mplcg。这样我们就豁然开朗了,java2jar作为脚本,其实单独存在的。剩下的jbc2mpl、mplcg都是独立的可执行文件,maple虽然可以调用他们,但是也就是个调用关系了。
所以,方舟公布的四个可执行文件除了Java2jar是个脚本之外,方舟是尝试将maple作为一个统一的接口去面向用户的,所以可以通过它去调用jbc2mpl、mplcg。只不过目前看来,其使用还是有些不太方便,并且没有公开的文档,只能通过命令行的help和sample的build系统自己diy。同时,由于这次公布的范围的问题,jbc2mpl和mplcg的代码没有公布,所以直接公布了可执行文件。
————总结—————
今天的学习笔记,解决了关于可执行文件的困惑,可以理清楚方舟的大致执行过程。万事万物,皆有其因,找到其根源,问题自然就迎刃而解了。
