6

LLVM命令行选项的处理

 3 years ago
source link: http://maskray.me/blog/2020-10-03-llvm-command-line-processing
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

题外话:不知不觉,达成了llvm-project 1900 commits的成就。

LLVM中命令行选项的处理有两个库。

llvm/Support/ComandLine.h

文档参见https://llvm.org/docs/CommandLine.html

简单来说,用全局变量(llvm::cl::opt<type> var最常见,也有llvm::cl::list等)表示命令行选项。opt的构造函数会在一个全局的registry中注册这个命令行选项。 在main中调用llvm::cl::ParseCommandLineOptions(argc, argv, ...)解析命令行。 opt支持很多类型,如各种integer types、bool、std::string等,还支持自定义enum类型。

static cl::OptionCategory cat("split-file Options");

static cl::opt<std::string> input(cl::Positional, cl::desc("filename"),
cl::cat(cat));

static cl::opt<std::string> output(cl::Positional, cl::desc("directory"),
cl::value_desc("directory"), cl::cat(cat));

static cl::opt<bool> noLeadingLines("no-leading-lines",
cl::desc("Don't preserve line numbers"),
cl::cat(cat));

int main(int argc, const char **argv) {
cl::ParseCommandLineOptions(argc, argv, ...);
}

LLVM中有很多开发者使用的命令行选项,除了功能选项外,还有:

  • 对某一pass有较大改动,in-tree开发时为了防止衰退,设置一个预设为false的enable变量
  • 一段时间功能稳定,把预设值改为true。在某些场合下发现衰退的用户可以使用false作为workaround
  • 给一个pass提供更多输入,用于测试

这个库使用便捷,添加一个新选项只需要在一个局部文件中加一个变量。还提供了一些锦上添花的小功能,如推荐拼写接近的选项。 但命令行解析的定制性很弱。比如:

  • 一个cl::opt<bool>选项接受-v 0 -v=0 -v false -v=false -v=False等多种输入方式
  • 不便同时支持--long--no-long。偶尔有需求时的workaround是给--no-long也设置一个变量。假如要处理两个选项互相override,就要判断两个选项在命令行中的相对位置

面向用户的外部工具往往有这类定制需求。GNU getopt_long的风格是--longllvm/Support/ComandLine.h-long--long可以混用,很长一段时间不支持强制--

LLVM binary utilities (llvm-nm、llvm-objdump、llvm-readelf等)为了替代GNU binutils, 需要提供POSIX shell utilities风格的grouped short options (-ab表示-a -b)。 很长一段时间这个功能不被支持,困扰了想要迁移到LLVM binary utilities的用户。

另外cl::opt是singleton,也可以定义局部变量动态增加选项,但这种用法很少见(llvm-readobj和llvm-cov)。 还有个很奇特的用法,opt工具中legacy pass manager自动获取pass name列表,并注册大量全局选项。

为了防止错误,cl::opt不支持多次定义同一个选项。如果同时链接了shared object和archive两种LLVM库,就会触发经典错误:

: CommandLine Error: Option 'help-list' registered more than once!
LLVM ERROR: inconsistency in registered CommandLine options

在Clang里如果要设置cl::opt变量的值,可以用-mllvm -option=value。使用LLD/LLVMgold.so Full/Thin LTO也可以设置这些选项值,用-plugin-opt=-option=value(LLD也可用-mllvm)。

llvm/Option/OptTable.h

原先给Clang开发,后来移入llvm,被llvm-objcopy、lld、llvm-symbolizer等采用。 用一个domain-specific language (TableGen)描述选项,生成一个parser。解析过的选项组织成一个object,每个选项用一个integer表示。 检查一对预设值不定的boolean选项(--demangle --no-demangle)是否生效很容易:Args.hasFlag(OPT_demangle, OPT_no_demangle, !IsAddr2Line)

multiclass B<string name, string help1, string help2> {
def NAME: Flag<["--", "-"], name>, HelpText<help1>;
def no_ # NAME: Flag<["--", "-"], "no-" # name>, HelpText<help2>;
}

multiclass Eq<string name, string help> {
def NAME #_EQ : Joined<["--", "-"], name #"=">,
HelpText<help>;
def : Separate<["--", "-"], name>, Alias<!cast<Joined>(NAME #_EQ)>;
}

defm debug_file_directory : Eq<"debug-file-directory", "Path to directory where to look for debug files">, MetaVarName<"<dir>">;
defm default_arch : Eq<"default-arch", "Default architecture (for multi-arch objects)">;
defm demangle : B<"demangle", "Demangle function names", "Don't demangle function names">;
def functions : F<"functions", "Print function name for a given address">;
opt::InputArgList Args = parseOptions(argc, argv, IsAddr2Line, Saver, Tbl);

LLVMSymbolizer::Options Opts;
...
Opts.DebugFileDirectory = Args.getAllArgValues(OPT_debug_file_directory_EQ);
Opts.DefaultArch = Args.getLastArgValue(OPT_default_arch_EQ).str();
Opts.Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, !IsAddr2Line);
Opts.DWPName = Args.getLastArgValue(OPT_dwp_EQ).str();

注意GCC的命令行选项不支持grouped short options,因此Clang也没有需求。很长一段时间因为缺少这个功能限制了它的使用场景。我在2020年7月加入了grouped short options (D83639)。

轶闻:LLD采用这个库解析命令行选项。GNU ld实际上支持grouped short options,比如ld.bfd -vvv表示-v -v -v。我提出GNU ld实际上支持很多-long风格的选项,再支持grouped short options容易引起混乱。

% touch an ommand ':)'
% ld.bfd -you -can -ofcourse -use -this -Long -command -Line ':)'
:)

binutils 2.36有望deprecate grouped short options:)

再拓展一下,很多getopt_long用户用一个switch加大量case处理命令行选项,很容易弄出各种各样position dependent行为。 对于大型build system,有时候搞不清楚compiler/linker options是在什么地方添加的,有些position dependent行为挺讨厌的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK