方舟编译器学习笔记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 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;
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的代码没有公布,所以直接公布了可执行文件。
————总结—————
今天的学习笔记,解决了关于可执行文件的困惑,可以理清楚方舟的大致执行过程。万事万物,皆有其因,找到其根源,问题自然就迎刃而解了。