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中非最後選項的無效選項值無法被檢測到。