@@ -442,8 +442,8 @@ def load(cx, ptr, t):
442
442
case S16() : return load_int(cx, ptr, 2 , signed = True )
443
443
case S32() : return load_int(cx, ptr, 4 , signed = True )
444
444
case S64() : return load_int(cx, ptr, 8 , signed = True )
445
- case Float32() : return maybe_scramble_nan32(reinterpret_i32_as_float( load_int(cx, ptr, 4 ) ))
446
- case Float64() : return maybe_scramble_nan64(reinterpret_i64_as_float( load_int(cx, ptr, 8 ) ))
445
+ case Float32() : return decode_i32_as_float( load_int(cx, ptr, 4 ))
446
+ case Float64() : return decode_i64_as_float( load_int(cx, ptr, 8 ))
447
447
case Char() : return convert_i32_to_char(cx, load_int(cx, ptr, 4 ))
448
448
case String() : return load_string(cx, ptr)
449
449
case List(t) : return load_list(cx, ptr, t)
@@ -469,51 +469,42 @@ def convert_int_to_bool(i):
469
469
return bool (i)
470
470
```
471
471
472
- Lifting and lowering float values may (from the component's perspective)
473
- non-deterministically modify the sign and payload bits of Not-A-Number (NaN)
474
- values, reflecting the practical reality that different languages, protocols
475
- and CPUs have different effects on NaNs. Although this non-determinism is
476
- expressed in the Python code below as generating a "random" NaN bit-pattern,
477
- native implementations do not need to literally generate a random bit-pattern;
478
- they may canonicalize to an arbitrary fixed NaN value. When a host implements
479
- the [ deterministic profile] , NaNs are canonicalized to a particular NaN
480
- bit-pattern.
472
+ Floats are loaded directly from memory, with the sign and payload information
473
+ of NaN values discarded. Consequently, there is only one unique NaN value per
474
+ floating-point type. This reflects the practical reality that some languages
475
+ and protocols do not preserve these bits. In the Python code below, this is
476
+ expressed as canonicalizing NaNs to a particular bit pattern.
477
+
478
+ See the comments about lowering of float values for a discussion of possible
479
+ optimizations.
481
480
``` python
482
481
DETERMINISTIC_PROFILE = False # or True
483
- THE_HOST_WANTS_TO = True # or False
484
482
CANONICAL_FLOAT32_NAN = 0x 7fc00000
485
483
CANONICAL_FLOAT64_NAN = 0x 7ff8000000000000
486
484
487
- def maybe_scramble_nan32 (f ):
485
+ def canonicalize_nan32 (f ):
488
486
if math.isnan(f):
489
- if DETERMINISTIC_PROFILE :
490
- f = reinterpret_i32_as_float(CANONICAL_FLOAT32_NAN )
491
- elif THE_HOST_WANTS_TO :
492
- f = reinterpret_i32_as_float(random_nan_bits(32 , 8 ))
487
+ f = core_f32_reinterpret_i32(CANONICAL_FLOAT32_NAN )
493
488
assert (math.isnan(f))
494
489
return f
495
490
496
- def maybe_scramble_nan64 (f ):
491
+ def canonicalize_nan64 (f ):
497
492
if math.isnan(f):
498
- if DETERMINISTIC_PROFILE :
499
- f = reinterpret_i64_as_float(CANONICAL_FLOAT64_NAN )
500
- elif THE_HOST_WANTS_TO :
501
- f = reinterpret_i64_as_float(random_nan_bits(64 , 11 ))
493
+ f = core_f64_reinterpret_i64(CANONICAL_FLOAT64_NAN )
502
494
assert (math.isnan(f))
503
495
return f
504
496
505
- def reinterpret_i32_as_float (i ):
497
+ def decode_i32_as_float (i ):
498
+ return canonicalize_nan32(core_f32_reinterpret_i32(i))
499
+
500
+ def decode_i64_as_float (i ):
501
+ return canonicalize_nan64(core_f64_reinterpret_i64(i))
502
+
503
+ def core_f32_reinterpret_i32 (i ):
506
504
return struct.unpack(' !f' , struct.pack(' !I' , i))[0 ] # f32.reinterpret_i32
507
505
508
- def reinterpret_i64_as_float (i ):
506
+ def core_f64_reinterpret_i64 (i ):
509
507
return struct.unpack(' !d' , struct.pack(' !Q' , i))[0 ] # f64.reinterpret_i64
510
-
511
- def random_nan_bits (total_bits , exponent_bits ):
512
- fraction_bits = total_bits - exponent_bits - 1
513
- bits = random.getrandbits(total_bits)
514
- bits |= ((1 << exponent_bits) - 1 ) << fraction_bits
515
- bits |= 1 << random.randrange(fraction_bits - 1 )
516
- return bits
517
508
```
518
509
519
510
An ` i32 ` is converted to a ` char ` (a [ Unicode Scalar Value] ) by dynamically
@@ -703,8 +694,8 @@ def store(cx, v, t, ptr):
703
694
case S16() : store_int(cx, v, ptr, 2 , signed = True )
704
695
case S32() : store_int(cx, v, ptr, 4 , signed = True )
705
696
case S64() : store_int(cx, v, ptr, 8 , signed = True )
706
- case Float32() : store_int(cx, reinterpret_float_as_i32(maybe_scramble_nan32(v) ), ptr, 4 )
707
- case Float64() : store_int(cx, reinterpret_float_as_i64(maybe_scramble_nan64(v) ), ptr, 8 )
697
+ case Float32() : store_int(cx, encode_float_as_i32(v ), ptr, 4 )
698
+ case Float64() : store_int(cx, encode_float_as_i64(v ), ptr, 8 )
708
699
case Char() : store_int(cx, char_to_i32(v), ptr, 4 )
709
700
case String() : store_string(cx, v, ptr)
710
701
case List(t) : store_list(cx, v, ptr, t)
@@ -724,13 +715,55 @@ def store_int(cx, v, ptr, nbytes, signed = False):
724
715
cx.opts.memory[ptr : ptr+ nbytes] = int .to_bytes(v, nbytes, ' little' , signed = signed)
725
716
```
726
717
727
- Floats are stored directly into memory (after the NaN-scrambling described
728
- above):
718
+ Floats are stored directly into memory, with the sign and payload bits of NaN
719
+ values modified non-deterministically. This reflects the practical reality that
720
+ different languages, protocols and CPUs have different effects on NaNs.
721
+
722
+ Although this non-determinism is expressed in the Python code below as
723
+ generating a "random" NaN bit-pattern, native implementations do not need to
724
+ use the same "random" algorithm, or even any random algorithm at all. Hosts
725
+ may instead chose to canonicalize to an arbitrary fixed NaN value, or even to
726
+ the original value of the NaN before lifting, allowing them to optimize away
727
+ both the canonicalization of lifting and the randomization of lowering.
728
+
729
+ When a host implements the [ deterministic profile] , NaNs are canonicalized to
730
+ a particular NaN bit-pattern.
729
731
``` python
730
- def reinterpret_float_as_i32 (f ):
732
+ def maybe_scramble_nan32 (f ):
733
+ if math.isnan(f):
734
+ if DETERMINISTIC_PROFILE :
735
+ f = core_f32_reinterpret_i32(CANONICAL_FLOAT32_NAN )
736
+ else :
737
+ f = core_f32_reinterpret_i32(random_nan_bits(32 , 8 ))
738
+ assert (math.isnan(f))
739
+ return f
740
+
741
+ def maybe_scramble_nan64 (f ):
742
+ if math.isnan(f):
743
+ if DETERMINISTIC_PROFILE :
744
+ f = core_f64_reinterpret_i64(CANONICAL_FLOAT64_NAN )
745
+ else :
746
+ f = core_f64_reinterpret_i64(random_nan_bits(64 , 11 ))
747
+ assert (math.isnan(f))
748
+ return f
749
+
750
+ def random_nan_bits (total_bits , exponent_bits ):
751
+ fraction_bits = total_bits - exponent_bits - 1
752
+ bits = random.getrandbits(total_bits)
753
+ bits |= ((1 << exponent_bits) - 1 ) << fraction_bits
754
+ bits |= 1 << random.randrange(fraction_bits - 1 )
755
+ return bits
756
+
757
+ def encode_float_as_i32 (f ):
758
+ return core_i32_reinterpret_f32(maybe_scramble_nan32(f))
759
+
760
+ def encode_float_as_i64 (f ):
761
+ return core_i64_reinterpret_f64(maybe_scramble_nan64(f))
762
+
763
+ def core_i32_reinterpret_f32 (f ):
731
764
return struct.unpack(' !I' , struct.pack(' !f' , f))[0 ] # i32.reinterpret_f32
732
765
733
- def reinterpret_float_as_i64 (f ):
766
+ def core_i64_reinterpret_f64 (f ):
734
767
return struct.unpack(' !Q' , struct.pack(' !d' , f))[0 ] # i64.reinterpret_f64
735
768
```
736
769
@@ -1181,8 +1214,8 @@ def lift_flat(cx, vi, t):
1181
1214
case S16() : return lift_flat_signed(vi, 32 , 16 )
1182
1215
case S32() : return lift_flat_signed(vi, 32 , 32 )
1183
1216
case S64() : return lift_flat_signed(vi, 64 , 64 )
1184
- case Float32() : return maybe_scramble_nan32 (vi.next(' f32' ))
1185
- case Float64() : return maybe_scramble_nan64 (vi.next(' f64' ))
1217
+ case Float32() : return canonicalize_nan32 (vi.next(' f32' ))
1218
+ case Float64() : return canonicalize_nan64 (vi.next(' f64' ))
1186
1219
case Char() : return convert_i32_to_char(cx, vi.next(' i32' ))
1187
1220
case String() : return lift_flat_string(cx, vi)
1188
1221
case List(t) : return lift_flat_list(cx, vi, t)
@@ -1256,10 +1289,10 @@ def lift_flat_variant(cx, vi, cases):
1256
1289
have = flat_types.pop(0 )
1257
1290
x = vi.next(have)
1258
1291
match (have, want):
1259
- case (' i32' , ' f32' ) : return reinterpret_i32_as_float (x)
1292
+ case (' i32' , ' f32' ) : return decode_i32_as_float (x)
1260
1293
case (' i64' , ' i32' ) : return wrap_i64_to_i32(x)
1261
- case (' i64' , ' f32' ) : return reinterpret_i32_as_float (wrap_i64_to_i32(x))
1262
- case (' i64' , ' f64' ) : return reinterpret_i64_as_float (x)
1294
+ case (' i64' , ' f32' ) : return decode_i32_as_float (wrap_i64_to_i32(x))
1295
+ case (' i64' , ' f64' ) : return decode_i64_as_float (x)
1263
1296
case _ : return x
1264
1297
c = cases[case_index]
1265
1298
if c.t is None :
@@ -1367,10 +1400,10 @@ def lower_flat_variant(cx, v, cases):
1367
1400
for i,have in enumerate (payload):
1368
1401
want = flat_types.pop(0 )
1369
1402
match (have.t, want):
1370
- case (' f32' , ' i32' ) : payload[i] = Value(' i32' , reinterpret_float_as_i32 (have.v))
1403
+ case (' f32' , ' i32' ) : payload[i] = Value(' i32' , encode_float_as_i32 (have.v))
1371
1404
case (' i32' , ' i64' ) : payload[i] = Value(' i64' , have.v)
1372
- case (' f32' , ' i64' ) : payload[i] = Value(' i64' , reinterpret_float_as_i32 (have.v))
1373
- case (' f64' , ' i64' ) : payload[i] = Value(' i64' , reinterpret_float_as_i64 (have.v))
1405
+ case (' f32' , ' i64' ) : payload[i] = Value(' i64' , encode_float_as_i32 (have.v))
1406
+ case (' f64' , ' i64' ) : payload[i] = Value(' i64' , encode_float_as_i64 (have.v))
1374
1407
case _ : pass
1375
1408
for want in flat_types:
1376
1409
payload.append(Value(want, 0 ))
0 commit comments