/gcc_potential_optimiser_bug

Demonstrator of what appears to be a GCC optimiser bug

Primary LanguageC

A small demonstrator of what appears to be a bug in GCC's optimiser that I can reproduce with g++ 7.2.0, 9.4.0, 12.3.0 and 13.2.0 .

make [CXX=C++ compiler] [INCLUDES=...] [LIBS=...] [OPTS=...] [DEFS=-DCREATE_GLOBAL_AFTER_STATICS]

(OPTS defaults to -O3)

The glitch occurs in this function from lmdb+++.h :

 /**
   * Closes this environment, releasing the memory map.
   *
   * @note this method is idempotent
   * @post `handle() == nullptr`
   */   
  void close() noexcept {
    std::cerr << __PRETTY_FUNCTION__ << " this=" << this << " handle=" << handle() << "\n";
    if (handle()) {
      lmdb::env_close(handle());
      _handle = nullptr;
//    std::cerr << " handle now " << handle();
    }
//     std::cerr << "\n";
  }

Unless I outcomment the 2 tracing expressions the _handle = nullptr; assignment appears to get optimised away by GCC but not by clang.

Here, lmdb+++.h is the header from https://github.com/bendiken/lmdbxx with just some tracing output added for this demonstrator.

On my systems:

> make -B INCLUDES=/opt/local/include LIBS=/opt/local/lib && lmdbhook 
g++ -std=c++11 -O3  -o lmdbhook -I/opt/local/include -L/opt/local/lib -Wl,-rpath,/opt/local/lib lmdbhook.cpp -llmdb
LMDBHook::LMDBHook() s_lmdbEnv=0x406298
lmdb::env::env(MDB_env*) this=0x406298 handle=0
lmdb::env::env(MDB_env*) this=0x7ffe6a341f58 handle=0x10d8020
lmdb::env::~env() this=0x7ffe6a341f58
void lmdb::env::close() this=0x7ffe6a341f58 handle=0
static bool LMDBHook::init() s_lmdbEnv=0x10d8020
mapsize=1048576 LZ4 state buffer:16384
LMDB instance is 0x406298
lmdb::env::~env() this=0x406298
void lmdb::env::close() this=0x406298 handle=0x10d8020
void lmdb::env_close(MDB_env*) env=0x10d8020
LMDBHook::~LMDBHook()
void lmdb::env::close() this=0x406298 handle=0x10d8020
void lmdb::env_close(MDB_env*) env=0x10d8020
*** Error in `lmdbhook': double free or corruption (!prev): 0x00000000010d8020 ***
Abort
> make -B CXX=clang++ INCLUDES=/opt/local/include LIBS=/opt/local/lib && lmdbhook
clang++ -std=c++11 -O3  -o lmdbhook -I/opt/local/include -L/opt/local/lib -Wl,-rpath,/opt/local/lib lmdbhook.cpp -llmdb
LMDBHook::LMDBHook() s_lmdbEnv=0x406290
lmdb::env::env(MDB_env *const) this=0x406290 handle=0
lmdb::env::env(MDB_env *const) this=0x7ffd1a043250 handle=0x465020
lmdb::env::~env() this=0x7ffd1a043250
void lmdb::env::close() this=0x7ffd1a043250 handle=0
static bool LMDBHook::init() s_lmdbEnv=0x465020
mapsize=1048576 LZ4 state buffer:16384
LMDB instance is 0x406290
lmdb::env::~env() this=0x406290
void lmdb::env::close() this=0x406290 handle=0x465020
void lmdb::env_close(MDB_env *const) env=0x465020
LMDBHook::~LMDBHook()
void lmdb::env::close() this=0x406290 handle=0

G++ without optimisation:

> make -B INCLUDES=/opt/local/include LIBS=/opt/local/lib OPT=-O0 && lmdbhook
g++ -std=c++11 -O0  -o lmdbhook -I/opt/local/include -L/opt/local/lib -Wl,-rpath,/opt/local/lib lmdbhook.cpp -llmdb
LMDBHook::LMDBHook() s_lmdbEnv=0x407278
lmdb::env::env(MDB_env*) this=0x407278 handle=0
lmdb::env::env(MDB_env*) this=0x7ffe07b9f9e8 handle=0x1309020
lmdb::env::~env() this=0x7ffe07b9f9e8
void lmdb::env::close() this=0x7ffe07b9f9e8 handle=0
static bool LMDBHook::init() s_lmdbEnv=0x1309020
mapsize=1048576 LZ4 state buffer:16384
LMDB instance is 0x407278
lmdb::env::~env() this=0x407278
void lmdb::env::close() this=0x407278 handle=0x1309020
void lmdb::env_close(MDB_env*) env=0x1309020
LMDBHook::~LMDBHook()
void lmdb::env::close() this=0x407278 handle=0

Options -O{1,2,3,fast} all trigger the issue, -O0, -Os, -Oz and -Og don't.

Evidently the issue doesn't get triggered when the global LMDBHook instance is created after initialising the LMDBHook static class variables:

> make -B INCLUDES=/opt/local/include LIBS=/opt/local/lib DEFS=-DCREATE_GLOBAL_AFTER_STATICS && lmdbhook
g++ -std=c++11 -O3 -DCREATE_GLOBAL_AFTER_STATICS -o lmdbhook -I/opt/local/include -L/opt/local/lib -Wl,-rpath,/opt/local/lib lmdbhook.cpp -llmdb
lmdb::env::env(MDB_env*) this=0x406298 handle=0
LMDBHook::LMDBHook() s_lmdbEnv=0x406298
lmdb::env::env(MDB_env*) this=0x7ffc6081a468 handle=0xa23020
lmdb::env::~env() this=0x7ffc6081a468
void lmdb::env::close() this=0x7ffc6081a468 handle=0
static bool LMDBHook::init() s_lmdbEnv=0xa23020
mapsize=1048576 LZ4 state buffer:16384
LMDB instance is 0x406298
LMDBHook::~LMDBHook()
void lmdb::env::close() this=0x406298 handle=0xa23020
void lmdb::env_close(MDB_env*) env=0xa23020
lmdb::env::~env() this=0x406298
void lmdb::env::close() this=0x406298 handle=0

An alternate env::close() function as I would write it also doesn't trigger any issues:

make -B INCLUDES=/opt/local/include LIBS=/opt/local/lib DEFS=-DALTERNATIVE_ENV_CLOSE && lmdbhook
c++ --version
c++ (MacPorts gcc12 12.3.0_4+cpucompat+libcxx) 12.3.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

c++ -std=c++11 -O3 -DALTERNATIVE_ENV_CLOSE -o lmdbhook -I/opt/local/include -L/opt/local/lib -Wl,-rpath,/opt/local/lib lmdbhook.cpp -llmdb
LMDBHook::LMDBHook() s_lmdbEnv=0x10e077738
lmdb::env::env(MDB_env*) this=0x10e077738 handle=0x0
lmdb::env::env(MDB_env*) this=0x7fff51b8a920 handle=0x7f8ab0c038f0
lmdb::env::~env() this=0x7fff51b8a920
void lmdb::env::close() this=0x7fff51b8a920 handle=0x0
static bool LMDBHook::init() s_lmdbEnv=0x7f8ab0c038f0
mapsize=1048576 LZ4 state buffer:16384
LMDB instance is 0x10e077738
lmdb::env::~env() this=0x10e077738
void lmdb::env::close() this=0x10e077738 handle=0x7f8ab0c038f0
void lmdb::env_close(MDB_env*) env=0x7f8ab0c038f0
LMDBHook::~LMDBHook()
void lmdb::env::close() this=0x10e077738 handle=0x0

*** Update 240330: testing lmdb::env::close() inside LMDBHook::init() shows that the same function works as expected when called while the programme is running normally...