Vous êtes sur la page 1sur 34

De-fragmenting C++: Making exceptions 9/22/2019

and RTTI more affordable and usable


Herb Sutter

Herb Sutter

1
Start of CastGuard check
; rcx == The right-hand side object pointer.

; First do the nullptr check. This could be optimized away but is not today.
; N.B. If the static_cast has to adjust the pointer base, this nullptr check
; already exists.
 This is a known algorithm,
4885c9 test mentioned at a previous CppCon, just in assembler.
rcx, rcx
 7416 raise your hand
Please jewhen you
codegentest!DoCast+0x26
recognize it. You have 15 seconds.

; Next load the RHS vftable and the comparison vftable.


488b11 mov rdx, qword ptr [rcx]
4c8d05ce8f0500 lea r8, [codegentest!MyChild1::`vftable']

; Now do the range check. Jump to the AppCompat check if the range check fail
492bc0 sub rdx, r8
4883f820 cmp rdx, 20h
7715 ja codegentest!DoCast+0x3b ; Jump to app-compat che

; End of CastGuard check


; After the cast, use the object in some way. 2

2 ; In this case, we call a virtual function as an example.


488b01 mov rax, qword ptr [rcx]
ff10 call qword ptr [rax]
1
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Core principles:
 Zero-overhead abstraction — don’t pay for what you don’t use,
what you use is as efficient as you can reasonably write by hand
 Determinism & control — over time & space, close to hardware,
leave no room for a lower language, trust the programmer
 Link compatibility — with C95 and C++prev
 Useful pragmatics for adoption and library consumption:
 Backward source compat with C — mostly C95, read-only, consume headers & seamlessly call
 Backward source compat with C++prev — C++98 and later, read-mostly, to use & to maintain
 What is not core C++:
 Specific syntax — we’ve been adding & recommending C-incompatible syntax since 1979
 Tedium — most modern abstractions (e.g., range-for, override) compatible with zero-overhead
 Lack of good defaults — good defaults are fine, as long as the programmer can override them
 Sharp edges — e.g., brittle dangling pointers are not necessary for efficiency and control
3

Historical strength: static


typing, compilation, linking

 Core principles: Major current trend (IME):


 Zero-overhead abstraction — don’t pay for what you don’t use, dynamic, static
what you use is as efficient as you can reasonably write by hand
concepts, contracts, constexpr,
 Determinism & control — over time & space, close to hardware, static error types (fs_error),
leave no room for a lower language, trust the programmer virtual → templates,
 Link compatibility — with C95 and C++prev RTTI’s typeid → reflection, …
 Useful pragmatics for adoption and library consumption:
 Backward source compat with C — mostly C95, read-only, consume headers & seamlessly call
 Backward source compat with C++prev — C++98 and later, read-mostly, to use & to maintain
 What is not core C++:
 Specific syntax — we’ve been adding & recommending C-incompatible syntax since 1979
 Tedium — most modern abstractions (e.g., range-for, override) compatible with zero-overhead
 Lack of good defaults — good defaults are fine, as long as the programmer can override them
 Sharp edges — e.g., brittle dangling pointers are not necessary for efficiency and control
4

2
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

Historical

add things add things

fix things fix things

simplify
simplify
A future worth considering?
5

Historical

add things add things

fix things fix things

simplify
simplify
A future worth considering?
6

3
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

We exceptions We RTTI
-fno-exceptions -fno-rtti
#define _HAS_EXCEPTIONS 0 /GR-

We exceptions We RTTI
-fno-exceptions -fno-rtti
#define _HAS_EXCEPTIONS 0 /GR-

4
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

static by default

dynamic by opt-in

 Establishing the problem: Today’s EH violates the zero-overhead principle


“I can’t afford to enable exception handling”  paying for what you don’t use
“I can’t afford to throw an exception”  can write it more efficiently by hand
Bonus “I can’t throw through this code”  lack of control, invisible vs. automatic propagation
 Key definition: What is a “recoverable error”?
Recoverable error != programming bug != abstract machine corruption
Exceptions/codes != pre/post contracts != stack and heap overflow
 Four coordinated proposals
1. Enable zero-overhead exception handling
2&3.Throw fewer exceptions (~90% of all exceptions should not be)
4. Support explicit “try” for visible propagation

10

10

5
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

status_code pathological(widget& a, gadget& b) {


...
if (!process(a)) return widget_error();
if (!dbwrite(b)) throw db_exception();
...
return good_result();
}
 Q: What do you think of this code?

11

11

status_code pathological(widget& a, gadget& b) {


...
if (!process(a)) return widget_error();
if (!dbwrite(b)) throw db_exception();
...
return good_result();
}
 Q: What do you think of this code?
 A: “Pick a lane!”
 Q2: What’s harder than getting callers to do decent error handling?
 A2: Getting them to do it twice, two different ways.
12

12

6
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 But this is “normal” in today’s bifurcated world:


Library A Library B
status_code process(widget& a) { void process(gadget& b) {
if (...) return widget_error(); if (...) throw db_exception();
... ...
} }

 “Pity the poor call site that uses A and B” – including all generic code:
template<typename T>
auto some_func(T& t) {
… process(t) … // ??? error handling ???
}
13

13

 About half of “C++” projects ban Q7: [Are exceptions] allowed in


exceptions in whole or in part. your current project? (N=3,240)
  Not really using Standard C++,
which requires exceptions. No: Not allowed
20%
 Using a divergent incompatible
language dialect with different Partial: Allowed in
48% some parts of the
idioms (e.g., factory functions code
instead of constructors). 32% Yes: Allowed
pretty much
 Using a divergent incompatible everywhere
std:: library dialect (e.g., EASTL,
_HAS_EXCEPTIONS=0), or none at
all (e.g., Epic).

14

14

7
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 About half of “C++” projects ban Q: [Are exceptions] allowed in


exceptions in whole or in part. your current project? (N=437)
  Not really using Standard C++,
which requires exceptions. No: Not allowed
19%
 Using a divergent incompatible
language dialect with different Partial: Allowed in
49% some parts of the
idioms (e.g., factory functions code
instead of constructors). 32% Yes: Allowed
pretty much
 Using a divergent incompatible everywhere
std:: library dialect (e.g., EASTL,
_HAS_EXCEPTIONS=0), or none at
all (e.g., Epic).

15

15

 About half of “C++” projects ban Q11: [Are exceptions] allowed in


exceptions in whole or in part. your current project? (N=2,123)
  Not really using Standard C++,
which requires exceptions. No: Not allowed

 Using a divergent incompatible 22%

language dialect with different Partial: Allowed in


some parts of the
idioms (e.g., factory functions 52%
code
instead of constructors). 26% Yes: Allowed
pretty much
 Using a divergent incompatible everywhere
std:: library dialect (e.g., EASTL,
_HAS_EXCEPTIONS=0), or none at
all (e.g., Epic).

16

16

8
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Error codes have strongest Q7: What error reporting


support of any error reporting methods are allowed on your
method. current project? (N=3,255)
70
 Expected/Outcome types are 60
50
“allowed everywhere” almost 40
equally to exceptions. 30
20
10
 Every method is banned outright 0
No: Not allowed Partial: Allowed in Yes: Allowed
in >10% of projects. some parts of the pretty much
code everywhere
 A measure of fragmentation into
Exceptions Numeric error codes
dialects.
Expected/Outcome types

17

17

 Error codes have strongest Q: What error reporting


support of any error reporting methods are allowed on your
method. current project? (N=437)
70
 Expected/Outcome types are 60
50
“allowed everywhere” almost 40
equally to exceptions. 30
20
10
 Every method is banned outright 0
No: Not allowed Partial: Allowed in Yes: Allowed
in >10% of projects. some parts of the pretty much
code everywhere
 A measure of fragmentation into
Exceptions Numeric error codes
dialects.
Expected/Outcome types

18

18

9
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Error codes have strongest Q11: What error reporting


support of any error reporting methods are allowed on your
method. current project? (N=2,123)
70
 Expected/Outcome types are 60
50
“allowed everywhere” almost 40
equally to exceptions. 30
20
10
 Every method is banned outright 0
No: Not allowed Partial: Allowed in Yes: Allowed
in >10% of projects. some parts of the pretty much
code everywhere
 A measure of fragmentation into
Exceptions Numeric error codes
dialects.
Expected/Outcome types

19

19

 Standard C++  Dialects


Boost std::
Outcome error_code
Exceptions enabled
result<>
Constructors and etc. … -fno-exceptions
operators
(especially) report Boost
failure by throwing expected
std::
errno experimental::
expected
20

20

10
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Standard C++  Dialects


Boost std::
Outcome error_code
Exceptions enabled
result<>
Constructors and etc. … -f-no-exceptions
operators
(especially) report Boost
failure by throwing expected
std::
errno experimental::
expected
21

21

 Violates C++’s zero-overhead principle in two ways.


1. “I can’t afford to enable exception handling.”
 Just turning on EH incurs space overhead.
 Zero overhead principle, part 1: “Don’t pay for what you don’t use.”
2. “I can’t afford to throw an exception.”
 Throwing an exception incurs not-statically-boundable space and time overhead.
 Throwing an exception usually less efficient than returning code/expected<> by hand.
 Zero overhead principle, part 2: “When you do use it you can’t reasonably write it better
by hand” including by using alternatives.
 Bonus problem: “I can’t throw through this code.”
 Lack of control: Automatic propagation is great, but invisible control flow makes writing
exception-safe code harder. More on this later…

22

22

11
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Establishing the problem: Today’s EH violates the zero-overhead principle SF WFN WA SA


“I can’t afford to enable exception handling”  paying for what you don’t use 22-20-5-1-0
“I can’t afford to throw an exception”  can write it more efficiently by hand 22-17-4-2-1
Bonus “I can’t throw through this code”  lack of control, invisible vs. auto propagtn 14-13-0-3-4
 Key definition: What is a “recoverable error”?
Recoverable error != programming bug != abstract machine corruption
Exceptions/codes != pre/post contracts != stack and heap overflow
 Four coordinated proposals
1. Enable zero-overhead exception handling
2&3.Throw fewer exceptions (~90% of all exceptions should not be)
4. Support explicit “try” for visible propagation

23

23

 Establishing the problem: Today’s EH violates the zero-overhead principle


“I can’t afford to enable exception handling”  paying for what you don’t use
“I can’t afford to throw an exception”  can write it more efficiently by hand
Bonus “I can’t throw through this code”  lack of control, invisible vs. automatic propagation
 Key definition: What is a “recoverable error”?
Recoverable error != programming bug != abstract machine corruption
Exceptions/codes != pre/post contracts != stack and heap overflow
 Four coordinated proposals
1. Enable zero-overhead exception handling
2&3.Throw fewer exceptions (~90% of all exceptions should not be)
4. Support explicit “try” for visible propagation

24

24

12
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

error: “an act that … fails to achieve what should be done.”


— [Merriam-Webster]

 P0709: “recoverable error” ≡ “a function couldn’t do what it advertised.”


 Its preconditions were met.
 It could not achieve its successful-return postconditions.
 The calling code can be told and can programmatically recover.

 Run-time errors (and only those) should be reported to the calling code.
 Regardless of mechanism: “Prefer exceptions” but applies to any reporting style.

25

25

 Abstract machine corruption causes a corrupted state that cannot be


recovered from programmatically.
 So it should never be reported to calling code as an error (e.g., via exception).

 Example: Stack exhaustion is always an abstract machine corruption.


 It can happen to any function.
 If we tried to report it using an exception then every function could throw.
 We cannot continue running normal code. (NB: Destructors are “normal code.”)
 The callee can’t run code to report it to the caller…
… and the caller couldn’t run code to recover anyway.
 Conclusion: Reporting this as a runtime error would be a category error.

26

26

13
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 A programming bug (e.g., out-of-bounds access, null dereference) causes a


corrupted state that cannot be recovered from programmatically.
 Therefore it should never be reported to the calling code as an error
(e.g., it should not be reported via an exception).
 Examples:
 A precondition (e.g., [[pre...]]) violation is always a bug in the caller (it shouldn’t
make the call).
 Corollary: std::logic_error and its derivatives should never be thrown (§4.2), its
existence is itself a “logic error”; use assertions/contracts/… instead.
 A postcondition (e.g., [[post...]]) violation on “success” return is always a bug in
the callee (it shouldn’t return success).
 Violating a noexcept declaration is also a form of postcondition violation.
 An assertion (e.g., [[assert...]]) failure is always a bug in the function.
27

27

What to use Report-to handler Handler species


A. Corruption of the Terminate User Human
abstract machine (e.g.,
stack exhaustion)

B. Programming bug (e.g., Asserts, log checks, Programmer Human


precondition violation) contracts, ...
C. Recoverable error (e.g., Throw exception, Calling code Code
host not found) error code, etc.

28

28

14
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Establishing the problem: Today’s EH violates the zero-overhead principle


“I can’t afford to enable exception handling”  paying for what you don’t use
“I can’t afford to throw an exception”  can write it more efficiently by hand
Bonus “I can’t throw through this code”  lack of control, invisible vs. automatic propagation
 Key definition: What is a “recoverable error”?
Recoverable error != programming bug != abstract machine corruption
Exceptions/codes != pre/post contracts != stack and heap overflow
 Four coordinated proposals
1. Enable zero-overhead exception handling
2&3.Throw fewer exceptions (~90% of all exceptions should not be)
4. Support explicit “try” for visible propagation

29

29

 Exceptions are great: Distinct “error” paths, can’t ignore, auto propagation.
 But: Inherently not zero-overhead, not deterministic.
 “Throwing objects of dynamic types…  dynamic allocation + type erasure
… and catching using RTTI.”  dynamic casting (special)

 Proposal:
 “Throwing values of static types…  stack allocation, share return channel
… and catching by value.”  no dynamic casting, just value comparison
 Isomorphic to error codes, identical space/time overhead and predictability.
 Share return channel  potential for negative overhead abstraction.
 If a function agrees (opts in) that any exceptions it emits are values of one statically
known type, we can implement it with zero dynamic/non-local overheads.

not a breaking change


30

30

15
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 As-if returning union{ Success; Error; } + bool, using the same


return channel (incl. registers + CPU flag for discriminant).
 Best of exceptions and error codes (and fully prior-art):
Exactly exceptions’ programming model (throw, try, catch).
Exactly error codes’ return-value implementation (w/o monopolizing channel).
 Doubles down on value semantics. (Cf: C++11 move semantics.)
 If you love:
 Exceptions: Can use them more widely, removing perf reasons to avoid/ban.
 Expected/Outcome: Gets language support, propagates automatically.
 Error codes: Doesn’t monopolize return channel, propagates automatically, and
the caller can’t forget to check it and gets distinct success/error paths.
 Termination (fail-fast): Hook the propagation notification (see §4.1.4).
31

31

 A static-exception-specification throws  function can throw std::error,


an evolution of std::error_code + SG14-driven improvements already underway.
string f() throws {
if (flip_a_coin()) throw arithmetic_error::something;
return “xyzzy”s + “plover”; // bad_alloc → std::errc::ENOMEM
}
string g() throws { return f() + “plugh”; } // bad_alloc → std::errc::ENOMEM
int main() {
try {
auto result = g();
cout << “success, result is: ” << result;
} catch(error err) { // catch by value
cout << “failed, error is: ” << err.error();
}
}
32

32

16
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 A static-exception-specification throws  function can throw std::error,


an evolution of std::error_code + SG14-driven improvements already underway.
string f() throws {
Default
if (flip_a_coin()) throwand recommended std::error usage == purely local return values:
arithmetic_error::something;
return “xyzzy”s• +Always
“plover”;
allocated as an ordinary// value → std::errc::ENOMEM
bad_alloc
stack
} • Share (not waste) the return channel
string g() throws• { Statically
return f()known
+ “plugh”;
type,} so never // RTTI → std::errc::ENOMEM
bad_alloc
need
int main() {
try { Zero-overhead: No extra static overhead in the binary image.
auto result = g(); No dynamic allocation. No need for RTTI.
cout << “success, result is:Identical
Determinism: ” << result;
space and time cost as if returning an error code by hand.
} catch(error err) { // catch by value
cout << “failed,
Note:error is: ” << err.error();
For compatibility, std::error can also wrap an exception_ptr, but this is a compatibility
} mode where the overheads come from using today’s model, which are just passed through
}
33

33

Today (pseudocode) Proposed (pseudocode)


// throw site: “throw MyException(value)” // throw site: “throw std::error(domain,value)”
return (void*) new MyException(value); return std::error(domain,value); // no alloc

// … // …
// propagate // propagate
// … // …

// catch site: “catch (EBase& e) {/*…*/}” by reference // catch site: “catch (std::error e)” by value
if (auto e = special_dynamic_cast<EBase*>(pvoid); e) if (e.failed) {/*…*/} // no RTTI
{/*…*/}

34

34

17
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 What are the benefits?


 Unification: All projects can turn on exception handling.
 Zero overhead principle, part 1: “Don’t pay for what you don’t use.”
 Unification: All code can report errors using exceptions.
 Zero overhead principle, part 2: “When you do use it you can’t reasonably write it
better by hand” including by using alternatives.
 Even space- and time-constrained code that need statically boundable costs.
 Simplification: Can teach “every function should be declared with
exactly one of noexcept or throws.”
 Just like we now can teach “every virtual function should be declared with exactly
one of virtual, override, or final.”

35

35

 Establishing the problem: Today’s EH violates the zero-overhead principle


“I can’t afford to enable exception handling”  paying for what you don’t use
“I can’t afford to throw an exception”  can write it more efficiently by hand
Bonus “I can’t throw through this code”  lack of control, invisible vs. automatic propagation
 Key definition: What is a “recoverable error”?
Recoverable error != programming bug != abstract machine corruption
Exceptions/codes != pre/post contracts != stack and heap overflow
 Four coordinated proposals
1. Enable zero-overhead exception handling
2&3.Throw fewer exceptions (~90% of all exceptions should not be)
4. Support explicit “try” for visible propagation

36

36

18
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

“[With contracts,] 90-something% of the typical uses of exceptions in


.NET and Java became preconditions. All of the ArgumentNullException,
ArgumentOutOfRangeException, and related types and, more importantly,
the manual checks and throws were gone.”

— [Duffy 2016]

37

37

Summary Status / Proposal


 Precondition violations are bugs, not  WG21:
program-recoverable errors  Supported by standard library maintainers
 Migration planned to move logic_error &
 Don’t report them using error handling derived types to not be exceptions
(exceptions or codes)  When used as preconditions
 Calling code can’t recover programmatically  Multi-release migration period
 Shared state must already be presumed
corrupt
 Use assertions, contracts, or similar instead
 Report to a human programmer who can fix
the bug

38

38

19
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

39

39

 Today:
 1. Exceptions must be dynamically allocated.
 3. Dynamic allocation failures are reported
using exceptions.

 Q: How does this statement describe two


independent issues?
 1. (see prev) Exceptions shouldn’t need to be
dynamically allocated.
 3. (see next) Allocation failures shouldn’t
always be reported as program-recoverable
errors (exceptions or otherwise)…

40

40

20
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Today:
 1. Exceptions must be dynamically allocated.
 3. Dynamic allocation failures are reported
using exceptions.

 Q: How does this statement describe two


independent issues?
 1. (see prev) Exceptions shouldn’t need to be
dynamically allocated.
 3. (see next) Allocation failures shouldn’t
always be reported as program-recoverable
errors (exceptions or otherwise)…

41

41

 Cologne (2019-07) Library Evolution subgroup (LEWG) direction:


 Add a noexcept-queryable allocator property for “reports vs. fails-fast” on
allocation failure. (Unanimous)
 Recommend conditional noexcept based on the relevant allocator) pervasively
on standard library functions that could only throw bad_alloc. (Unanimous)
 Fail-fast for default std::allocator and default operator new. (19 11 4 1 0)
 NB: Even though a breaking change.

 Cologne (2019-07) Language Evolution subgroup (EWG) direction:


 Fail-fast for default std::allocator and default operator new. (0 17 6 10 5)
 NB: Even though a breaking change.
 Updated proposal: Let the programmer decide for global new.

42

42

21
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

What to use Report-to handler Handler species


A. Corruption of the Terminate User Human
abstract machine (e.g.,
stack exhaustion, failure-
aborting allocator)
B. Programming bug (e.g., Asserts, log checks, Programmer Human
precondition violation) contracts, ...
C. Recoverable error (e.g., Throw exception, Calling code Code
host not found, failure- error code, etc.
reporting allocator)

43

43

 What are the benefits?


 Correctness: Exceptions are not appropriate for reporting non-errors.
 Bugs (e.g., preconditions) and corruption (e.g., abstract machine failures).
 Correctness and performance: Eliminate ~90% of all exceptions.
 The vast majority of the standard library would not throw.
 (Recall: Language-independent. Also true of Java and C#.)
 Simplification: Eliminate ~90% of the invisible control flow paths.
 (Which today dominate the visible ones.)
 Clear code is easier to write correctly and reason about.
 Example: See GotW #20, a 4-line function with 3 normal (and visible) control flow
paths and 20 exceptional (and invisible) control flow paths.

44

44

22
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Establishing the problem: Today’s EH violates the zero-overhead principle


“I can’t afford to enable exception handling”  paying for what you don’t use
“I can’t afford to throw an exception”  can write it more efficiently by hand
Bonus “I can’t throw through this code”  lack of control, invisible vs. automatic propagation
 Key definition: What is a “recoverable error”?
Recoverable error != programming bug != abstract machine corruption
Exceptions/codes != pre/post contracts != stack and heap overflow
 Four coordinated proposals
1. Enable zero-overhead exception handling
2&3.Throw fewer exceptions (~90% of all exceptions should not be)
4. Support explicit “try” for visible propagation

45

45

 Good news: Exceptional control flow is automatic.


 Bad news: Exception control flow is invisible.
 Hard to reason about exceptions, especially in legacy code.
 Proposal: try before an expression/statement where a subexpression can throw.
 Makes exceptional paths visible.
 If we required it in new code: Compile-time guarantees (e.g., no “try/throw”  noexcept).
string f() throws {
if (flip_a_coin()) throw arithmetic_error::something;
return try “xyzzy”s + “plover”; // greppable
try string s(“xyzzy”); // same, just showing statement form too
try return s + “plover”;
}
string g() throws { return try f() + “plugh”; }
46

46

23
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 What are the benefits?


 Convenience (as today): Automatic exception propagation.
 Correctness (new): Visible (still convenient) propagation.

47

47

 “One more thing”… Sets the stage for a potential new world:
 2+3: Enables “~90% of all functions are noexcept.”
 2+3+4: Enables “require try on every expression that can throw.”
 Simplification: Enables using C code in C++ projects with confidence.
 Can take any C code, compile it as C++, and (automatically) add try on
every expression that could throw  feasible to inspect and validate the
code is exception-safe.

48

48

24
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Establishing the problem: Today’s EH violates the zero-overhead principle SF WFN WA SA


“I can’t afford to enable exception handling”  paying for what you don’t use 22-20-5-1-0
“I can’t afford to throw an exception”  can write it more efficiently by hand 22-17-4-2-1
Bonus “I can’t throw through this code”  lack of control, invisible vs. auto propagtn 14-13-0-3-4
 Key definition: What is a “recoverable error”?
Recoverable error != programming bug != abstract machine corruption
Exceptions/codes != pre/post contracts != stack and heap overflow
 Four coordinated proposals SF WFN WA SA
1. Enable zero-overhead exception handling 15-16-6-6-3
2&3.Throw fewer exceptions (~90% of all exceptions should not be) (see earlier)
4. Support explicit “try” for visible propagation 5-5-6-15-16

49

49

(but basically the same)

50

25
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Establishing the problem: Today’s RTTI violates the zero-overhead principle


“I can’t afford to enable RTTI”  paying for what you don’t use
“I can’t afford to use dynamic_cast”  can write it more efficiently by hand

 Two coordinated proposals (coming over the next year-ish)


1. Adopt static reflection  can make typeid/type_info consteval
2. Adopt down_cast  can ask for only the work you need

51

51

void myfunc(base* a) {
...
if (something) {
auto concrete = static_cast<derived*>(a);
use(concrete);
}
...
}
 Q: What do you think of this code?

52

52

26
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

void myfunc(base* b) {
...
if (something) {
auto concrete = static_cast<derived*>(b);
use(concrete);
}
...
}
 Q: What do you think of this code?
 A: “Wait, that’s an unchecked down-cast!”
53

53

Root cause of CVEs by patch year


100%
90% 59
30 41 59
44 44 159 139 197
80% 61 120 17 221
4 103 9
70% 41 2 8 9
11 25
4
61 8
21 39 88
60% 4 16 13 30 7 13 76 55
12 5 6 22
5
6 7 15 25
50% 18 44 19 82 61 Joe Bialek (Microsoft)
22 14
40% 36
36 35 39 186 71 81
30% 57 113 183
43 45 87
64 81 99
20%
30 36
10% 32 24 35 71 104 79
21 22 26 28 61
0% 13 4 11 4 3 7 8
1
2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018

Stack Corruption Heap Corruption Use After Free Type Confusion


Uninitialized Use Heap OOB Read Other
Shayne Hiet-Block
(Microsoft)

54

27
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Many “C++” projects ban RTTI Q13: [Is RTTI] allowed in your
in whole or in part. current project? (N=2,058)
 One root cause of security
No: Not allowed
vulnerabilities is “didn’t use 18%
dynamic_cast” because:
Partial: Allowed in
 “We can’t, RTTI not enabled 14% some parts of the
because it’s too expensive.” code
68% Yes: Allowed
 “We can’t, RTTI is enabled but pretty much
dynamic_cast is too expensive.” everywhere

 “And we tested so we know this


downcast is safe…”

55

55

 All nonstandard methods are Q12: What run-time casting


“allowed everywhere” only a little methods are allowed on your
current project? (N=2.083)
less than dynamic_cast. 60
50
 Every method is banned outright 40
30
in >20% of projects.
20
10
 A measure of fragmentation 0
No: Not allowed Partial: Allowed in Yes: Allowed
into dialects. some parts of the pretty much
code everywhere
 NB: Worse than EH’s 10%! dynamic_cast
Virtual query (e.g., QueryInterface)
Custom (e.g., project-specific type tag)
56

56

28
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Violates C++’s zero-overhead principle in two ways.


1. “I can’t afford to enable RTTI.”
 Just turning on EH incurs space overhead.
 Zero overhead principle, part 1: “Don’t pay for what you don’t use.”
2. “I can’t afford to call dynamic_cast.”
 dynamic_cast incurs not-statically-boundable space and time overhead.
 dynamic_cast is usually less efficient than a custom type-tag solution.
 Zero overhead principle, part 2: “When you do use it you can’t reasonably write
it better by hand” including by using alternatives.

57

57

 Establishing the problem: Today’s RTTI violates the zero-overhead principle


“I can’t afford to enable RTTI”  paying for what you don’t use
“I can’t afford to use dynamic_cast”  can write it more efficiently by hand

 Two coordinated proposals (coming over the next year-ish)


1. Adopt static reflection  can make typeid/type_info consteval
2. Adopt down_cast  can ask for only the work you need

58

58

29
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 Prefer static (know exactly what this program


asks to use) to dynamic (gotta store it in
case someone might ask).
 Type information is great! But don’t give
it to me unless I ask for it.
 Observation 1: Static reflection makes
run-time type_info redundant.
 Observation 2: Anything that’s static,
we can easily make dynamic.
 Just store it somewhere.
 Only pay for what you store  zero-overhead.

 Proposal: Adopt static reflection + consteval programming,


make typeid/type_info consteval. Andrew Sutton (Lock3)
59

59

 Observation 1: Don’t ask for more work than you need.


 dynamic_cast can do a lot, including cross-casts and virtual inheritance.
 Example: std::equal_range vs. std::lower_bound.

 Observation 2: A frequent use case is down-cast without virtual


inheritance.
 Conjecture: A down_cast that doesn’t compile unless the cast is downward and
not across a virtual inheritance link can be much more efficient.
 Why? Because we’re asking for less work.
 We get to stop being the demanding boss.
 You only pay for what you use  zero-overhead.

60

60

30
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

 CastGuard injects checks for static_cast down-casts.


 Based on Clang Control Flow Integrity (CFI). Peter Collingbourne Jim Radigan
 No change to ABI incl. vtables. Doesn’t use RTTI. (Google) (Microsoft)
 Check is a simple range check. No tree walks. Vtables are
arranged in memory so “IS-A related type” is a SUB.
 Limitation: Intra-DLL, doesn’t check cross-DLL casts.
 Preliminary: Low impact on binary size and run time.
 Current worst case: One Windows DLL has +1.5% binary size…
Kostya Serebryany Joe Bialek
 … vs. unchecked static_cast (not dynamic_cast, which is banned). (Google) (Microsoft)

 Proposal: Provide down_cast to ask for only what you need.


 To compile, must be base-to-derived and not cross a virtual inheritance link.
 Constant-time within a DLL.
 Zero-overhead: Don’t pay if you don’t use it + when you use it you can’t do better by hand.
61

61

dynamic_cast, simple SI CastGuard, simple SI


71472 c744242000000000 mov dword ptr [rsp+20h], 0 ss:0000001f`baeff890=567bf330
7147a 4c8d0ddf070600 lea r9, [test3!MyChild1 `RTTI Type Descriptor' (d1c60)]
bd60e 1bc0
71481 4c8d0590080600 sbb r8,
lea eax, eax
[test3!MyBase `RTTI Type Descriptor' (d1d18)]
bd610 2301xor edx,
71488 33d2 andedxeax, dword ptr [rcx]
bd612 4863c8
7148a 488bcb bd14dmov movsxd
741f rcx, je rcx, eax
rbx test3!FindSITargetTypeInstance+0x52 (bd16e) [br=0] Start of CastGuard check
bd615 4c2bf1
7148d e81ec10400 call sub
bd14f 4d8bc3 r14, rcx
mov r8, r11
test3!__RTDynamicCast (bd5b0) ; rcx == The right-hand side object pointer.
bd618 391e bd68f
bd152 496310 cmp 4c8bc8 dword ptr
movsxd rdx, mov
[rsi], ebx
dword r9,ptr
rax[r8] <-- take this jump
bd692 4885c0
bd155 4903d1
test3!__RTDynamicCast: add rdx, r9 test rax, rax ; First do the nullptr check. This could be optimized away but is not today.
bd63a 48634614
bd158 ffc1
bd5b0 48895c2410 bd695
mov inc7510ecx
movsxd
qword ptr jne ptr
rax,[rsp+10h],
dword test3!__RTDynamicCast+0xf7
[rsi+14h]
rbx ds:c9b8c=00059b78 (bd6a7) <-- take this jump
ss:0000001f`baeff878=0000000000000001 ; N.B. If the static_cast has to adjust the pointer base, this nullptr check
bd63e 4c8bce
bd5b5 4889742418 movmov
bd15a 486302 qword r9,ptr
movsxd rsi [rsp+18h],
rax, dwordrsiptr [rdx] ; already exists.
bd641 4c2bc8
bd5ba 57 bd15d bd6a7
rdisub add
push4903c1 4139590c
r9, raxrax, r9 cmp dword ptr [r9+0Ch], ebx 4885c9 test rcx, rcx
bd644 48634610
bd5bb 4154 bd160 bd6ab
r12movsxd
483bc3
push 7c13
cmp rax, rax, jlrbx test3!__RTDynamicCast+0x110
dword ptr [rsi+10h] (bd6c0) [br=1] <-- take this jump 7416 je codegentest!DoCast+0x26
bd648 428b4c0804
bd5bd 4155 bd163 7467r13 mov
push ecx, dword ptr [rax+r9+4]
je test3!FindSITargetTypeInstance+0xb0 (bd1cc) [br=1] <-- take this jump
bd64d f6c101
bd5bf 4156 push bd6c0
r14test 49634108
cl, 1 movsxd rax, dword ptr [r9+8] ds:c9bd0=00000000
; Next load the RHS vftable and the comparison vftable.
bd650 7510
bd5c1 4157 bd1cc bd6c4
413bca
push jne 4803c3
r15 cmp ecx, addr10d rax, rbx
test3!__RTDynamicCast+0xb2 (bd662) <-- do not take this jump
bd6c7 4903c6 add rax, r14 488b11 mov rdx, qword ptr [rcx]
bd652 4d8bc7
bd5c3 4883ec50 bd1cf 73e2
sub rsp, movjae 50hr8,test3!FindSITargetTypeInstance+0x97
r15 (bd1b3) [br=0]
bd655 498bd4
bd5c7 4d8bf9 bd1d1 bd6ca
4d8d048b
mov move90dffffff
r15, r9 leardx, r12 jmp test3!__RTDynamicCast+0x2c (bd5dc)
r8, [r11+rcx*4] <-- jump 4c8d05ce8f0500 lea r8, [codegentest!MyChild1::`vftable']
bd658 488bce
bd5ca 4d8be0 bd1d5mov496300 movr8movsxd
r12, rcx, rsi rax, dword ptr [r8]
bd65b e8bcfaffff
bd5cd 4c63ea bd1d8movsxdbd5dc call
42f644081404
r13,4c8d5c2450
test byte lea r11, [rsp+50h]
test3!FindSITargetTypeInstance
edx ptr [rax+r9+14h], 4(bd11c) <-- call instruction ; Now do the range check. Jump to the AppCompat check if the range check fails.
bd5d0 488bf9 bd1demov bd5e1
75d3 rdi, rcx498b5b38 mov rbx, qword ptr [r11+38h]
jne test3!FindSITargetTypeInstance+0x97 (bd1b3) [br=0] 492bc0 sub rdx, r8
bd5d3 33db bd5e5 498b7340
test3!FindSITargetTypeInstance:
bd1e0xor4a630408
ebx, ebx movsxd rax,mov dwordrsi,ptrqword ptr [r11+40h]
[rax+r9] 4883f820 cmp rdx, 20h
bd11c 488bc4
bd5d5 4885c9 bd1e4test bd5e9
mov
4903c1
rcx, rcx 498be3
add rsprax, r9mov rsp, r11
rax, 7715 ja codegentest!DoCast+0x3b ; Jump to app-compat check
bd11f 48895808
bd5d8 751c bd1e7 bd5ec
jne493bc6mov 415f qwordrax,
cmp
test3!__RTDynamicCast+0x46ptrpop r14 r15rbx(bd5f6) <-- take this jump
[rax+8],
bd123 48896810
bd1ea 740d bd5ee
mov 415eje qword ptrpop r14 rbp
[rax+10h],
test3!FindSITargetTypeInstance+0xdd (bd1f9) [br=1] ; End of CastGuard check
bd127 48897018
bd5f6 488b01 mov bd5f0rax, 415d
mov qword
qword ptrptr pop
[rcx] r13 rsi
[rax+18h],
ds:000001a4`f0889610={test3!MyChild1::`vftable' ; After the cast, use the object in some way.
(c9270)}bd12b 48897820 bd5f2
bd1f9 488bc2 mov415c movqwordrax, ptrpop r12 rdi
[rax+20h],
rdx ; In this case, we call a virtual function as an example.
bd12f 4156
bd5f9 488b70f8 bd1fc mov
ebb7bd5f4
push 5f
rsi,r14
jmp ptrpop[rax-8]rdi
qwordtest3!FindSITargetTypeInstance+0x99 (bd1b5) <-- jump
488b01 mov rax, qword ptr [rcx]
bd131 48634110
bd5fd 8b4604 mov bd5f5eax,c3
movsxd dword rax,ptr ret
dword
[rsi+4] ptr [rcx+10h]
ff10 call qword ptr [rax]
bd135 498bd8
bd600 4c8bf7 bd1b5 mov
488b5c2410
mov r14, rdi rbx,movr8 rbx, qword ptr [rsp+10h] ss:0000001f`baeff7f0=0000000000000000
bd138 33c9
bd603 4c2bf0 bd1ba xorr14,ecx,
488b6c2418
sub rax ecx mov rbp, qword ptr [rsp+18h]
bd13a 4c8bf2
bd606 8b5608 bd1bfmov mov edx, r14,
488b742420 dword rdxptrrsi,
mov qword ptr [rsp+20h]
[rsi+8]
bd13d 4e635c080c
bd609 482bca bd1c4sub movsxd
488b7c2428
rcx, rdx mov r11, dword ptr [rax+r9+0Ch]
rdi, qword ptr [rsp+28h]
bd142 468b540808
bd60c f7da bd1c9
neg415eedxmovpop r10d, r14dword ptr [rax+r9+8]
bd147 4d03d9
bd1cb c3 add retr11, r9
bd14a 4585d2 test r10d, r10d
; back to test3!__RTDynamicCast
bd660 eb2d jmp test3!__RTDynamicCast+0xdf (bd68f) <-- jump

62

62

31
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

CastGuard, simple SI

Start of CastGuard check


; rcx == The right-hand side object pointer.

; First do the nullptr check. This could be optimized away but is not today.
; N.B. If the static_cast has to adjust the pointer base, this nullptr check
; already exists.
4885c9 test rcx, rcx
7416 je codegentest!DoCast+0x26

; Next load the RHS vftable and the comparison vftable.


488b11 mov rdx, qword ptr [rcx]
4c8d05ce8f0500 lea r8, [codegentest!MyChild1::`vftable']

; Now do the range check. Jump to the AppCompat check if the range check fails.
492bc0 sub rdx, r8
4883f820 cmp rdx, 20h
7715 ja codegentest!DoCast+0x3b ; Jump to app-compat check
63
; End of CastGuard check
63 ; After the cast, use the object in some way.
; In this case, we call a virtual function as an example.
488b01 mov rax, qword ptr [rcx]
ff10 call qword ptr [rax]

Historical

add things add things

fix things fix things

simplify
simplify
A future worth considering?
64

64

32
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

Historical

add things add things

fix things fix things

simplify
simplify
A future worth considering?
65

65

static by default

dynamic by opt-in

66

66

33
De-fragmenting C++: Making exceptions 9/22/2019
and RTTI more affordable and usable
Herb Sutter

Historical strength: static


typing, compilation, linking

 Core principles: Major current trend (IME):


 Zero-overhead abstraction — don’t pay for what you don’t use, dynamic, static
what you use is as efficient as you can reasonably write by hand
concepts, contracts, constexpr,
 Determinism & control — over time & space, close to hardware, static error types (fs_error),
leave no room for a lower language, trust the programmer virtual → templates,
 Link compatibility — with C95 and C++prev RTTI’s typeid → reflection, …
 Useful pragmatics for adoption and library consumption:
 Backward source compat with C — mostly C95, read-only, consume headers & seamlessly call
 Backward source compat with C++prev — C++98 and later, read-mostly, to use & to maintain
 What is not core C++:
 Specific syntax — we’ve been adding & recommending C-incompatible syntax since 1979
 Tedium — most modern abstractions (e.g., range-for, override) compatible with zero-overhead
 Lack of good defaults — good defaults are fine, as long as the programmer can override them
 Sharp edges — e.g., brittle dangling pointers are not necessary for efficiency and control
67

67

Herb Sutter

68

34

Vous aimerez peut-être aussi