Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Make the compiler sweat, chill in runtime by Si...

Make the compiler sweat, chill in runtime by Simon Lindholm

Malmö C++ User Group - Meeting 0x2 - June 26, 2018

https://www.meetup.com/Malmo-C-User-Group/events/251265413/

Modern C++ offers some powerful features for doing work in compile time instead of in runtime. We'll explore some of these features, for a faster, smaller and more correct runtime.

Simon Lindholm is an independent contractor, mostly working with Linux and various embedded platforms.

Ólafur Waage

June 26, 2018
Tweet

More Decks by Ólafur Waage

Other Decks in Programming

Transcript

  1. 7 Who am I? • Started programming BASIC on C64

    and Apple II • Borland C++ 3.0 ca. 1994 • Studied at LTH • Worked mostly with embedded stuf
  2. 8 Embedded systems • Limited memory • Limited CPU power

    • Important with correctness • Mostly dominated by C • Somewhat conservative attitude towards C++ (C with classes...)
  3. 9 The process leading up to compile-time trickery • “It

    would be really nice if it was possible to...” • “Is it possible?” • “It is!” • ... or “...well, sort of”
  4. 10 What’s this talk about? • Just go through a

    couple of these ideas • Nothing new under the sun • These ideas presented as inspiration
  5. 14 CRC32 • CRC = Cyclic Redundancy Check • Many

    diferent variations • 32 bit most common size • Robust against burst errors
  6. 16 CRC32: Polynomials • The algorithm is specified by a

    polynomial • A trillion diferent standards • Ethernet, ZIP, etc.: x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1 • ...or just 0xEDB88320 • Can just treat it as a magic number
  7. 17 CRC32: API • Basically just want a class: –

    Constructor: CRC32(uint32_t polynomial) – Method: add(const uint8_t * data, size_t N) – Method: uint32_t result()
  8. 21 CRC32 - Implementation 1 • Calls calc_crc_value() for each

    byte • Pure function - output only depends on the input • Only 256 possible inputs • → Precomputation table!
  9. 25 CRC32 - Implementation 2 • Table lookup instead of

    calc_crc_value() for each byte • Takes about 1K memory (256 * 4) • The code to generate the table also takes a small amount of memory • Less flexible - can’t choose polynomial at runtime (could get around this...)
  10. 29 CRC32 - Implementation 3 • Don’t have to generate

    the table in runtime • Gets rid of the code to generate the table • Very inflexible
  11. 38 CRC32 - Implementation 4 • Don’t have to generate

    the table in runtime • Gets rid of the code to generate the table • Pretty flexible • General strategy for any lookup table (sin, cos, etc.?)
  12. 40 C-style bufer handling • Pointer arithmetic: uint8_t * •

    memset(), memcpy(), memcmp() • Common when doing low-level stuf (raw access to pixel bufer, etc.) • Error prone • A lot of bounds checking can be had in compile time instead of runtime
  13. 41 Benefits of compile-time bounds checks • Catch errors earlier

    (compiler error) • Can skip those checks in runtime • Safer code • Faster code • Smaller code
  14. 51 The ownership classes • StaticBufer – As the name

    suggest, pretty static – Put it on the stack, or as static data • DynamicBufer – Essentially just wrapper of std::unique_ptr<uint8_t> – So, std::move() if you want to transfer ownership
  15. 52 The wrapper classes Bufer, ConstBufer, FixedBufer<>, FixedConstBufer<> • FixedBufer<>

    & FixedConstBufer<> – Single pointer (uint8_t* or const uint8_t*) • Bufer & ConstBufer – Pointer + std::size • Hence, they are meant for pass-by-value Do this! Not this!
  16. 53 Methods • take(I,N) & skip(I) Pointer arithmetic • set_zero(),

    fill_with(X) memset() • copy(dst, src) memcpy() • equals(buf1, buf2) memcmp() • operator<<(std::ostream&) [debugging..] • ... tons of overloads for these...
  17. 61 Copying & comparing • If both bufer types fixed,

    bounds checking happen at compile time • Otherwise, in runtime
  18. 63 Summary & reflections • These bufer classes by no

    means perfect • Should implement operator overloading so they behave the same as C-style pointers • Complete brain-fart to implement copy(), equals() etc. as standalone functions • Main point: Most of the bounds checks that can be done in compile time are being done in compile time.
  19. 65 Why in the world would you want to? •

    Data you want to integrate into your code somehow – Cross-language message specifications – Hardware configurations – etc. • JSON: Common, convenient, simple
  20. 66 Generate C++ code with script? • Depending on build

    system, can be messy to integrate • Adds requirement of Python etc for the build system • Actually pretty convenient most of the time • *BUT* aesthetically displeasing to have to resort to a separate language to do something that SHOULD be possible in pure C++
  21. 67 What’s really our goal here? • Parsing JSON is

    actually pretty simple. It’s really just: – Numbers (12, -34, 0.23, 1.3e-4, etc.) – Strings (“foo”, “bar”, “hey\nho!”) – Booleans (true / false) – Lists [ 1, 2, “foo”, [ ] ] – Dictionaries { “foo”: 129, “bar”: [1,2,3] } – null • ...but we want to do it in compile time now
  22. 68 What’s really our goal here? • Let’s not spend

    too much time on preamble • ...just dive in head first and see where we end up
  23. 69 Some limitations... • Template parameters are most often types

    (std::vector<int>) • ...but perfectly fine to use values as well (std::array<int,10>) • But not all values are welcome, we aren’t allowed to use strings as template arguments, for instance
  24. 72 Let’s combine a few things • Variadic templates •

    User-defined literals • ...and unfortunately a C-macro (yuck!)
  25. 74 We can print the strings easily (useful for debugging)

    ...but let’s do something a bit more general, (becuase we will end up wanting to print quite a lot of diferent types)
  26. 76 And now we can print our strings thusly: Before:

    After: Almost the same, but the $<> notation is trivial to extend to other types - and we’ll have a lot of them
  27. 77 How do we read the JSON data to begin

    with? • Ideally, we would just want to be able to read it as a regular file • Not possible in compile time - there are no constexpr I/O functions • We can almost use #include, but not quite • With can get something reasonably satisfactory using raw string literals
  28. 78 Alt 1. Embed the JSON directly into the C++

    code Look somewhat Ok, but we have to copy/paste our JSON data into our code - not nice...
  29. 79 Alt 2. #include the JSON data JSON somewhat more

    independent from the C+ + code, but we need it wrapped in R”( )”_js
  30. 80 meta::list • Skipping a ton of details here •

    But let’s look at a general list class/template we’ll be using quite a bit • Really just a wrapper for std::tuple, but with a bunch of helpers
  31. 87 With the two meta-classes list and str, we can

    start building a parser • The stuff we want to parse out is – Numbers (12, -34, 0.23, 1.3e-4, etc.) – Strings (“foo”, “bar”, “hey\nho!”) – Booleans (true / false) – Lists [ 1, 2, “foo”, [ ] ] – Dictionaries { “foo”: 129, “bar”: [1,2,3] } – null • (For simplicity, we won’t support floats - only integers)
  32. 92 Our building blocks for the data, will be (Dictionaries

    will be treated as just lists of key-value pairs)
  33. 93 Mapping JSON <--> types • So if we have

    some JSON data like this: • Then we want our resulting type to be something like this:
  34. 99 Next most basic parser: from_func (could do with a

    better name) So, if we only have some constexpr versions of the <cctype> functions...
  35. 100 Next most basic parser: from_func (could do with a

    better name) Then we can use our from_func as such: And we have equivalents of the simple regexes:
  36. 109 JSON: In summary • It’s sort of doable to

    parse JSON at compile time • This implementation is ridiculously slow • Don’t do stuf like this in real projects • ...but it does show that you CAN do a lot of things in compile time that might not have been previously possible