vlambda博客
学习文章列表

方舟编译器学习笔记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; }
#if __linux__ or __linux std::vector<std::string> tmpArgs; StringUtils::Split(args, tmpArgs, ' '); // remove ' ' in vector for (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 args const char **argv = new const char* [tmpArgs.size() + 1]; // argv[0] is program name argv[0] = cmd.c_str(); // copy args for (int j = 0; j < tmpArgs.size(); ++j) { argv[j] = tmpArgs[j].c_str(); } // end of arguments sentinel is NULL argv[tmpArgs.size()] = NULL; pid_t pid = fork(); if (pid == 0) { // child process fflush(NULL); if (execv(cmd.c_str(), (char **)argv) < 0) { delete [] argv; exit(1); } } else { // parent process int 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;#else return -1;#endif }};

我们通过这些代码其实可以看明白,这是通过

 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的代码没有公布,所以直接公布了可执行文件。

————总结—————

今天的学习笔记,解决了关于可执行文件的困惑,可以理清楚方舟的大致执行过程。万事万物,皆有其因,找到其根源,问题自然就迎刃而解了。