Monday 30 September 2024

C++ on Sea video for "Building on clang-tidy to move from printf-style to std::print-style logging and beyond" posted

cpponsea-clang-tidy

Back in July 2024 I was lucky enough to talk about my recent work implementing and applying a clang-tidy check at C++ on Sea 2024 in Folkestone. I think that the talk went well and the C++ on Sea team have now posted a recording which I haven’t dared watch myself. The slides are also available.

I’m expecting to present the talk for the online track at Meeting C++ on November 15th 2024.

Monday 23 September 2024

My Thirty Years of Linux

thirty-years-of-linux

I first installed Linux in the final week of September 1994 on my 486SX 25MHz computer, which for reasons that will take too long to explain had 20 megabytes of memory. This was a huge amount for the time when four megabytes was normal for such machines. During the summer before then I was developing 16-bit Windows 3.1 software using Visual C++ and Visual Basic.

I know I installed Slackware from a magazine cover CDROM (as they were known at the time), but I’ve been unable to find out which one. I’m reasonably sure that it was Slackware 1.2 from March 1994 - at least I think that the kernel version was 1.0.8 and that version would probably match the lead times for the CDROMs that I would have received by then.

I didn’t have access to the Internet at that point so all I could do was play around with the software included in the distribution. My editor of choice at that point was Microemacs. I had first encountered Microemacs on my Sinclair QL. On that machine it was rather too slow to actually use as an editor so I stuck with QD. I do remember being very impressed at how I could split the frame showing a single buffer into two windows and text I typed in one would lazily appear in the other though. It was a simpler time! Once I got the aforementioned 486SX PC I started using Microemacs under DOS as my primary editor, even though I was mostly running it full screen under Windows 3.1. I believe that I actually took the source code for Microemacs 3.9 from my QL C68 discs across to my new Linux installation to compile it there. This would mean that I never compiled Microemacs from source on DOS myself and only used binaries downloaded for me from CIX. That might be false though and I actually had the source for a later DOS version and compiled that. To my great surprise it compiled and ran on Linux quite happily!

I played around for a while. I remember trying to run XFree86 but not knowing about startx so all I got was a blank screen with an X mouse pointer moving around when I ran the X binary directly.

My solo investigations of Linux were curtailed by me going away to university the following weekend and I couldn’t take my computer with me.

When I got to university I was faced with the university computer services department’s X terminals and remember being amazed in my first few days how I could use Mosaic to view web pages about Linux for free! I discovered the Linux Documentation Project and made use of the computer services line printer to print documentation that I could take back to my room and read at my leisure. I started learning a lot about Unix.

I believe that Microemacs was available on the university Computing Services Unix machines. Either that or I was able to download its source code using FTP and compile it myself quite quickly. (I remember that the editor recommended by lecturers in the Computer Science department was Jove at that point - another Emacs-like editor that I didn’t use much.) I think that I moved to GNU Emacs relatively quickly, but the default incremental search confused me greatly and I rebound the C-s to non-incremental search for a good while! It wasn’t long before I started using XEmacs like the other cool kids (which continued until I noticed that Emacs had improved greatly in about 2006.)

Being on a joint Maths and Computer Science degree course meant that I was mostly doing Maths with only a small amount of Computer Science. This meant spending a lot of time up Gibbet Hill in the Maths department which had a small computer room with about six Sun Sparcstation 2s in it. Maths students in higher years hanging out in that room helped me to improve my configuration and I started using vtwm rather than the default twm X window manager and tinkered with its configuration myself. I later moved to fvwm 1 and later fvwm2. I also realised that there wasn’t enough Computer Science in my course and I switched to just Computer Science course from the second year.

I remember returning home one weekend during the term and working on a program that would eject the CD in the CD drive connected to my SoundBlaster 16. I think that I also messed around with SVGATextMode to use the 132x30 text-mode resolution on my Trident graphics card that I had previously used in Windows full-screen DOS sessions.

I continued tinkering with Linux over the Christmas holiday and when I went back to university I was able to take my computer with me to use in my university accommodation. Halls didn’t provide Internet connections back then. I remember trying to persuade the university that this would be a good idea in the hope that they would do so in time for my third year when I stood a chance of being back on campus. I failed in that. Having access to the Internet through the university meant that I could download source code to build and install on my Linux box but I had to carry it on 3.5 inch floppy disks back and forth to my Linux PC. I had to learn how to split tarballs across multiple floppies and cope with the common occurrence of being unable to read files written on the Sun floppy drives on my PC. This was a common problem for others too and no-one ever had a good explanation for it.

At some points during the first year I upgraded to newer Slackware versions and compiling my own newer 1.1.x development kernels. By the second year I had started running the 1.3.x development kernels and remember being excited as the 2.0 kernel approached. I reinstalled Slackware again from scratch to migrate from a.out to ELF format binaries.

Access to the Internet meant access to the comp.os.linux.* USENET hierarchy which I participated in eagerly. Borrowing the O’Reilly books from the library I learnt how to run BIND for DNS, INN for news and how to tweak my XF86Config to get the best resolution at a sensible refresh rate out of my monitor. Along with my housemate in the second year we set up an IP network and we managed to share his dial-up Internet connection to a certain extent. It was set to dial up automatically in the early morning and if I left my computer on over night email would arrive at it and the beep of xbiff would wake me up! This was a time when you could still forward email through another machine by using % in the local part!

Over those three years at university I learnt an awful lot about Linux but when I got back I home I knew that my Slackware install that I’d installed newer versions of INN, the kernel and various other packages on wasn’t really sustainable. I had heard about Debian and decided to complete reinstall using that. I must have ordered a CD containing Debian 1.3 “Bo” from The Linux Emporium. I think that I upgraded to Debian 2.0 “Hamm” slightly by accident because by that point my Linux PC was connected to the Internet permanently, though rather slowly. By that point I was working on the empeg Car MP3 player and Linux was fully entrenched in my life both in the products I was working on and the operating system I was using to build them.

I had no idea when I first installed Linux thirty years ago that it would influence my career from that point onwards. I’m glad I did!

Saturday 21 September 2024

New modernize-use-std-format clang-tidy check and modernize-use-std-print improvements in Clang 19

clang-tidy-modernize-use-std-format

Clang 19 has recently been released. It includes my new modernize-use-std-format check and fixes a bug in the modernize-use-std-print check that I added in Clang 17. (I also fixed another bug in the intervening Clang 18 release.)

The new modernise-use-std-format check by default turns calls to absl::StrFormat into calls to std::format. For example, it turns:

return absl::StrFormat("The %s is %d", description.c_str(), value);

into

return std::format("The {} is {}", description, value);

The full documentation for the check is available on the clang-tidy web site.

Although the default behaviour of the check is to replace calls to absl::StrFormat, many codebases have their own “wrap snprintf to return a std::string” function. Ours was called strprintf.

Given a file named example.cpp containing:

#include <string>

extern std::string strprintf(const char *format, ...);

std::string format_row(int quantity, const std::string &name, double mass, const std::string &description)
{
  return strprintf("| %3d | %10s | %6.3fkg | %-30s |\n", quantity, name.c_str(), mass, description.c_str());
}

int main()
{
  puts(format_row(4, "carrots", 0.25, "Long orange things").c_str());
  puts(format_row(9, "sprouts", 0.1, "Roundish green things").c_str());
}

then running

clang-tidy '-checks=-*,modernize-use-std-format' \
    -config="{CheckOptions: { modernize-use-std-format.StrFormatLikeFunctions: '::strprintf;' }}" \
    -fix example.cpp -- -std=c++20

will result in the file being rewritten to:

#include <format>
#include <string>                                                                                              'format' file not found [pp_file_not_found]

extern std::string strprintf(const char *format, ...);

std::string format_row(int quantity, const std::string &name, double mass, const std::string &description)
{
  return std::format("| {:3} | {:>10} | {:6.3f}kg | {:30} |\n", quantity, name, mass, description);
}

int main()
{
  puts(format_row(4, "carrots", 0.25, "Long orange things").c_str());
  puts(format_row(9, "sprouts", 0.1, "Roundish green things").c_str());
}

The check has done several things:

  1. The <format> header is now included. (The declaration of strprintf has not been removed since it may still be used in ways that could not be converted.)
  2. The strprintf call has been replaced with std::format.
  3. The format string has been rewritten to use the new C++ format language.
  4. The now-unnecessary calls to std::string::c_str() for name and description have been removed.

If you aren’t able to use C++20 yet then the check can be told to use fmt::format from the {fmt} library instead with something like:

clang-tidy '-checks=-*,modernize-use-std-format' \
    -config="{CheckOptions: { \
      modernize-use-std-format.StrFormatLikeFunctions: '::strprintf;', \
      modernize-use-std-format.ReplacementFormatFunction: 'fmt::format', \
      modernize-use-std-format.FormatHeader: '<fmt/core.h>' }}" \
    -fix example.cpp

You can play with the modernize-use-std-format and modernize-use-std-print checks using the excellent Compiler Explorer.

Thanks to everyone who reviewed and made suggestions for the new check and bug fixes. I have several further improvements to these checks in development that I hope will land in time for Clang 20.

Tuesday 18 June 2024

Open Group Base Specifications Issue 8 (aka POSIX) published

A new version of the POSIX standard has recently been published. This includes a new set of thread functions originally proposed by me so that I could implement them in Glibc.

I proposed these functions so that I could fix the implementation of std::condition_variable::wait_for and std::condition_variable::wait_until to work correctly when the system clock is warped. This can happen particularly at boot time on devices that lack a real time clock. You can learn more about this problem in my earlier blog post and about how it is fixed in GCC 10 and later provided you have Glibc 2.30 or later. I ended up proposing a whole set of functions to fix the problem for mutexes and read-write locks too along with adding new non-standard pthread_clockjoin_np function.

It’s now nearly a decade since I first started trying to fix this problem so it is great to see the new standard version be published. Looking at the search results for pthread_cond_clockwait I didn’t really realise how annoying it would be for people attempting to run binaries compiled against new C libraries with older versions.

Thanks to everyone who reviewed my text and code changes and did the hard work of publishing a new standard.

Wednesday 20 September 2023

New modernize-use-std-print clang-tidy check in Clang 17

clang-tidy-modernize-use-std-print

Clang 17 has recently been released. It includes the new modernize-use-std-print check, which is the culmination of work I started two years ago. In short, it turns lines like:

  fprintf(stderr, "The %s is %3d\n", description.c_str(), value);

into:

  std::println(stderr, "The {} is {:3}", description, value);

The full documentation for the check is available on the clang-tidy web site, but here are some examples. Given a file named example.cpp containing:

#include <cstdio>
#include <string>

void print_row(int quantity, const std::string &name, double mass, const std::string &description)
{
  printf("| %3d | %10s | %6.3fkg | %-30s |\n", quantity, name.c_str(), mass, description.c_str());
}

int main()
{
  print_row(4, "carrots", 0.25, "Long orange things");
  print_row(9, "sprouts", 0.1, "Roundish green things");
  print_row(60, "peas", 0.15, "Round green things");
  print_row(10, "beans", 0.05, "Long green things");
}

then running

clang-tidy '-checks=-*,modernize-use-std-print' -fix example.cpp -- -std=c++23

will result in the file being rewritten to:

#include <cstdio>
#include <print>
#include <string>

void print_row(int quantity, const std::string &name, double mass, const std::string &description)
{
  std::println("| {:3} | {:>10} | {:6.3f}kg | {:30} |", quantity, name, mass, description);
}

int main()
{
  print_row(4, "carrots", 0.25, "Long orange things");
  print_row(9, "sprouts", 0.1, "Roundish green things");
  print_row(60, "peas", 0.15, "Round green things");
  print_row(10, "beans", 0.05, "Long green things");
}

The check has done several things:

  1. The <print> header is now included. (The inclusion of <cstdio> has not been removed since it may be required for other reasons.)
  2. The printf call has been replaced with std::println.
  3. The \n has been removed from the end of the format string since std::println will add a newline automatically.
  4. The format string has been rewritten to use the new C++ format language.
  5. The now-unnecessary calls to std::string::c_str() have been removed.

Despite all this, the output is the same.

Of course, most of us aren’t lucky enough to be using C++23 yet. Helpfully, the author of the original std::print and std::format feature proposal has the excellent {fmt} library which can be used with earlier C++ versions (though sometimes with less functionality.) The check can be customised to use calls to the {fmt} equivalents instead. To make this easier, this time we’re going to create a .clang-tidy configuration file in the same directory as example.cpp since passing options on the command line is rather unwieldy:

Checks: -*, modernize-use-std-print
CheckOptions:
  modernize-use-std-print.PrintHeader: '<fmt/core.h>'
  modernize-use-std-print.ReplacementPrintFunction: 'fmt::print'
  modernize-use-std-print.ReplacementPrintlnFunction: 'fmt::println'

Now, we no longer need to enable C++23 when running clang-tidy, and it will automatically pick up the configuration:

clang-tidy -fix example.cpp --

results in:

#include <fmt/core.h>

#include <cstdio>
#include <string>

void print_row(int quantity, const std::string &name, double mass, const std::string &description)
{
  fmt::println("| {:3} | {:>10} | {:6.3f}kg | {:30} |", quantity, name, mass, description);
}

int main()
{
  print_row(4, "carrots", 0.25, "Long orange things");
  print_row(9, "sprouts", 0.1, "Roundish green things");
  print_row(60, "peas", 0.15, "Round green things");
  print_row(10, "beans", 0.05, "Long green things");
}

This is the same as before, except for the header inclusion and the use of fmt::println rather than std::println.

The check will only convert calls if it thinks that it can do so without changing the behaviour. However, in order to enforce this it’s necessary to enable the StrictMode feature. This ought to only affect corner cases, where you may actually want the change in behaviour anyway. Consider this code:

int count = -42;
printf("%u\n", count);

On the majority of modern machines this will print 4294967254 but either count being int or using %u may have been unintentional as GCC’s -Wformat doesn’t complain about this. Running:

clang-tidy '-checks=-*,modernize-use-std-print' -fix example.cpp -- -std=c++23

will yield:

int count = -42;
std::println("{}", count);

which will always print -42.

If you really want the same output as before then it is necessary to enable StrictMode with something like:

clang-tidy -config="{CheckOptions: [{key: StrictMode, value: true}]}" '-checks=-*,modernize-use-std-print' -fix example.cpp -- -std=c++23

(or the equivalent in the .clang-tidy file) which yields:

int i = -42;
std::println("{}", static_cast<unsigned int>(i));

When appropriately configured, the check is also capable of converting from fmt::printf and absl::PrintF. When doing so, no casts are added by StrictMode since those functions print based on the real type rather than the format string.

The implementation of the check in Clang v17.0.1 unfortunately contains a bug when supplying field widths and precision in arguments. The check will generate corrupt output (that probably won’t compile) when arguments need to be reordered in combination with the addition of cases (for StrictMode) or c_str() removal. For example:

std::string s{"world"};
printf("%*s\n", 20, s.c_str());

ought to be converted to:

std::string s{"world"};
std::println("{:>{}}", s, 20);

This bug has been fixed on the main branch and that fix ought to be present in Clang v18.0.0 when it is released.

Saturday 7 September 2019

std::condition_variable will work correctly with std::chrono::steady_clock in GCC 10!

In a previous blog post I lamented the lack of proper support for std::chrono::steady_clock in libstdc++’s implementation of std::condition_variable. I’m pleased to say that this problem will be solved when GCC 10 is released.
In the end, I managed to guide the “Invent a new pthreads function” solution through the Austin Group, although my proposed pthread_cond_timedwaitonclock function became simply pthread_cond_clockwait and was joined by pthread_mutex_clocklock, pthread_rwlock_clockwrlock, pthread_rwlock_clockrdlock and sem_clockwait along the way. These new functions are available in glibc v2.30.
Once pthread_cond_clockwait was available in glibc, it was straightforward to fix libstdc++’s std::condition_variable to use it.
Thanks to Adhemerval Zanella for reviewing the new glibc functions and Jonathan Wakely for reviewing the libstdc++ changes.

Sunday 17 February 2019

Making the SD activity LED work on the Raspberry Pi 3 AArch64 Debian Buster image

I tried installing Gunnar Wolf's Debian Buster Image on my Raspberry Pi 3. I found that the SD card activity LED didn't work by default. This can easily be fixed (until the next reboot) with:

echo mmc0 > /sys/class/leds/ACT/trigger

Attempting to set the trigger to the more generic disk-activity didn't seem to work.