Skip to content

Commit 1880cea

Browse files
authored
feat: migrate cpp/language/basic_concepts/as_if (#88)
* feat: migrate cpp/language/basic_concepts/as_if * style: split the final codeblock to 3 tabs
1 parent eb89c9f commit 1880cea

File tree

1 file changed

+148
-0
lines changed
  • src/content/docs/cpp/language/basic_concepts

1 file changed

+148
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
title: The as-if rule
3+
---
4+
5+
import { Tabs, TabItem } from "@astrojs/starlight/components";
6+
import { Desc, DescList } from "@components/desc-list";
7+
import { RevisionBlock } from "@components/revision";
8+
import DocLink from "@components/DocLink.astro";
9+
10+
Allows any and all code transformations that do not change the observable behavior of the program.
11+
12+
## Explanation
13+
14+
_Observable behavior_ of a program includes the following:
15+
16+
<RevisionBlock until="C++11">
17+
- At every <DocLink dest="/cpp/language/expressions/eval_order">sequence point</DocLink>, the values of all <DocLink dest="/cpp/language/declarations/cv">volatile</DocLink> objects are stable (previous evaluations are complete, new evaluations not started).
18+
</RevisionBlock>
19+
20+
<RevisionBlock since="C++11">
21+
- Accesses (reads and writes) to <DocLink dest="/cpp/language/declarations/cv">volatile</DocLink> objects occur strictly according to the semantics of the expressions in which they occur. In particular, they are <DocLink dest="/cpp/library/atomic/memory_order">not reordered</DocLink> with respect to other volatile accesses on the same thread.
22+
</RevisionBlock>
23+
24+
<RevisionBlock until="C++26">
25+
- At program termination, data written to files is exactly as if the program was executed as written.
26+
</RevisionBlock>
27+
28+
<RevisionBlock since="C++26">
29+
- Data delivered to the host environment is written into files.
30+
</RevisionBlock>
31+
32+
- Prompting text which is sent to interactive devices will be shown before the program waits for input.
33+
- If the ISO C pragma <DocLink dest="/cpp/language/preprocessor/impl">`#pragma STDC FENV_ACCESS`</DocLink> is supported and is set to `ON`, the changes to the <DocLink dest="/cpp/library/numeric/fenv">floating-point</DocLink> environment (floating-point exceptions and rounding modes) are guaranteed to be observed by the floating-point arithmetic operators and function calls as if executed as written, except that
34+
- the result of any floating-point expression other than cast and assignment may have range and precision of a floating-point type different from the type of the expression (see <DocLink dest="/cpp/library/utility/types/climits/FLT_EVAL_METHOD">FLT_EVAL_METHOD</DocLink>),
35+
- notwithstanding the above, intermediate results of any floating-point expression may be calculated as if to infinite range and precision (unless <DocLink dest="/cpp/language/preprocessor/impl">`#pragma STDC FP_CONTRACT`</DocLink> is `OFF`).
36+
37+
<RevisionBlock until="C++26">
38+
The C++ compiler is permitted to perform any changes to the program as long as given the same input, the observable behavior of the program is one of the possible observable behaviors corresponding to that input.
39+
40+
However, if certain input will result in <DocLink dest="/cpp/language/basic_concepts/ub">undefined behavior</DocLink>, the compiler cannot guarantee any observable behavior of the program with that input, even if any operation of the observable behavior happens before any possible undefined operation.
41+
</RevisionBlock>
42+
43+
<RevisionBlock since="C++26">
44+
A program may contain _observable checkpoints_.
45+
46+
An operation OP is _undefined-free_ if for every undefined operation U, there is an observable checkpoint CP such that OP happens before CP and CP happens before U. The _defined prefix_ of the program with a given input comprises all its undefined-free operations.
47+
48+
The C++ compiler is permitted to perform any changes to the program as long as given the same input, the observable behavior of the defined prefix of the program is one of the possible observable behaviors corresponding to that defined prefix.
49+
50+
If certain input will result in <DocLink dest="/cpp/language/basic_concepts/ub">undefined behavior</DocLink>, the compiler cannot guarantee any observable behavior of the program with that input that does not belong to the defined prefix.
51+
</RevisionBlock>
52+
53+
## Notes
54+
55+
Because the compiler is (usually) unable to analyze the code of an external library to determine whether it does or does not perform I/O or volatile access, third-party library calls also aren't affected by optimization. However, standard library calls may be replaced by other calls, eliminated, or added to the program during optimization. Statically-linked third-party library code may be subject to link-time optimization.
56+
57+
Programs with undefined behavior often change observable behavior when recompiled with different optimization settings. For example, if a test for signed integer overflow relies on the result of that overflow, e.g. `if (n + 1 < n) abort();`, [it is removed entirely by some compilers](#https://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html) because <DocLink dest="/cpp/language/expressions/operator_arithmetic" section="overflows">signed overflow is undefined behavior</DocLink> and the optimizer is free to assume it never happens and the test is redundant.
58+
59+
<DocLink dest="/cpp/language/initialization/copy_elision">Copy elision</DocLink> is an exception from the as-if rule: the compiler may remove calls to move- and copy-constructors and the matching calls to the destructors of temporary objects even if those calls have observable side effects.
60+
61+
<RevisionBlock since="C++14">
62+
<DocLink dest="/cpp/language/expressions/new">new expression</DocLink> has another exception from the as-if rule: the compiler may remove calls to the <DocLink dest="/cpp/library/memory/new/operator_new">replaceable allocation functions</DocLink> even if a user-defined replacement is provided and has observable side-effects.
63+
</RevisionBlock>
64+
65+
The count and order of floating-point exceptions can be changed by optimization as long as the state as observed by the next floating-point operation is as if no optimization took place:
66+
67+
```cpp
68+
#pragma STDC FENV_ACCESS ON
69+
for (i = 0; i < n; ++i)
70+
x + 1; // x + 1 is dead code, but may raise FP exceptions
71+
// (unless the optimizer can prove otherwise). However, executing it n times
72+
// will raise the same exception over and over. So this can be optimized to:
73+
if (0 < n)
74+
x + 1;
75+
```
76+
77+
## Example
78+
79+
```cpp
80+
int& preinc(int& n) { return ++n; }
81+
int add(int n, int m) { return n + m; }
82+
83+
// volatile input to prevent constant folding
84+
volatile int input = 7;
85+
86+
// volatile output to make the result a visible side-effect
87+
volatile int result;
88+
89+
int main() {
90+
int n = input;
91+
// using built-in operators would invoke undefined behavior
92+
// int m = ++n + ++n;
93+
// but using functions makes sure the code executes as-if
94+
// the functions were not overlapped
95+
int m = add(preinc(n), preinc(n));
96+
result = m;
97+
}
98+
```
99+
100+
Full code of the `main()` function as produced by the GCC compiler, for different platforms:
101+
102+
<Tabs>
103+
<TabItem label="x86 (Intel)">
104+
```
105+
movl input(%rip), %eax # eax = input
106+
leal 3(%rax,%rax), %eax # eax = 3 + eax + eax
107+
movl %eax, result(%rip) # result = eax
108+
xorl %eax, %eax # eax = 0 (the return value of main())
109+
ret
110+
```
111+
</TabItem>
112+
<TabItem label="PowerPC (IBM)">
113+
```
114+
lwz 9,LC..1(2)
115+
li 3,0 # r3 = 0 (the return value of main())
116+
lwz 11,0(9) # r11 = input;
117+
slwi 11,11,1 # r11 = r11 << 1;
118+
addi 0,11,3 # r0 = r11 + 3;
119+
stw 0,4(9) # result = r0;
120+
blr
121+
```
122+
</TabItem>
123+
<TabItem label="Sparc (Sun)">
124+
```
125+
sethi %hi(result), %g2
126+
sethi %hi(input), %g1
127+
mov 0, %o0 # o0 = 0 (the return value of main)
128+
ld [%g1+%lo(input)], %g1 # g1 = input
129+
add %g1, %g1, %g1 # g1 = g1 + g1
130+
add %g1, 3, %g1 # g1 = 3 + g1
131+
st %g1, [%g2+%lo(result)] # result = g1
132+
jmp %o7+8
133+
nop
134+
```
135+
</TabItem>
136+
</Tabs>
137+
138+
In all cases, the side effects of `preinc()` were eliminated, and the entire `main()` function was reduced to the equivalent of `result = 2 * input + 3;`.
139+
140+
## See also
141+
142+
- <DocLink dest="/cpp/language/initialization/copy_elision">copy elision</DocLink>
143+
144+
<DescList>
145+
<Desc>
146+
<DocLink slot="item" dest="/c/language/basic_concepts/as_if"> C documentation</DocLink> for **as-if rule**
147+
</Desc>
148+
</DescList>

0 commit comments

Comments
 (0)