We know that -O
will produce the "behavior".
But, -O*
turns on a number of finer grain -f
optimization options.
I was curious as to which -f
actually was to "blame".
A list of -f
options can be found at: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
The specific optimization that produces the behavior is:
-ftree-bit-ccp
The documentation for it is:
Perform sparse conditional bit constant propagation on trees and propagate
pointer alignment information. This pass only operates on local scalar
variables and is enabled by default at -O1 and higher, except for -Og. It
requires that -ftree-ccp is enabled.
Starting out, I didn't know which -f
option was doing the optimization. So, I decided to apply the options one by one and rebuild/rerun the test program.
Being lazy, I didn't want to do this by hand. I wrote a [perl] script to pull the above .html
file, parse it, and apply the individual -f
options, one by one.
Side note: Ironically, this probably took longer than hand editing the .html
file to create a script, but it was fun ...
And, there have been times when I've wanted to know which -f
option was doing a given optimization in my own code, but I always punted.
The script is a bit crude, but it could probably be adapted and reused for other test programs in the future.
#!/usr/bin/perl
# gccblame -- decide which -f option causes issues
#
# options:
# "-A" -- specify __foo__ address (DEFAULT: FFF)
# "-arr" -- define __foo__ as array
# "-clean" -- clean generated files
# "-doc" -- show documentation
# "-f" -- preclean and force reload
# "-no" -- apply -fno-foobar instead of -ffoobar
# "-T<type>" -- specify __foo__ type (DEFAULT: int)
# "-url" -- (DFT: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html)
master(@ARGV);
exit(0);
# master -- master control
sub master
{
my(@argv) = @_;
# get command line options
optdcd(\@argv,
qw(opt_A opt_arr opt_clean opt_doc opt_f opt_no opt_T opt_url));
$opt_T //= "int";
$opt_A //= "FFF";
$opt_A =~ s/^0x//;
$opt_A = "0x" . $opt_A;
$opt_arr = $opt_arr ? "[]" : "";
$opt_url //= "https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html";
$root = "fopturl";
$fopt_ifile = clean("$root.html");
$fopt_ofile = clean("$root.txt");
$nul_c = clean("nul.c");
$dftlink = clean("./default.ld");
# compiled output
clean("foo.o");
clean("foo");
$tmp = clean("tmp.txt");
# clean generated files
if ($opt_clean or $opt_f) {
# get more files to clean
sysall(0);
foreach $file (sort(keys(%clean))) {
if (-e $file) {
printf("cleaning %s\n",$file);
unlink($file);
}
}
exit(0) if ($opt_clean);
}
# get the options documentation from the net
$fopturl = fopturl();
# parse it
foptparse(@$fopturl);
# create all static files
sysall(1);
# create linker scripts and test source file
dftlink();
###exit(0);
# start with just the -O option
dopgm($opt_no ? "-O3" : "-Og");
# test all -f options
dolist();
printf("\n");
docstat()
if ($opt_doc);
printf("all options passed!\n");
}
# optdcd -- decode command line options
sub optdcd
{
my(@syms) = @_;
my($argv);
my($arg);
my($sym,$val,$match);
$argv = shift(@syms);
# get options
while (@$argv > 0) {
$arg = $argv->[0];
last unless ($arg =~ /^-/);
shift(@$argv);
$match = 0;
foreach $sym (@syms) {
$opt = $sym;
$opt =~ s/^opt_/-/;
if ($arg =~ /^$opt(.*)$/) {
$val = $1;
$val =~ s/^=//;
$val = 1
if ($val eq "");
$$sym = $val;
$match = 1;
last;
}
}
sysfault("optdcd: unknown option -- '%s'\n",$arg)
unless ($match);
}
}
# clean -- add to clean list
sub clean
{
my($file) = @_;
my($self,$tail);
$self = filetail($0);
$tail = filetail($file);
sysfault("clean: attempt to clean script -- '%s'\n",$tail)
if ($tail eq $self);
$clean{$tail} = 1;
$file;
}
# dftlink -- get default linker script
sub dftlink
{
my($xfdst);
my($buf,$body);
my($grabflg);
my($lno);
# build it to get default link file
$code = doexec("gcc","-o","/dev/null",$nul_c,
"-v","-Wl,--verbose",">$dftlink","2>&1");
exit(1) if ($code);
# get all messages
$body = fileload($dftlink);
# split off the linker script from all the verbose messages
open($xfdst,">$dftlink");
while (1) {
$buf = shift(@$body);
last unless (defined($buf));
if ($grabflg) {
last if ($buf =~ /^=======/);
print($xfdst $buf,"\n");
++$lno;
}
# get starting section and skip the "=======" line following
if ($buf =~ /^using internal linker script:/) {
$grabflg = 1;
shift(@$body);
}
}
close($xfdst);
printf("dftlink: got %d lines\n",$lno);
exit(1) if ($lno <= 0);
}
# sysall -- extract all files
sub sysall
{
my($goflg) = @_;
my($xfsrc,$xfdst,$buf);
my($otail,$ofile);
$xfsrc = sysdata("gccblame");
while ($buf = <$xfsrc>) {
chomp($buf);
# apply variable substitution
$buf = subenv($buf);
# start new file
if ($buf =~ /^%\s+(\S+)$/) {
$otail = $1;
# add to list of files to clean
clean($otail);
next unless ($goflg);
close($xfdst)
if (defined($ofile));
$ofile = $otail;
printf("dftlink: creating %s ...\n",$ofile);
open($xfdst,">$ofile") or
sysfault("dftlink: unable to open '%s' -- $!\n",$ofile);
next;
}
print($xfdst $buf,"\n")
if (defined($ofile));
}
close($xfdst)
if (defined($ofile));
}
# fileload -- load up file contents
sub fileload
{
my($file) = @_;
my($xf);
my(@data);
open($xf,"<$file") or
sysfault("fileload: unable to open '%s' -- $!\n",$file);
@data = <$xf>;
chomp(@data);
close($xf);
\@data;
}
# fopturl -- fetch and convert remote documentation file
sub fopturl
{
my($sti,$sto);
my($data);
# get GCC's optimization options from remote server
$sti = _fopturl($fopt_ifile,"curl","-s",$opt_url);
# convert it to text
$sto = _fopturl($sti,$fopt_ofile,"html2text",$fopt_ifile);
# read in the semi-clean data
$data = fileload($fopt_ofile);
$data;
}
# _fopturl -- grab data
sub _fopturl
{
my(@argv) = @_;
my($sti);
my($ofile);
my($sto);
$ofile = shift(@argv);
if (ref($ofile)) {
$sti = $ofile;
$ofile = shift(@argv);
}
else {
$sti = {};
}
while (1) {
$sto = sysstat($ofile);
if (ref($sto)) {
last if ($sto->{st_mtime} >= $sti->{st_mtime});
}
$code = doexec(@argv,">$tmp");
exit(1) if ($code);
msgv("fopturl: RENAME",$tmp,$ofile);
rename($tmp,$ofile) or
sysfault("fopturl: unable to rename '%s' to '%s' -- $!\n",
$tmp,$ofile);
}
$sto;
}
# foptparse -- parse and cross reference the options
sub foptparse
{
local(@argv) = @_;
local($buf);
local($env);
my(%uniq);
$env = "xO";
while (1) {
$buf = shift(@argv);
last unless (defined($buf));
if ($buf =~ /^`-f/) {
$env = "xB";
}
# initial are:
# -ffoo -fbar
if (($env eq "xO") and ($buf =~ /^\s*-f/)) {
_foptparse(0);
next;
}
# later we have:
# `-ffoo`
# doclines
if (($env eq "xB") and ($buf =~ /^`-f/)) {
_foptparse(1);
next;
}
if ($buf =~ /^`-O/) {
printf("foptparse: OLVL %s\n",$buf);
next;
}
}
xrefuniq("xO","xB");
xrefuniq("xB","xO");
foreach $opt (@xO,@xB) {
next if ($uniq{$opt});
$uniq{$opt} = 1;
push(@foptall,$opt);
}
}
sub _foptparse
{
my($fix) = @_;
my($docsym,$docptr);
$buf =~ s/^\s+//;
$buf =~ s/\s+$//;
if ($fix) {
$buf =~ s/`//g;
}
printf("foptparse: %s %s\n",$env,$buf);
@rhs = split(" ",$buf);
foreach $buf (@rhs) {
next if ($env->{$buf});
$env->{$buf} = 1;
push(@$env,$buf);
$docsym //= $buf;
}
# get documentation for option
if ($fix) {
$docptr = [];
$foptdoc{$docsym} = $docptr;
while (1) {
$buf = shift(@argv);
last unless (defined($buf));
# put back _next_ option
if ($buf =~ /^`/) {
unshift(@argv,$buf);
last;
}
push(@$docptr,$buf);
}
# strip leading whitespace lines
while (@$docptr > 0) {
$buf = $docptr->[0];
last if ($buf =~ /\S/);
shift(@$docptr);
}
# strip trailing whitespace lines
while (@$docptr > 0) {
$buf = $docptr->[$#$docptr];
last if ($buf =~ /\S/);
pop(@$docptr);
}
}
}
# xrefuniq -- get unique set of options
sub xrefuniq
{
my($envlhs,$envrhs) = @_;
my($sym,$lhs,$rhs);
while (($sym,$lhs) = each(%$envlhs)) {
$rhs = $envrhs->{$sym};
next if ($rhs);
printf("xrefuniq: %s %s\n",$envlhs,$sym);
}
}
# dolist -- process all -f options
sub dolist
{
my($foptnew);
foreach $foptnew (@foptall) {
dopgm($foptnew);
}
}
# dopgm -- compile, link, and run the "foo" program
sub dopgm
{
my($foptnew) = @_;
my($code);
$foptnew =~ s/^-f/-fno-/
if ($opt_no);
printf("\n");
printf("NEWOPT: %s\n",$foptnew);
# show documentation
docshow($foptnew);
{
# compile to .o -- this proves that the compiler is changing things
# and _not_ some link time optimization
$code = doexec(qw(gcc -Wall -Wextra -Werror foo.c -c),
@foptlhs,$foptnew);
# the source should always compile cleanly -- if not, the option is
# just bad/unknown
if ($code) {
printf("IGNORING: %s\n",$foptnew);
###pop(@foptlhs);
last;
}
push(@foptlhs,$foptnew);
# build the final program
$code = doexec(qw(gcc -Wall -Wextra -Werror foo.o -o foo),
"-T","link.ld");
exit(1) if ($code);
# run the program
$code = doexec("./foo");
# if it runs cleanly, we have the bad option
if ($opt_no) {
$code = ! $code;
}
if ($code) {
printf("\n");
printf("BADOPT: %s\n",$foptnew);
exit(1);
}
}
}
# docshow -- show documentation
sub docshow
{
my($foptnew) = @_;
my($docptr,$docrhs,$doclhs,$doclen);
my(@opt);
{
last unless ($opt_doc);
$docptr = $foptdoc{$foptnew};
last unless (ref($docptr));
push(@opt,"-pre=#","#");
foreach $docrhs (@$docptr) {
$doclen = length($docrhs);
# remember max length
if ($doclen > $docmax) {
$docmax = $doclen;
printf("NEWMAX: %d\n",$docmax);
}
$dochisto[$doclen] += 1;
if ($doclen > 78) {
msgv(@opt,split(" ",$docrhs));
}
else {
msgv(@opt,$docrhs);
}
}
}
}
# docstat -- show documentations statistics
sub docstat
{
my($curlen);
my($cnt);
printf("DOCMAX: %d\n",$docmax);
$curlen = -1;
foreach $cnt (@dochisto) {
++$curlen;
next if ($cnt <= 0);
$ptr = $lookup[$cnt];
$ptr //= [];
$lookup[$cnt] = $ptr;
push(@$ptr,$curlen);
}
$cnt = -1;
foreach $ptr (@lookup) {
++$cnt;
next unless (ref($ptr));
msgv("DOCLEN: $cnt",@$ptr);
}
}
# doexec -- execute a program
sub doexec
{
my(@argv) = @_;
my($cmd);
my($code);
msgv("doexec: EXEC",@argv);
$cmd = join(" ",@argv);
system($cmd);
$code = ($? >> 8) & 0xFF;
$code;
}
# filetail -- get file tail
sub filetail
{
my($file) = @_;
$file =~ s,.*/,,g;
$file;
}
# msgv -- output a message
sub msgv
{
my(@argv) = @_;
local($opt_pre);
my($seplen);
my($rhs);
my($prenow);
my($lhs);
my($lno);
optdcd(\@argv,qw(opt_pre));
$opt_pre //= "+";
$opt_pre .= " ";
foreach $rhs (@argv) {
$seplen = (length($lhs) > 0);
if ((length($prenow) + length($lhs) + $seplen + length($rhs)) > 80) {
printf("%s%s\n",$prenow,$lhs);
undef($lhs);
$prenow = $opt_pre;
++$lno;
}
$lhs .= " "
if (length($lhs) > 0);
$lhs .= $rhs;
}
if (length($lhs) > 0) {
printf("%s%s\n",$prenow,$lhs);
++$lno;
}
$lno;
}
# subenv -- substitute environment
sub subenv
{
my($rhs) = @_;
my($ix);
my($sym,$val);
my($lhs);
while (1) {
$ix = index($rhs,'${');
last if ($ix < 0);
$lhs .= substr($rhs,0,$ix);
$rhs = substr($rhs,$ix + 2);
$ix = index($rhs,"}");
$sym = substr($rhs,0,$ix);
$rhs = substr($rhs,$ix + 1);
$val = $$sym;
sysfault("subenv: unknown symbol -- '%s'\n",$sym)
unless (defined($val));
$lhs .= $val;
}
$lhs .= $rhs;
$lhs;
}
# sysdata -- locate the __DATA__ unit
sub sysdata
{
my($pkgsrc) = @_;
my($xfsrc,$sym,$pos);
$pkgsrc //= caller();
{
$sym = $pkgsrc . "::DATA";
$xfsrc = \*$sym;
# remember the starting position -- since perl doesn't :-(
$pos = \$sysdata_rewind{$pkgsrc};
$$pos = tell($xfsrc)
unless (defined($$pos));
last if (seek($xfsrc,$$pos,0));
sysfault("sysdata: seek fault pkgsrc='$pkgsrc' pos=$$pos -- $!\n");
}
return wantarray ? ($xfsrc,$sym,$$pos) : $xfsrc;
}
# sysfault -- fault
sub sysfault
{
printf(@_);
exit(1);
}
# sysstat -- get file status
sub sysstat
{
my($file) = @_;
my(@st);
my($st);
@st = stat($file);
if (@st > 0) {
$st = {};
($st->{st_dev},
$st->{st_ino},
$st->{st_mode},
$st->{st_nlink},
$st->{st_uid},
$st->{st_gid},
$st->{st_rdev},
$st->{st_size},
$st->{st_atime},
$st->{st_mtime},
$st->{st_ctime},
$st->{st_blksize},
$st->{st_blocks}) = @st;
}
$st;
}
package gccblame;
__DATA__
% foo.c
#include <stdint.h>
#include <stdio.h>
extern ${opt_T} __foo__${opt_arr};
#define IPTR(_adr) ((intptr_t) _adr)
#define ADDR_MASK IPTR(0xFFF)
#define EXPECTED_ADDR IPTR(${opt_A})
#define FOO_ADDR (IPTR(&__foo__) & ADDR_MASK)
#define FOO_ADDR_IS_EXPECTED() (FOO_ADDR == EXPECTED_ADDR)
int
main(void)
{
printf("__foo__ at %p\n", &__foo__);
printf("FOO_ADDR=0x%lx\n", FOO_ADDR);
printf("EXPECTED_ADDR=0x%lx\n", EXPECTED_ADDR);
int ok = FOO_ADDR_IS_EXPECTED();
if (ok) {
printf("***Expected ***\n");
}
else {
printf("### UNEXPECTED ###\n");
}
return ! ok;
}
% ${nul_c}
int
main(void)
{
return 0;
}
% link.ld
INCLUDE ${dftlink}
__foo__ = ${opt_A};