Open3

GCCとClangの差を追い込む

okuokuokuoku

前回 https://zenn.dev/okuoku/scraps/d9c5ad4ddcc894 はコンパイル のバイナリに含まれるシンボルの差に注目していたが、そもそも、セマンティクス的な差が無いかを追い込んでやる必要がありそう。

Clangはgccのdrop-in-replacementである事を割と目標にしていて、例えば __gcc__ 等が事前定義に含まれる。(もっともClangはgccの特殊な機能(ローカル関数定義とか)には対応していないという差はある。まぁ"そんなもん使うな"で良いだろう。)

ちょっと困る可能性があるのは、 コンパイル自体は通るがコードの解釈は異なる というケースで、実際に動かしてみてハマった場合解決できる自信がない。

okuokuokuoku

predefine

C/C++では特に何も指定していなくても暗黙にdefineされるマクロが存在する。例えば、 __clang__ はClangであれば 1 にpredefineされるので、ソースコード上でclangにしか存在しない機能を使いたい場合に #ifdef できる。

Predefineはダンプする方法が存在するため、ダンプしたうえで比較できる。ダンプするには -dM オプション を付与して空のソースをプリプロセスする。

$ touch empty.cpp
g++ -dM -E empty.cpp | sort > gcc.i.cpp

Cygwin付属のgcc、clang、野良ビルドしたclangの3つで出力させてみた

  • gcc
#define __cplusplus 201703L
#define __PIC__ 1
#define __pic__ 1
#define __GXX_ABI_VERSION 1016
#define __INT_FAST16_TYPE__ long int
#define __INT_FAST32_TYPE__ long int
#define __WINT_TYPE__ unsigned int
  • clang
#define __cplusplus 201402L
#define __PIC__ 2
#define __pic__ 2
#define __GXX_ABI_VERSION 1002
#define _GNU_SOURCE 1
#define __INT_FAST16_TYPE__ short
#define __INT_FAST32_TYPE__ int
#define __WINT_TYPE__ int

比較してみると、gccでは __cplusplus がC++17になっているのに対し、clangはC++14になっている。つまり、何もオプションを指定しなかったときのデフォルトが異なっている。

_GNU_SOURCE はclang側にしかない。つまりclangではデフォルト( -std=gnu++14 )になっているが、gccでは -std=c++17 になっているということだろう。

PICとか型のサイズやsignednessが合ってないのはちょっと移植が不完全だな。。

とりあえずC++17に統一すると割と揃うようなので、これを基準にする。

https://gist.github.com/okuoku/1c8b7c75bd0a5d940c476ee24e07529c

okuokuokuoku

Pragmaの差

次に、

#include <iostream>
#include <future>

#ifdef __clang__
#pragma GCC warning "I'm clang"
#else
#pragma GCC warning "I'm gcc (maybe)"
#endif


int main()
{
  std::future<int> f1 = std::async([](){ return 42; });
  f1.wait();
  std::cout << "Magic number is: " << f1.get() << std::endl;
}

のプリプロセス結果の差分を確認してみた(赤がgcc、緑がclang)。

https://gist.github.com/okuoku/fee1387837b5ffc9ff81f4b3cd588f23

大体は構文解釈やホワイトスペースの差だが、int128のようなコンパイラ機能に起因する差もある。

... ちょっと気になるのは、 #pragma GCC visibility push(default) はプリプロセス結果に両方で残り (白色)、 #pragma GCC warning はclangにしかない (緑色) 点。そもそも #pragma はプリプロセサ指令なので後段に残す必要性って無いと思うんだけど。。

もちろん、通常のシチュエーションでは g++ -E したソースを g++ に掛けたら一括でコンパイルしたものと同じ挙動を示すべきなのでこの仕様自体は理解できるが、ではgccは何故 #pragma GCC warning を落としてしまうんだろうか。。?

gccは一応 cpp を別の実行ファイルとして提供しているので、 #pragma GCC warningcpp の出力に含めると cppcc1 の両方でwarningを出してしまうという点を心配しているんではないかという気はする。

$ cat warn.c
#pragma GCC warning "Oh"

$ cpp warn.c
# 0 "warn.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "warn.c"
warn.c:1:21: warning: Oh
    1 | #pragma GCC warning "Oh"
      |                     ^~~~
$ /usr/lib/gcc/x86_64-pc-cygwin/11/cc1 -o dummy.o warn.c
warn.c:1:21: warning: Oh
    1 | #pragma GCC warning "Oh"
      |                     ^~~~

Analyzing compilation unit
Performing interprocedural optimizations
 <*free_lang_data> {heap 1280k} <visibility> {heap 1280k} <build_ssa_passes> {heap 1280k} <opt_local_passes> {heap 1280k} <remove_symbols> {heap 1280k} <targetclone> {heap 1280k} <free-fnsummary> {heap 1280k} <emutls> {heap 1280k}Streaming LTO
 <whole-program> {heap 1280k} <fnsummary> {heap 1280k} <inline> {heap 1280k} <modref> {heap 1280k} <free-fnsummary> {heap 1280k} <single-use> {heap 1280k}Assembling functions:
 <simdclone> {heap 1280k}
Time variable                                   usr           sys          wall           GGC
 phase setup                        :   0.01 (100%)   0.00 (  0%)   0.02 ( 59%)  1302k ( 90%)
 phase opt and generate             :   0.00 (  0%)   0.02 (100%)   0.01 ( 28%)  2664  (  0%)
 callgraph ipa passes               :   0.00 (  0%)   0.02 (100%)   0.01 ( 24%)  2512  (  0%)
 ipa inlining heuristics            :   0.00 (  0%)   0.02 (100%)   0.00 (  3%)     0  (  0%)
 TOTAL                              :   0.01          0.02          0.03         1441k

ただ全ての #pragma がこの挙動に従っているのかは何とも言えない。