summaryrefslogtreecommitdiff
path: root/chapter14/README.ja.md
blob: 6fa0cd22006592ce6f2e51de3683024b2db27876 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# 第14章: 数値型

[English](README.md) | 日本語

[前: 第13章](../chapter13/README.ja.md) |
[次: 第15章](../chapter15/README.ja.md)

この文書は [原文](README.md) の日本語訳です。

# 目次

1. [浮動小数点演算](#浮動小数点演算)
2. [狭いワード型](#狭い型とサブワード型)
3. [オーバーフローの処理](#オーバーフローの処理)
4. [整数の範囲](#整数の範囲)
5. [ビット演算](#ビット演算)
6. [優先順位](#優先順位)
7. [型の束](#型の実装)

構造体、参照フィールド、スケジューリングがそろったので、この章では、それらが運ぶ数値を拡張します。浮動小数点演算、狭い整数型と浮動小数点型、整数の範囲を追加します。

[linear](https://github.com/SeaOfNodes/Simple/tree/linear) ブランチの線形の Git リビジョン履歴で[この章](https://github.com/SeaOfNodes/Simple/tree/linear-chapter14)を読むことや、前章との[差分](https://github.com/SeaOfNodes/Simple/compare/linear-chapter13...linear-chapter14)を見ることもできます。

## 浮動小数点演算

浮動小数点値は `flt` 型から始まり、IEEE754 の64ビット演算を行います。整数式は、浮動小数点式に含まれると自動的に浮動小数点へ拡張されます。

```java
flt f = 0.5;
int x = 2;
f = f+x;     // Auto-widen x to 2.0, result is 2.5
f = f+true;  // Since bools are just synonyms for integers 0,1 they auto-widen also
```

今のところ、逆に `flt` を丸めて `int` に戻す方法はありません。

```java
int x = 3.14; // error
```

平方根を計算するニュートン法を示します。

```java
flt guess = arg;  // Initial guess is just argument
while( true ) {
    // Next guess from Newton's method
    flt next = (arg/guess + guess)/2;
    // Break if hit fixed point
    if( next == guess ) break;
    guess = next;
}
return guess;
```

引数 2 で実行すると 1.414213562373095 を生成します。

浮動小数点演算には専用の Node があります。

| ノード | 演算 |
|---|---|
| AddF, SubF, MulF, DivF | 基本的な二項浮動小数点演算 |
| MinusF | 単項の符号反転 |
| EQF, LTF, LEF | 浮動小数点の比較演算子 |

パーサーは [`Node.widen()`](src/main/java/com/seaofnodes/simple/node/Node.java) を使って浮動小数点演算を選び、`ToFloatNode` による変換を挿入します。浮動小数点の束は、範囲導入前の整数の束のように、`TOP` と `BOT` の間に定数を持ちます。[`TypeFloat`](src/main/java/com/seaofnodes/simple/type/TypeFloat.java) は、値が `f32` に収まるかどうかも記録します。`f32` への代入では、その精度に丸めます。浮動小数点から整数への変換は、別の未実装の言語機能として残ります。

## 狭い型とサブワード型

狭い整数型、またはサブワード整数型とは、システムやプログラミング言語が提供する標準的な「全幅」(通常32または64ビット)の整数型よりも、使用するメモリ(ビット数)が少ない整数データ型のことです。

配列、特にあらゆるネットワークコードでよく使うバイト配列の前段階として、Simple にはサブワード整数型を操作する方法が必要です。整数と浮動小数点の演算は、常に64ビットで計算しますが、変数やフィールドへの代入時には切り詰め(丸め)、ロード時には(符号)拡張します。

```java
i8 x = 123456789;
if( x != 21 )
  return false;
return true;
```

変数 `x` は符号付き8ビットに制限されます。非常に大きい値 `123456789`(16進数では `0x75BCD15`)を代入すると、単に8ビットに切り詰めて `21 (0x15)` になります。ロード時には符号拡張されます。

```java
i16 x = 123456789;
if( x != -13035 )
  return false;
return true;
```

変数 `x` は符号付き16ビットに制限されます。非常に大きい値 `0x75BCD15` を代入すると、単に16ビットに切り詰めて `0xCD15` になります。ロード時には符号拡張され、`-13035 (0xFFFF FFFF FFFF CD15)` になります。

```java
u16 x = 123456789;
if( x != 52501 )
  return false;
return true;
```

変数 `x` は符号なし16ビットに制限されます。非常に大きい値 `0x75BCD15` を代入すると、単に16ビットに切り詰めて `0xCD15` になります。ロード時にはゼロ拡張され、`52501 (0x0000 0000 0000 CD15)` になります。

同様に、浮動小数点数を IEEE754 の標準32ビット形式に制限できます。代入時には丸め、ロード時には64ビットに拡張します。

これでプリミティブ型の完全な一覧は次のようになります。

| データ型 | サイズ(ビット) | 説明 |
|---|---|---|
| `int`, `i64` | 64 | 現代のハードウェアに対応する64ビット符号付き整数。 |
| `u64` | 64 | **未実装**。64ビットを超える振る舞いを示唆します。 |
| `i32` | 32 | 32ビット符号付き整数。ロード時に符号拡張。 |
| `u32` | 32 | 32ビット符号なし整数。ロード時にゼロ拡張。 |
| `i16` | 16 | 16ビット符号付き整数。ロード時に符号拡張。 |
| `u16` | 16 | 16ビット符号なし整数。ロード時にゼロ拡張。 |
| `i8` | 8 | 8ビット符号付き整数。ロード時に符号拡張。 |
| `u8` | 8 | 8ビット符号なし整数。ロード時にゼロ拡張。 |
| `i1` | 1 | 1ビット符号付き整数。ロード時に符号拡張。 |
| `u1` | 1 | 1ビット符号なし整数。ロード時にゼロ拡張。 |
| `bool` | 1 | `u1` の別名。 |
| `flt`, `f64` | 64 | IEEE 754 の64ビット浮動小数点数。一般に double と呼ばれます。 |
| `f32` | 32 | IEEE 754 の32ビット浮動小数点数。 |

## オーバーフローの処理

整数変数が保持できる最大値より大きな値を、その変数に格納しようとすると整数オーバーフローが起きます。オーバーフローにはいくつかの場合があります。

#### 符号なし(固定幅)整数のオーバーフロー

```java
u8 a = 255;
u8 b = a + 1; // match u8 type against expr.type
// a = 256
// u8 = [0...255]
return b;
```

式の型は、指定した型に一致しません。expr: ConstantNode(256) t: u8(0, 255)

```java
private Node parseExpressionStatement() {
    Type t = type(); // u8 = [0...255]
    ...
    // expr.type = TypeInteger(256);
    // Auto-narrow wide ints to narrow ints
    expr = zsMask(expr,t);
}
```

符号なしなので、指定した型からあふれる新しい値にビットマスクをかけるだけです。「ビットごとの AND」のオペランドは、値(256)とマスク(255、上限)です。これを切り詰めと呼びます。

`ZsMask:`

```java
    // zero/sign extend.  "i" is limited to either classic unsigned (min==0) or
    // classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
private Node zsMask(Node val, Type t ) {
    if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) {
        if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) )
            return val;
        // Float rounding
        return new RoundF32Node(val).peephole();
    }
    if( t0._min==0 )        // Unsigned
        return new AndNode(val,new ConstantNode(TypeInteger.constant(t0._max)).peephole()).peephole();
    // Signed extension
    int shift = Long.numberOfLeadingZeros(t0._max)-1;
    Node shf = new ConstantNode(TypeInteger.constant(shift)).peephole();
    if( shf._type==TypeInteger.ZERO )
        return val;
    return new SarNode(new ShlNode(val,shf.keep()).peephole(),shf.unkeep()).peephole();
}
```

切り詰めは `AndNode` が行います。

```java
...
if( t0._min==0 )       // Unsigned
        return new AndNode(val,con(t0._max)).peephole();
```

これらは定数なので、peephole を呼ぶと `256 & 255` = 0 になります。出力は次の通りです。

```
return 0;
```

#### 最大整数のオーバーフロー

```java
i64 a = 9223372036854775807;
i64 b = a + 1;
return b;
```

加算の計算時には、オーバーフローしないことを確認します。

`AddNode.overflow:`

```java
private static boolean overflow( long x, long y ) {
    if(    (x ^      y ) < 0 ) return false; // unequal signs, never overflow
    return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow
}
```

`AddNode.compute`

```java
...
// Fold ranges like {0-1} + {2-3} into {2-4}.
if( !overflow(i1._min,i2._min) &&
    !overflow(i1._max,i2._max) )
    return TypeInteger.make(i1._min+i2._min,i1._max+i2._max);
```

ここで **x + y** はオーバーフローして**負**になり、x ^ 負の値 = 負の値になります。負の数はすべて 0 より小さいので、true を返します。**オーバーフロー**する場合、TypeInteger のボトム型を返すだけです。

```
       return TypeInteger.BOT; // []
```

ただし、この例では定数を扱っているので、ここではそのまま自然にオーバーフローさせます。

```java
// i1: 9223372036854775807
// i2: 1
// i1 + i2 will overflow.
return TypeInteger.constant(i1.value()+i2.value()); // -9223372036854775808;
```

#### 定数の範囲の加算

```java
u8 a = 12;
u8 b = 13;
u8 c = 124;
u8 d = a + b + c;
return d;
```

`is_con` が true なら、一般に `min = max` と言えます。したがって次のようになります。

```java
public static TypeInteger make(boolean is_con, long con) {
    return make(is_con ? con : (con==0 ? Long.MAX_VALUE : Long.MIN_VALUE),
                is_con ? con : (con==0 ? Long.MIN_VALUE : Long.MAX_VALUE));
```

- この結果は次の通りです。

```java
return make(con, con);
```

`value()` は定数に対してだけ呼ばれます。つまり `_min == _max;` という不変条件が成り立つため、`min_` と `max_` は等しく、どちらを返してもかまいません。

これは通常の加算と同じです。

```java
public long value() { assert isConstant(); return _min; }
```

#### さらに切り詰める

```java
u8 a = arg;  // arg & 255
u8 b = arg;  // arg & 255 (same as a)
u8 c = a + b; // (arg&255)*2, type = TypeInteger.BOT
return c; // (((arg&255)*2)&255), type = TypeInteger.U8
```

前と同じく、**arg** は **(arg&255)** になります。このノードを **GVN** テーブルに挿入し、**b** はその値を取得します。AddNode のピープホール最適化はこれらを `((arg&255)*2)` に変え、式に追加のマスクをかけて有効な境界内に保ちます。

## 整数の範囲

一般的な範囲の計算については、Hacker's Delight を参照してください。[^1]

## ビット演算

次のビット演算子をサポートします。
AND `&`、OR `|`、XOR `^`、左シフト `<<`、右シフト `>>`、ゼロ埋め右シフト `>>>`

| 演算子 | 記号 | 説明 |
|---|---|---|
| **AND** | `&` | ビットごとの AND 演算。 |
| **OR** | `\|` | ビットごとの OR(包含的論理和)演算。 |
| **XOR** | `^` | ビットごとの XOR(排他的論理和)演算。 |
| **左シフト** | `<<` | ビットを左にずらし、ゼロで埋めます。 |
| **右シフト** | `>>` | ビットを右にずらし、空いた位置に符号ビットの値を複製して埋めます。 |
| **符号なし右シフト** | `>>>` | ビットを右にずらし、空いた位置を常にゼロで埋めます。 |

`>>>` は算術シフトではなく論理シフトです。

これらの論理演算のピープホール最適化の例を示します。

#### AndNode:

##### lhs & -1 = lhs;

```java
// And of -1.  We do not check for (-1&x) because this will already
// canonicalize to (x&-1)
if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 )
return lhs;

```

#### OrNode:

##### lhs | 0 = lhs;

```java
// Or of 0.  We do not check for (0|x) because this will already
// canonicalize to (x|0)
if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
return lhs;

```

#### 右シフト

lhs >> 0 = lhs;

```java
// Sar of 0.
if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
    return lhs;
```

#### 左シフト

lhs << 0 = lhs;

```java
// Shl of 0.
if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
    return lhs;

```

#### 符号なし右シフト

lhs >>> 0 = lhs;

```java
// Shr of 0.
if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
return lhs;
```

#### XorNode:

##### lhs ^ 0 = lhs;

```java
// Xor of 0.  We do not check for (0^x) because this will already
// canonicalize to (x^0)
if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
    return lhs;

```

### 優先順位

ビット演算子の優先順位は最も低くなります。たとえば次のようになります。

```
arg + 123 & 3 = (arg + 123) & 3
```

## 型の実装

![整数](../chapter10/docs/type-integer.svg)の `TypeInteger` クラスを作り直し、min/max 値のあらゆる範囲をサポートします。今のところ、プログラマーに公開するのはサイズが2のべき乗になる一部の範囲だけですが、最適化器の内部ではすべての範囲をサポートします。MEET 操作は最小値の最小と最大値の最大を取ります。DUAL 操作は最小値と最大値を入れ替えます。

例: 定数 `0` は `[0...0]` です。

例: `bool` 型は範囲 `[0...1]` を持ちます。

例: `u8` 型は範囲 `[0...255]` を持ちます。

例: `i8` 型は範囲 `[-128...127]` を持ちます。

例: `bool` の `dual` は `[1...0]` です(最小値と最大値を交換するだけです)。

ここで導入する![浮動小数点](../docs/type-float.svg)の `TypeFloat` クラスは、32ビットと64ビットの両方のサイズをサポートします。

整数範囲の図は模式図で、取り得る範囲の例としてブール値の範囲とその双対を示しています。ほかの領域は、前の章の名前付き構造体、null 許容ポインタ、精密なメモリエイリアスを維持します。

![グラフ1](./docs/lattice.svg)

### meet:

2つの範囲の Meet は次の通りです。

```java
@Override
public Type xmeet(Type other) {
// Invariant from caller: 'this' != 'other' and same class (TypeInteger)
TypeInteger i = (TypeInteger)other; // Contract
return make(Math.min(_min,i._min), Math.max(_max,i._max));
}
```

これによって、どちらかの範囲内に起こり得るすべての値を組み合わせます。また、実装もごく単純です。

[^1]: Hacker's delight.
    4-2 Propagating Bounds through Adds and Subtracts

次の章では、これらの数値型を配列の長さ、添字、バイト配列を含む要素の格納に使います。