summaryrefslogtreecommitdiff
path: root/docs/chapter14/README.md
blob: e22b1138941db01e634752a8fda8af1317c0ce6d (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
408
409
410
411
412
413
414
415
416
417
418
# Chapter 14: Numeric Types

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

[Previous: Chapter 13](../chapter13/README.md) |
[Next: Chapter 15](../chapter15/README.md)

# Table of Contents

1. [Floating-point arithmetic](#floating-point-arithmetic)
2. [Narrow Word Types](#narrow-or-sub-word-types)
3. [Overflow handling](#overflow-handling)
4. [Integer ranges](#integer-ranges)
5. [Bitwise operations](#bitwise-operations)
6. [Precedence](#precedence)
7. [Type lattice](#type-implementation)


With structs, reference fields, and scheduling in place, this chapter extends
the numeric values they carry. We add floating-point arithmetic, narrow
integer and float types, and integer ranges.

You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter14) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter13...linear-chapter14) it to the previous chapter.

## Floating-point arithmetic

Floating point values start with the `flt` type and compute IEEE754 64-bit arithmetic.
Integer expressions auto-widen to floats when involved in a float expression.

```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
```

Assigning a float to an integer truncates toward zero:
```java
int rounded = 17.3/2.5;  // 6
rounded = -17.3/2.5;     // -6
```
This applies to variable initialization, reassignment, and stores to integer
fields or array elements. Conversion first produces a signed 64-bit integer:
NaN becomes zero, and values outside the range (including infinities) clamp to
the nearest limit. Narrow integer destinations then apply the same bit
truncation and sign/zero extension as integer-to-integer assignments.

Here is Newton's method for computing a square root:
```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;
```
When run with argument 2, produces 1.414213562373095.

Floating point operations have their own Nodes:

| Node                   | Op                   |
|------------------------|----------------------|
| AddF, SubF, MulF, DivF | Basic binary FP ops  |
| MinusF                 | Unary negate         |
| EQF, LTF, LEF          | FP compare operators |
| ToFloat, ToInteger     | Numeric conversions  |
| RoundF32               | Round to f32 precision |

The parser uses [`Node.widen()`](../../src/main/java/com/seaofnodes/simple/node/Node.java)
to choose the floating-point operation and insert `ToFloatNode` conversions.
The float lattice has constants between `TOP` and `BOT`, like the integer
lattice before ranges. [`TypeFloat`](../../src/main/java/com/seaofnodes/simple/type/TypeFloat.java)
also records whether a value fits `f32`; assigning to `f32` rounds it to that
precision. `ToIntegerNode` performs the inverse numeric conversion on assignment
to an integer destination, folding constants with the same rules as runtime
conversion.


## Narrow or sub-word types

Narrow or sub-word integer types refer to integer data types that occupy less
memory (fewer bits) than the standard or "full-width" (typically 32 or 64 bits)
integer types provided by a system or programming language.

As a prelude to arrays, especially arrays of bytes common in all networking
codes, Simple needs some way to manipulate sub-word integer types.
Computations are always done in 64 bits for both integer and float ops, but
will be truncated (rounded) on a variable or field assignment, and (sign)
extended on a load.

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

The variable `x` is limited to 8 bits signed.  Assigning the very large value
`123456789` (hex `0x75BCD15`) simply truncates to 8 bits yielding `21 (0x15)`.
The value is sign-extended on a load.

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

The variable `x` is limited to 16 bits signed.  Assigning the very large value
`0x75BCD15` simply truncates to 16 bits yielding `0xCD15`.  The value is
sign-extended on a load yielding `-13035 (0xFFFF FFFF FFFF CD15)`.

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

The variable `x` is limited to 16 bits unsigned.  Assigning the very large value
`0x75BCD15` simply truncates to 16 bits yielding `0xCD15`.  The value is
zero-extended on a load yielding `52501 (0x0000 0000 0000 CD15)`.

Similarly, floats can be limited to IEEE754 32-bit standard format and will be
rounded when assigned, and inflated to 64-bits when loaded.

The complete list of primitive types is now:

| Data Type | Size (Bits) | Description                                                  |
|-----------|-------------|--------------------------------------------------------------|
| `int`, `i64` | 64          | 64-bit signed integer, matching modern hardware.           |
| `u64`       | 64          | **Not implemented**, hints at behavior beyond 64-bit.      |
| `i32`       | 32          | 32-bit signed integer, sign-extended on load.              |
| `u32`       | 32          | 32-bit unsigned integer, zero-extended on load.            |
| `i16`       | 16          | 16-bit signed integer, sign-extended on load.              |
| `u16`       | 16          | 16-bit unsigned integer, zero-extended on load.            |
| `i8`        | 8           | 8-bit signed integer, sign-extended on load.               |
| `u8`        | 8           | 8-bit unsigned integer, zero-extended on load.             |
| `i1`        | 1           | 1-bit signed integer, sign-extended on load.               |
| `u1`        | 1           | 1-bit unsigned integer, zero-extended on load.             |
| `bool`      | 1           | Alias for `u1`.                                            |
| `flt`, `f64`| 64          | IEEE 754 64-bit floating-point number, often called a double. |
| `f32`       | 32          | IEEE 754 32-bit floating-point number.                     |

## Overflow handling

An integer overflow occurs when you attempt to store inside an integer variable a value that is larger than the maximum value the variable can hold.
There are several cases of overflow:

#### Unsigned(fixed-width) integer overflow:

```java
u8 a = 255;
u8 b = a + 1; // match u8 type against expr.type
// a = 256
// u8 = [0...255]
return b;
```
The expression type does not match the type that was provided. 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);
}
```
Since it is unsigned we just do a bitmask on the new value that overflows the specified type:
The operands to the "bitwise AND" are the values(256) and the mask(255, max boundary).
This is called truncating.

`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();
}
```

The truncation is done by the `AndNode`:
```java
...
if( t0._min==0 )       // Unsigned
        return new AndNode(val,con(t0._max)).peephole();
```
Since they are constants, after calling peephole it turns into `256 & 255` = 0;
Output:
```
return 0;
```

#### Maximum integer overflow
```java
i64 a = 9223372036854775807;
i64 b = a + 1;
return b;
```
When we are computing the addition, we make sure they don't overflow:

`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);
```
Here **x + y** overflows and becomes **negative**, x ^ negative = negative. Any negative number is less than 0, so it returns true.
If it **overflows** we just return the bottom type of TypeInteger.
```
       return TypeInteger.BOT; // []
```
However, in our case we are dealing with constants, so we just allow the overflow naturally here:
```java
// i1: 9223372036854775807
// i2: 1
// i1 + i2 will overflow.
return TypeInteger.constant(i1.value()+i2.value()); // -9223372036854775808;
```

#### Adding constant ranges
```java
u8 a = 12;
u8 b = 13;
u8 c = 124;
u8 d = a + b + c;
return d;
```
if `is_con` is true, then generally we can state: `min = max`.
Such that:
```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));
```
- This results in:
```java
return make(con, con);
```

`value()` is only called in constants, e.g  `_min == _max;` invariant holds, then since
both `min_` and `max_` are equal, we can return either one.

This is the same as regular additions:
```java
public long value() { assert isConstant(); return _min; }
```

#### More Truncate

```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
```

Same as before, **arg** turns into **(arg&255)**, this node is then inserted into the **GVN** table and  **b** retrieves this value.
The AddNode peephole will change them into `((arg&255)*2)`, and put on extra mask
on the expression to keep it in the valid boundaries.


## Integer ranges

For general range math, visit Hackers Delight:[^1]

## Bitwise operations
We support the following bitwise operators:
And `&`, Or `|`, Xor `^`, Shift left `<<`, Shift right `>>`, Shift right zero `>>>`

| Operator                 | Symbol  | Description                                                                                             |
|--------------------------|---------|---------------------------------------------------------------------------------------------------------|
| **And**                  | `&`     | Performs a bitwise AND operation.                                                                       |
| **Or**                   | `\|`        | Performs a bitwise OR (inclusive OR) operation.                                                         |
| **Xor**                  | `^`     | Performs a bitwise XOR (exclusive OR) operation.                                                        |
| **Shift Left**           | `<<`    | Shifts bits to the left, filling with zeros.                                                            |
| **Shift Right**          | `>>`    | Shifts bits to the right, any vacated bit positions are filled by replicating the value of the sign bit. |
| **Unsigned Right Shift** | `>>>`   | Shifts bits to the right, any vacated bit positions are always filled with zero                          |

Note `>>>` is a logical shift and not an arithmetic shift.

Here is a sample of peephole opts on these logical operations:

####  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;

```

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

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

```
#### Unsigned right shift
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;

```
### precedence

Bitwise operators have the lowest precedence, e.g
```
arg + 123 & 3 = (arg + 123) & 3
```

## Type Implementation

The ![Integer](../chapter10/type-integer.svg) `TypeInteger` class is
reworked to support a full range of min/max values.  At this time, only some
power-of-2 sized ranges are exposed to the programmer but the optimizer
internally supports all ranges.  The MEET operation takes the min-of-mins and
max-of-maxes.  The DUAL operation inverts the min and max.

Example: the constant `0` has `[0...0]`.

Example: the `bool` type has the range `[0...1]`.

Example: the `u8` type has the range `[0...255]`.

Example: the `i8` type has the range `[-128...127]`.

Example: the `dual` of `bool` is `[1...0]` (just swap min and max).

The ![Float](../type-float.svg) `TypeFloat` class introduced here supports
both 32-bit and 64-bit sizes.

The integer range diagram is schematic: it shows the boolean range and its
dual as examples among the possible ranges.  The other domains retain the
named structs, nullable pointers, and precise memory aliases from earlier chapters.

![Graph1](./lattice.svg)

### meet:

The Meet of two ranges:
```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));
}
```

This combines all values that could possibly occur within either range.
Also, the implementation is trivial.



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

The next chapter uses these numeric types for array lengths, indexing, and
element storage, including byte arrays.