@@ -436,8 +436,8 @@ def load(cx, ptr, t):
436
436
case S16() : return load_int(cx, ptr, 2 , signed = True )
437
437
case S32() : return load_int(cx, ptr, 4 , signed = True )
438
438
case S64() : return load_int(cx, ptr, 8 , signed = True )
439
- case Float32() : return canonicalize32 (reinterpret_i32_as_float(load_int(cx, ptr, 4 )))
440
- case Float64() : return canonicalize64 (reinterpret_i64_as_float(load_int(cx, ptr, 8 )))
439
+ case Float32() : return maybe_scramble_nan32 (reinterpret_i32_as_float(load_int(cx, ptr, 4 )))
440
+ case Float64() : return maybe_scramble_nan64 (reinterpret_i64_as_float(load_int(cx, ptr, 8 )))
441
441
case Char() : return convert_i32_to_char(cx, load_int(cx, ptr, 4 ))
442
442
case String() : return load_string(cx, ptr)
443
443
case List(t) : return load_list(cx, ptr, t)
@@ -463,28 +463,51 @@ def convert_int_to_bool(i):
463
463
return bool (i)
464
464
```
465
465
466
- For reasons [ given] ( Explainer.md#type-definitions ) in the explainer, floats are
467
- loaded from memory and then "canonicalized", mapping all Not-a-Number bit
468
- patterns to a single canonical ` nan ` value.
466
+ Lifting and lowering float values may (from the component's perspective)
467
+ non-deterministically modify the sign and payload bits of Not-A-Number (NaN)
468
+ values, reflecting the practical reality that different languages, protocols
469
+ and CPUs have different effects on NaNs. Although this non-determinism is
470
+ expressed in the Python code below as generating a "random" NaN bit-pattern,
471
+ native implementations do not need to literally generate a random bit-pattern;
472
+ they may canonicalize to an arbitrary fixed NaN value. When a host implements
473
+ the [ deterministic profile] , NaNs are canonicalized to a particular NaN
474
+ bit-pattern.
469
475
``` python
470
- def reinterpret_i32_as_float (i ):
471
- return struct.unpack(' !f' , struct.pack(' !I' , i))[0 ] # f32.reinterpret_i32
472
-
473
- def reinterpret_i64_as_float (i ):
474
- return struct.unpack(' !d' , struct.pack(' !Q' , i))[0 ] # f64.reinterpret_i64
475
-
476
+ DETERMINISTIC_PROFILE = False # or True
477
+ THE_HOST_WANTS_TO = True # or False
476
478
CANONICAL_FLOAT32_NAN = 0x 7fc00000
477
479
CANONICAL_FLOAT64_NAN = 0x 7ff8000000000000
478
480
479
- def canonicalize32 (f ):
481
+ def maybe_scramble_nan32 (f ):
480
482
if math.isnan(f):
481
- return reinterpret_i32_as_float(CANONICAL_FLOAT32_NAN )
483
+ if DETERMINISTIC_PROFILE :
484
+ f = reinterpret_i32_as_float(CANONICAL_FLOAT32_NAN )
485
+ elif THE_HOST_WANTS_TO :
486
+ f = reinterpret_i32_as_float(random_nan_bits(32 , 8 ))
487
+ assert (math.isnan(f))
482
488
return f
483
489
484
- def canonicalize64 (f ):
490
+ def maybe_scramble_nan64 (f ):
485
491
if math.isnan(f):
486
- return reinterpret_i64_as_float(CANONICAL_FLOAT64_NAN )
492
+ if DETERMINISTIC_PROFILE :
493
+ f = reinterpret_i64_as_float(CANONICAL_FLOAT64_NAN )
494
+ elif THE_HOST_WANTS_TO :
495
+ f = reinterpret_i64_as_float(random_nan_bits(64 , 11 ))
496
+ assert (math.isnan(f))
487
497
return f
498
+
499
+ def reinterpret_i32_as_float (i ):
500
+ return struct.unpack(' !f' , struct.pack(' !I' , i))[0 ] # f32.reinterpret_i32
501
+
502
+ def reinterpret_i64_as_float (i ):
503
+ return struct.unpack(' !d' , struct.pack(' !Q' , i))[0 ] # f64.reinterpret_i64
504
+
505
+ def random_nan_bits (total_bits , exponent_bits ):
506
+ fraction_bits = total_bits - exponent_bits - 1
507
+ bits = random.getrandbits(total_bits)
508
+ bits |= ((1 << exponent_bits) - 1 ) << fraction_bits
509
+ bits |= 1 << random.randrange(fraction_bits - 1 )
510
+ return bits
488
511
```
489
512
490
513
An ` i32 ` is converted to a ` char ` (a [ Unicode Scalar Value] ) by dynamically
@@ -674,8 +697,8 @@ def store(cx, v, t, ptr):
674
697
case S16() : store_int(cx, v, ptr, 2 , signed = True )
675
698
case S32() : store_int(cx, v, ptr, 4 , signed = True )
676
699
case S64() : store_int(cx, v, ptr, 8 , signed = True )
677
- case Float32() : store_int(cx, reinterpret_float_as_i32(canonicalize32 (v)), ptr, 4 )
678
- case Float64() : store_int(cx, reinterpret_float_as_i64(canonicalize64 (v)), ptr, 8 )
700
+ case Float32() : store_int(cx, reinterpret_float_as_i32(maybe_scramble_nan32 (v)), ptr, 4 )
701
+ case Float64() : store_int(cx, reinterpret_float_as_i64(maybe_scramble_nan64 (v)), ptr, 8 )
679
702
case Char() : store_int(cx, char_to_i32(v), ptr, 4 )
680
703
case String() : store_string(cx, v, ptr)
681
704
case List(t) : store_list(cx, v, ptr, t)
@@ -695,9 +718,8 @@ def store_int(cx, v, ptr, nbytes, signed = False):
695
718
cx.opts.memory[ptr : ptr+ nbytes] = int .to_bytes(v, nbytes, ' little' , signed = signed)
696
719
```
697
720
698
- Floats are stored directly into memory (in the case of NaNs, using the
699
- 32-/64-bit canonical NaN bit pattern selected by
700
- ` canonicalize32 ` /` canonicalize64 ` ):
721
+ Floats are stored directly into memory (after the NaN-scrambling described
722
+ above):
701
723
``` python
702
724
def reinterpret_float_as_i32 (f ):
703
725
return struct.unpack(' !I' , struct.pack(' !f' , f))[0 ] # i32.reinterpret_f32
@@ -1153,8 +1175,8 @@ def lift_flat(cx, vi, t):
1153
1175
case S16() : return lift_flat_signed(vi, 32 , 16 )
1154
1176
case S32() : return lift_flat_signed(vi, 32 , 32 )
1155
1177
case S64() : return lift_flat_signed(vi, 64 , 64 )
1156
- case Float32() : return canonicalize32 (vi.next(' f32' ))
1157
- case Float64() : return canonicalize64 (vi.next(' f64' ))
1178
+ case Float32() : return maybe_scramble_nan32 (vi.next(' f32' ))
1179
+ case Float64() : return maybe_scramble_nan64 (vi.next(' f64' ))
1158
1180
case Char() : return convert_i32_to_char(cx, vi.next(' i32' ))
1159
1181
case String() : return lift_flat_string(cx, vi)
1160
1182
case List(t) : return lift_flat_list(cx, vi, t)
@@ -1277,8 +1299,8 @@ def lower_flat(cx, v, t):
1277
1299
case S16() : return lower_flat_signed(v, 32 )
1278
1300
case S32() : return lower_flat_signed(v, 32 )
1279
1301
case S64() : return lower_flat_signed(v, 64 )
1280
- case Float32() : return [Value(' f32' , canonicalize32 (v))]
1281
- case Float64() : return [Value(' f64' , canonicalize64 (v))]
1302
+ case Float32() : return [Value(' f32' , maybe_scramble_nan32 (v))]
1303
+ case Float64() : return [Value(' f64' , maybe_scramble_nan64 (v))]
1282
1304
case Char() : return [Value(' i32' , char_to_i32(v))]
1283
1305
case String() : return lower_flat_string(cx, v)
1284
1306
case List(t) : return lower_flat_list(cx, v, t)
@@ -1656,6 +1678,7 @@ component instance defining a resource can access its representation.
1656
1678
[ Multi-value ] : https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md
1657
1679
[ Exceptions ] : https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md
1658
1680
[ WASI ] : https://github.com/webassembly/wasi
1681
+ [ Deterministic Profile ] : https://github.com/WebAssembly/profiles/blob/main/proposals/profiles/Overview.md
1659
1682
1660
1683
[ Alignment ] : https://en.wikipedia.org/wiki/Data_structure_alignment
1661
1684
[ UTF-8 ] : https://en.wikipedia.org/wiki/UTF-8
0 commit comments