@solarwinds
C++ Modules: the good, the bad, the
ugly
@solarwinds
Uladzislau Chekhareu
• Technical Lead Developer at Solarwinds
• Love cats, C++ and riding a bike
2
@solarwinds
Introduction
• Modules - one of the most anticipated things in C++ 20
• But it’s not discussed much comparing to some others!
• I’ll try to explain, what is this beast and how to deal with it
3
@solarwinds
The state of modules
• Paper p1103r2.pdf approved for merge into International Standard
• To some extent implemented by major compilers
• Examples from slides were tested using gcc
4
@solarwinds
What do we have now
Organizing code through headers
• Old good #include directives
• Preprocessed, not part of the language
• Simple text replacement
5
@solarwinds
What do we have now
Bad:
• Include guards
• Compilation times
• Weak physical encapsulation
• No isolation
• Sometimes include order matters
• ODR violations
6
@solarwinds
What do we have now
Good:
• Easy
• Parallel compilation
• Good support by build systems
• Established practices to prevent smelling code
7
@solarwinds
Modules goals
• Modular interfaces
• Physical encapsulation
• Isolation
• Compilation speed
8
@solarwinds
New terms
module, import - new special identifiers
export - reused and repurposed keyword
Module unit - translation unit that contains module declaration
Module - collection of module units with the same name
9
@solarwinds
Module units
10
@solarwinds
Primary interface unit
// hello.cxx
export module hello;
export int foo() {
return 42;
}
// valid module names
hello
hello.core
hello_util
hello101
// invalid module names
1111hello
.
.hello
// main.cxx
import hello;
int main() {
foo();
}
11
@solarwinds
Interface partition unit
// hello_world.cxx
export module hello:world;
export int bar() {
return 50;
}
// hello.cxx
export module hello;
export import :world;
export int foo() {
return 42;
}
12
@solarwinds
Implementation unit
// hello.cxx
export module hello;
export import :world;
export int foo();
// hello_impl.cxx
module hello;
int foo() {
return 42;
}
13
@solarwinds
Implementation partition unit
// hello_helper.cxx
module hello:helper;
int doStuff(int bar) {
return bar + 1;
}
// hello_impl.cxx
module hello;
import :helper;
int foo() {
return doStuff(42);
}
// hello_world.cxx
export module
hello:world;
import :helper;
export int bar() {
return doStuff(50);
}
14
@solarwinds
Client code
// main.cxx
import hello; // Ok
import hello:world; // Error, can’t explicitly import interface partition
import hello:helper; // Error, can’t import implementation partition
int main() {
foo(); // Ok
bar(); // Ok
doStuff(1); // Error
}
15
@solarwinds
Exporting and importing
16
@solarwinds
Export can only appear at namespace scope
export void foo() {} // Ok, global namespace
export template<typename T> // Ok
void foot(T input) { /* snip */ }
export enum class Errors { /* snip */ } // Ok, also allowed for old-style enums
namespace Bar {
export int foo = 42; // Ok, namespace scope
export struct FooBar { // Ok, also allowed for classes
int Baz = 2;
}
}
17
@solarwinds
class Gadget {
export Widget w; // illegal
};
void bar() { export int i = 5; } // bad
void baz(export std::string input) {} // please don't
template<export typename T> // stop
export class my_container{};
enum class Errors {
export InvalidArgument = 1000 // error
};
Export can only appear at namespace scope
18
@solarwinds
Convenient exporting
export namespace Goods { // exports all enclosing entities
int const Count = 5;
template<typename T>
void foot(T input) { /* snip */ }
}
namespace Goods {
void bar {} // NOT exported!
}
export {
auto Value = 10.f;
class Gadget {};
/* more exported entities */
}
19
@solarwinds
Can’t export entities with internal linkage
namespace {
export int foo = 42; // bad - anonymous namespace
export void bar() {}
export class MyClass() {};
}
export static int baz = 30; // bad - static
export static void foobar() {}
20
@solarwinds
Can’t export macroses!
export #define FOO 42 // hell no!
#define BAR 42
export BAR; // you shall not pass
21
@solarwinds
Re-export of imported module
// gadget.cxx
export module gadget;
export struct Gadget
{
int Detail;
};
// widget.cxx
export module widget;
// re-export
export import gadget;
export struct Widget {
Gadget First;
};
// main.cxx
#include <iostream>
import widget;
using namespace std;
int main() {
Gadget g{42};
Widget w{g};
cout << w.First.Detail << endl;
}
22
@solarwinds
Modular code: all imports inside preamble
// foo.cxx
export module foo;
import bar; // Ok
import baz; // Ok
class baz {}; // Preamble ends, no more imports allowed
import foobar; // Error
23
@solarwinds
Non-modular code: import everywhere
// main.cxx
#include <stdio.h>
import foo; // Can use names from foo from this point
void foobar() {
func_from_foo(); // Ok
func_from_bar(); // Error
}
import bar; // Can use names from bar from this point
void barfoo() {
func_from_bar(); // Ok now
}
24
@solarwinds
Can’t import partitions belonging to other modules
// a_p1.cxx
module a:p1;
/* snip */
// b.cxx
export module b;
import :p1; // Ok
import a:p1; // Error
import b; // Error
/* snip */
// b_p1.cxx
module b:p1;
/* snip */
25
@solarwinds
Cyclic import is not allowed
// a.cxx
export module a;
import b;
/* snip */
// b.cxx
export module b;
import c;
/* snip */
// c.cxx
export module c;
import a; // not allowed
/* snip */
26
@solarwinds
Partial backward compatibility
// foo.cxx
export module foo;
class import {}; // Ok, but please don’t do it
import i1; // Error, treated as keyword
::import i2; // Ok, please don’t
27
@solarwinds
Modules in-depth
28
@solarwinds
Name mangling
Before modules:
1. Internal linkage
2. External linkage
3. No linkage
Modules introduce:
4. Module linkage
29
@solarwinds
Name mangling
// alinkage.cxx
export module alinkage;
export int foo = 42; // external
static int baz = 50; // internal
int bar = 10; // module
export void doStuff() { // external
int something = 5; // no linkage
}
static void doInternalStuff() {} // internal
void doModuleStuff() {} // module
Symbol table from alinkage.o:
Bind Name
LOCAL _ZW8alinkageEL3baz
LOCAL _ZW8alinkageEL15doInternalStuffv
GLOBAL foo
GLOBAL bar
GLOBAL _Z7doStuffv
GLOBAL _ZW8alinkageE13doModuleStuffv
30
@solarwinds
Name mangling
// alinkage_impl.cxx
// implementation unit of module alinkage
module alinkage;
void doBar() {
bar = 110; // Ok, module linkage
baz = 150; // Error, internal linkage
doModuleStuff(); // Ok
doInternalStuff(); // Error
}
31
Symbol table from alinkage.o:
Bind Name
LOCAL _ZW8alinkageEL3baz
LOCAL _ZW8alinkageEL15doInternalStuffv
GLOBAL foo
GLOBAL bar
GLOBAL _Z7doStuffv
GLOBAL _ZW8alinkageE13doModuleStuffv
@solarwinds
Name mangling
// blinkage.cxx
// separate module blinkage
export module blinkage;
export int foo = 50; // Link time error
export int baz = 55; // Ok
int bar = 15; // Should be ok
void doModuleStuff() {} // Ok
32
Symbol table from alinkage.o:
Bind Name
LOCAL _ZW8alinkageEL3baz
LOCAL _ZW8alinkageEL15doInternalStuffv
GLOBAL foo
GLOBAL bar
GLOBAL _Z7doStuffv
GLOBAL _ZW8alinkageE13doModuleStuffv
@solarwinds
Name mangling implications
1. Modules are not name scoping mechanism!
2. Place exported names inside namespace
3. Use static and anonymous namespaces carefully
33
@solarwinds
Compilation
Headers
• Assuming m headers and n
sources
• Source code size is m + n
• Header is compiled for each
inclusion
• So compilation takes m * n
time
Modules
• Source code size is still m + n
• Module is compiled once
• Compilation takes m + n
time!
• How does it work?
34
@solarwinds
Compilation
BMI - binary module interface
• Produced by compiling module interface
• Read by importers
• Contains information required by importers to use module (for
example, AST)
• Compiled once per build!
35
@solarwinds
Compilation
• BMI is produced by compiling module interface...
• And read by importers
• Dependency!
• Module interface must be compiled before importers
• Building modular code is not fully parallelizable
• Headers, on the other hand...
36
@solarwinds
Compilation
Paper concerning modular code compilation speed:
https://bfgroup.github.io/cpp_tooling_stats/modules/modules_perf_D1441R1.html
Author: Rene Rivera, committee member, participant of SG15
Twitter: https://twitter.com/grafikrobot
37
@solarwinds
Compilation
Method used:
• Compile 150 source files
• Test for various (up to 150) dependency DAG chain depths
• On each level include (or import) 3 components from previous
levels
• Simple code - only int variables definitions
38
@solarwinds
Modular source
export module m148;
import m0;
import m15;
import m84;
namespace m148_ns
{
export int n = 0;
export int i1 = 1;
// ...
export int i300 = 300;
}
Source
#include "h148.hpp"
Header
#ifndef H_GUARD_h148
#define H_GUARD_h148
#include "h77.hpp"
#include "h78.hpp"
#include "h92.hpp"
namespace h148_ns
{
int n = 0;
int i1 = 1;
// ...
int i300 = 300;
}
#endif
Compilation
39
@solarwinds
Compilation
40
@solarwinds
Compilation
41
@solarwinds
Compilation
42
@solarwinds
Compilation
43
@solarwinds
Compilation
44
@solarwinds
Compilation
Observations:
• Unlike headers, modules build can not fully utilize system
resources in highly parallel environments
• At least we can watch youtube while building cool modular code
:)
• Moore’s law
• But test is not perfect
• And compilers and build systems module support will mature
45
@solarwinds
Legacy
46
@solarwinds
Legacy
• #include "legacy_header.h" - inside global module fragment
• import "legacy_header.h" - inside module preamble
• Leaks macroses with all related problems
• Huge theme for another talk
47
@solarwinds
Summary
48
@solarwinds
Modules goals ?
• Modular interfaces
• Physical encapsulation
• Isolation (except exported names)
• Compilation speed (hopefully)
49
@solarwinds
What do we do now?
• Wait and hope?
• Try it out yourself!
• Consider good practices for writing modular code (don’t forget to
share!)
• Prepare your legacy
50
@solarwinds
How to try out
MSVC 2019
• /experimental:module
• /std:c++latest
• Modular code must be in *.icc files
• Only basic features are supported
• Somehow “supports” modular stl (import std.core;)
51
@solarwinds
How to try out
GCC development branch ‘c++-modules’
• Wiki link: https://gcc.gnu.org/wiki/cxx-modules
• -fmodules-ts
• -fmodule-header
• Mostly supports merging proposal
• Sometimes ICEs (specifically on some header units)
52
@solarwinds
Some guidelines
Module naming:
• Module name should be unique
• Avoid names like util or core
• Prefix with company and/or product name
53
@solarwinds
Some guidelines
Exporting:
• All exports should be done within single top-level namespace
• Do not export multiple top-level namespaces
• Do not re-export everything, keep interfaces minimal
54
@solarwinds
Conclusion
So are C++ Modules:
• Good?
• Bad?
• Ugly?
55
@solarwinds
THANK
YOU!
56
@solarwinds
Q&A
57
@solarwinds
Links
• http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1103r2.pdf - merging
module proposal
• https://gcc.gnu.org/wiki/cxx-modules - GCC module implementation state
• https://bfgroup.github.io/cpp_tooling_stats/modules/modules_perf_D1441R1.html -
compilation speed comparison
• http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1427r0.pdf -
module toolability concerns
58

Модули в С++ 20: хорошие, плохие и ужасные. Владислав Чехарев. CoreHard Spring 2019

  • 1.
    @solarwinds C++ Modules: thegood, the bad, the ugly
  • 2.
    @solarwinds Uladzislau Chekhareu • TechnicalLead Developer at Solarwinds • Love cats, C++ and riding a bike 2
  • 3.
    @solarwinds Introduction • Modules -one of the most anticipated things in C++ 20 • But it’s not discussed much comparing to some others! • I’ll try to explain, what is this beast and how to deal with it 3
  • 4.
    @solarwinds The state ofmodules • Paper p1103r2.pdf approved for merge into International Standard • To some extent implemented by major compilers • Examples from slides were tested using gcc 4
  • 5.
    @solarwinds What do wehave now Organizing code through headers • Old good #include directives • Preprocessed, not part of the language • Simple text replacement 5
  • 6.
    @solarwinds What do wehave now Bad: • Include guards • Compilation times • Weak physical encapsulation • No isolation • Sometimes include order matters • ODR violations 6
  • 7.
    @solarwinds What do wehave now Good: • Easy • Parallel compilation • Good support by build systems • Established practices to prevent smelling code 7
  • 8.
    @solarwinds Modules goals • Modularinterfaces • Physical encapsulation • Isolation • Compilation speed 8
  • 9.
    @solarwinds New terms module, import- new special identifiers export - reused and repurposed keyword Module unit - translation unit that contains module declaration Module - collection of module units with the same name 9
  • 10.
  • 11.
    @solarwinds Primary interface unit //hello.cxx export module hello; export int foo() { return 42; } // valid module names hello hello.core hello_util hello101 // invalid module names 1111hello . .hello // main.cxx import hello; int main() { foo(); } 11
  • 12.
    @solarwinds Interface partition unit //hello_world.cxx export module hello:world; export int bar() { return 50; } // hello.cxx export module hello; export import :world; export int foo() { return 42; } 12
  • 13.
    @solarwinds Implementation unit // hello.cxx exportmodule hello; export import :world; export int foo(); // hello_impl.cxx module hello; int foo() { return 42; } 13
  • 14.
    @solarwinds Implementation partition unit //hello_helper.cxx module hello:helper; int doStuff(int bar) { return bar + 1; } // hello_impl.cxx module hello; import :helper; int foo() { return doStuff(42); } // hello_world.cxx export module hello:world; import :helper; export int bar() { return doStuff(50); } 14
  • 15.
    @solarwinds Client code // main.cxx importhello; // Ok import hello:world; // Error, can’t explicitly import interface partition import hello:helper; // Error, can’t import implementation partition int main() { foo(); // Ok bar(); // Ok doStuff(1); // Error } 15
  • 16.
  • 17.
    @solarwinds Export can onlyappear at namespace scope export void foo() {} // Ok, global namespace export template<typename T> // Ok void foot(T input) { /* snip */ } export enum class Errors { /* snip */ } // Ok, also allowed for old-style enums namespace Bar { export int foo = 42; // Ok, namespace scope export struct FooBar { // Ok, also allowed for classes int Baz = 2; } } 17
  • 18.
    @solarwinds class Gadget { exportWidget w; // illegal }; void bar() { export int i = 5; } // bad void baz(export std::string input) {} // please don't template<export typename T> // stop export class my_container{}; enum class Errors { export InvalidArgument = 1000 // error }; Export can only appear at namespace scope 18
  • 19.
    @solarwinds Convenient exporting export namespaceGoods { // exports all enclosing entities int const Count = 5; template<typename T> void foot(T input) { /* snip */ } } namespace Goods { void bar {} // NOT exported! } export { auto Value = 10.f; class Gadget {}; /* more exported entities */ } 19
  • 20.
    @solarwinds Can’t export entitieswith internal linkage namespace { export int foo = 42; // bad - anonymous namespace export void bar() {} export class MyClass() {}; } export static int baz = 30; // bad - static export static void foobar() {} 20
  • 21.
    @solarwinds Can’t export macroses! export#define FOO 42 // hell no! #define BAR 42 export BAR; // you shall not pass 21
  • 22.
    @solarwinds Re-export of importedmodule // gadget.cxx export module gadget; export struct Gadget { int Detail; }; // widget.cxx export module widget; // re-export export import gadget; export struct Widget { Gadget First; }; // main.cxx #include <iostream> import widget; using namespace std; int main() { Gadget g{42}; Widget w{g}; cout << w.First.Detail << endl; } 22
  • 23.
    @solarwinds Modular code: allimports inside preamble // foo.cxx export module foo; import bar; // Ok import baz; // Ok class baz {}; // Preamble ends, no more imports allowed import foobar; // Error 23
  • 24.
    @solarwinds Non-modular code: importeverywhere // main.cxx #include <stdio.h> import foo; // Can use names from foo from this point void foobar() { func_from_foo(); // Ok func_from_bar(); // Error } import bar; // Can use names from bar from this point void barfoo() { func_from_bar(); // Ok now } 24
  • 25.
    @solarwinds Can’t import partitionsbelonging to other modules // a_p1.cxx module a:p1; /* snip */ // b.cxx export module b; import :p1; // Ok import a:p1; // Error import b; // Error /* snip */ // b_p1.cxx module b:p1; /* snip */ 25
  • 26.
    @solarwinds Cyclic import isnot allowed // a.cxx export module a; import b; /* snip */ // b.cxx export module b; import c; /* snip */ // c.cxx export module c; import a; // not allowed /* snip */ 26
  • 27.
    @solarwinds Partial backward compatibility //foo.cxx export module foo; class import {}; // Ok, but please don’t do it import i1; // Error, treated as keyword ::import i2; // Ok, please don’t 27
  • 28.
  • 29.
    @solarwinds Name mangling Before modules: 1.Internal linkage 2. External linkage 3. No linkage Modules introduce: 4. Module linkage 29
  • 30.
    @solarwinds Name mangling // alinkage.cxx exportmodule alinkage; export int foo = 42; // external static int baz = 50; // internal int bar = 10; // module export void doStuff() { // external int something = 5; // no linkage } static void doInternalStuff() {} // internal void doModuleStuff() {} // module Symbol table from alinkage.o: Bind Name LOCAL _ZW8alinkageEL3baz LOCAL _ZW8alinkageEL15doInternalStuffv GLOBAL foo GLOBAL bar GLOBAL _Z7doStuffv GLOBAL _ZW8alinkageE13doModuleStuffv 30
  • 31.
    @solarwinds Name mangling // alinkage_impl.cxx //implementation unit of module alinkage module alinkage; void doBar() { bar = 110; // Ok, module linkage baz = 150; // Error, internal linkage doModuleStuff(); // Ok doInternalStuff(); // Error } 31 Symbol table from alinkage.o: Bind Name LOCAL _ZW8alinkageEL3baz LOCAL _ZW8alinkageEL15doInternalStuffv GLOBAL foo GLOBAL bar GLOBAL _Z7doStuffv GLOBAL _ZW8alinkageE13doModuleStuffv
  • 32.
    @solarwinds Name mangling // blinkage.cxx //separate module blinkage export module blinkage; export int foo = 50; // Link time error export int baz = 55; // Ok int bar = 15; // Should be ok void doModuleStuff() {} // Ok 32 Symbol table from alinkage.o: Bind Name LOCAL _ZW8alinkageEL3baz LOCAL _ZW8alinkageEL15doInternalStuffv GLOBAL foo GLOBAL bar GLOBAL _Z7doStuffv GLOBAL _ZW8alinkageE13doModuleStuffv
  • 33.
    @solarwinds Name mangling implications 1.Modules are not name scoping mechanism! 2. Place exported names inside namespace 3. Use static and anonymous namespaces carefully 33
  • 34.
    @solarwinds Compilation Headers • Assuming mheaders and n sources • Source code size is m + n • Header is compiled for each inclusion • So compilation takes m * n time Modules • Source code size is still m + n • Module is compiled once • Compilation takes m + n time! • How does it work? 34
  • 35.
    @solarwinds Compilation BMI - binarymodule interface • Produced by compiling module interface • Read by importers • Contains information required by importers to use module (for example, AST) • Compiled once per build! 35
  • 36.
    @solarwinds Compilation • BMI isproduced by compiling module interface... • And read by importers • Dependency! • Module interface must be compiled before importers • Building modular code is not fully parallelizable • Headers, on the other hand... 36
  • 37.
    @solarwinds Compilation Paper concerning modularcode compilation speed: https://bfgroup.github.io/cpp_tooling_stats/modules/modules_perf_D1441R1.html Author: Rene Rivera, committee member, participant of SG15 Twitter: https://twitter.com/grafikrobot 37
  • 38.
    @solarwinds Compilation Method used: • Compile150 source files • Test for various (up to 150) dependency DAG chain depths • On each level include (or import) 3 components from previous levels • Simple code - only int variables definitions 38
  • 39.
    @solarwinds Modular source export modulem148; import m0; import m15; import m84; namespace m148_ns { export int n = 0; export int i1 = 1; // ... export int i300 = 300; } Source #include "h148.hpp" Header #ifndef H_GUARD_h148 #define H_GUARD_h148 #include "h77.hpp" #include "h78.hpp" #include "h92.hpp" namespace h148_ns { int n = 0; int i1 = 1; // ... int i300 = 300; } #endif Compilation 39
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
    @solarwinds Compilation Observations: • Unlike headers,modules build can not fully utilize system resources in highly parallel environments • At least we can watch youtube while building cool modular code :) • Moore’s law • But test is not perfect • And compilers and build systems module support will mature 45
  • 46.
  • 47.
    @solarwinds Legacy • #include "legacy_header.h"- inside global module fragment • import "legacy_header.h" - inside module preamble • Leaks macroses with all related problems • Huge theme for another talk 47
  • 48.
  • 49.
    @solarwinds Modules goals ? •Modular interfaces • Physical encapsulation • Isolation (except exported names) • Compilation speed (hopefully) 49
  • 50.
    @solarwinds What do wedo now? • Wait and hope? • Try it out yourself! • Consider good practices for writing modular code (don’t forget to share!) • Prepare your legacy 50
  • 51.
    @solarwinds How to tryout MSVC 2019 • /experimental:module • /std:c++latest • Modular code must be in *.icc files • Only basic features are supported • Somehow “supports” modular stl (import std.core;) 51
  • 52.
    @solarwinds How to tryout GCC development branch ‘c++-modules’ • Wiki link: https://gcc.gnu.org/wiki/cxx-modules • -fmodules-ts • -fmodule-header • Mostly supports merging proposal • Sometimes ICEs (specifically on some header units) 52
  • 53.
    @solarwinds Some guidelines Module naming: •Module name should be unique • Avoid names like util or core • Prefix with company and/or product name 53
  • 54.
    @solarwinds Some guidelines Exporting: • Allexports should be done within single top-level namespace • Do not export multiple top-level namespaces • Do not re-export everything, keep interfaces minimal 54
  • 55.
    @solarwinds Conclusion So are C++Modules: • Good? • Bad? • Ugly? 55
  • 56.
  • 57.
  • 58.
    @solarwinds Links • http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1103r2.pdf -merging module proposal • https://gcc.gnu.org/wiki/cxx-modules - GCC module implementation state • https://bfgroup.github.io/cpp_tooling_stats/modules/modules_perf_D1441R1.html - compilation speed comparison • http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1427r0.pdf - module toolability concerns 58

Editor's Notes

  • #7 .. А также возможность случайно нарушить one definition rule
  • #9 Модульные интерфейсы - возможность описать весь интерфейс модуля в одном (в крайнем случае нескольких) файлах с исходным кодом Физическая инкапсуляция - возможно явно указать, какие сущности экспортируются модулем, а какие являются деталью реализации Изоляция - во-первых, импорт модуля не должен влиять на компиляцию клиентов или других модулей, а они в свою очередь не должны влиять на него; во-вторых, символы определяемые двумя разными модулями не должны пересекаться
  • #18 Упомянуть, что неймспейс неявно экспортируется в таком случае
  • #20 Ловушка: второе определение нейсмпейса на экспортирует ничего!
  • #26 Можно экспортировать свою партицию, нельзя чужие А еще нельзя импортировать самого себя. Так, на всякий случай
  • #31 Декорированные имена