2023-05更新
题外话:不知不觉,达成了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类型。
1 | static cl::OptionCategory cat("split-file Options"); |
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的风格是--long。
llvm/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库,就会触发经典错误:
1 | : CommandLine Error: Option 'help-list' registered more than once! |
在Clang里如果要设置cl::opt变量的值,可以用-mllvm -option=value。使用ld.lld/LLVMgold.so
Full/Thin
LTO也可以设置这些选项值,用-plugin-opt=-option=value(ld.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)。
1 | multiclass B<string name, string help1, string help2> { |
在C++源文件中,可以这样写:
1 | opt::InputArgList Args = parseOptions(argc, argv, IsAddr2Line, Saver, Tbl); |
Grouped short options
注意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容易引起混乱。
1 | % touch an ommand ':)' |
binutils 2.36有望deprecate grouped short options:)
Target-specific options
在Clang中,clang/include/clang/Driver/Options.td声明了通用选项和目标特定选项。
优点是,如果一个有用的功能在GCC中是机器特定的,在Clang中很容易将其实现为与目标无关的选项。
缺点是,如果它是一个固有的目标特定选项,很容易忘记为其他目标报告错误。
通常会有一个-Wunused-command-line-argument警告,但警告可能不够好。
例如,GCC的powerpc端口不支持-march=,但Clang错误地解析并忽略了它,导致-Wunused-command-line-argument警告。
https://reviews.llvm.org/D145141将警告改为错误。
为了防止上述问题,在处理clang/include/clang/Driver/Options.td中的目标特定选项时,我们应该添加标注兼容clang::driver::ToolChain的能力。
与getopt_long的比较
很多getopt_long用户用一个switch加大量case处理命令行选项,很容易弄出各种各样position
dependent行为。 对于大型build system,有时候搞不清楚compiler/linker
options是在什么地方添加的,有些position dependent行为挺讨厌的。
Clang中广泛使用的Args.getLastArgValue(..., ...)模式有一个限制。
对于接受值的选项,我们通常只验证最后一个选项,忽略之前的选项。
例如,clang -ftls-model=xxx -ftls-model=initial-exec和clang -mstack-protector-guard=xxx -mstack-protector-guard=global中非最后选项的无效选项值无法被检测到。