@@ -436,8 +436,8 @@ def load(cx, ptr, t):
436436 case S16() : return load_int(cx, ptr, 2 , signed = True )
437437 case S32() : return load_int(cx, ptr, 4 , signed = True )
438438 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 )))
441441 case Char() : return convert_i32_to_char(cx, load_int(cx, ptr, 4 ))
442442 case String() : return load_string(cx, ptr)
443443 case List(t) : return load_list(cx, ptr, t)
@@ -463,28 +463,51 @@ def convert_int_to_bool(i):
463463 return bool (i)
464464```
465465
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.
469475``` 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
476478CANONICAL_FLOAT32_NAN = 0x 7fc00000
477479CANONICAL_FLOAT64_NAN = 0x 7ff8000000000000
478480
479- def canonicalize32 (f ):
481+ def maybe_scramble_nan32 (f ):
480482 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))
482488 return f
483489
484- def canonicalize64 (f ):
490+ def maybe_scramble_nan64 (f ):
485491 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))
487497 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
488511```
489512
490513An ` i32 ` is converted to a ` char ` (a [ Unicode Scalar Value] ) by dynamically
@@ -674,8 +697,8 @@ def store(cx, v, t, ptr):
674697 case S16() : store_int(cx, v, ptr, 2 , signed = True )
675698 case S32() : store_int(cx, v, ptr, 4 , signed = True )
676699 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 )
679702 case Char() : store_int(cx, char_to_i32(v), ptr, 4 )
680703 case String() : store_string(cx, v, ptr)
681704 case List(t) : store_list(cx, v, ptr, t)
@@ -695,9 +718,8 @@ def store_int(cx, v, ptr, nbytes, signed = False):
695718 cx.opts.memory[ptr : ptr+ nbytes] = int .to_bytes(v, nbytes, ' little' , signed = signed)
696719```
697720
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):
701723``` python
702724def reinterpret_float_as_i32 (f ):
703725 return struct.unpack(' !I' , struct.pack(' !f' , f))[0 ] # i32.reinterpret_f32
@@ -1153,8 +1175,8 @@ def lift_flat(cx, vi, t):
11531175 case S16() : return lift_flat_signed(vi, 32 , 16 )
11541176 case S32() : return lift_flat_signed(vi, 32 , 32 )
11551177 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' ))
11581180 case Char() : return convert_i32_to_char(cx, vi.next(' i32' ))
11591181 case String() : return lift_flat_string(cx, vi)
11601182 case List(t) : return lift_flat_list(cx, vi, t)
@@ -1277,8 +1299,8 @@ def lower_flat(cx, v, t):
12771299 case S16() : return lower_flat_signed(v, 32 )
12781300 case S32() : return lower_flat_signed(v, 32 )
12791301 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))]
12821304 case Char() : return [Value(' i32' , char_to_i32(v))]
12831305 case String() : return lower_flat_string(cx, v)
12841306 case List(t) : return lower_flat_list(cx, v, t)
@@ -1656,6 +1678,7 @@ component instance defining a resource can access its representation.
16561678[ Multi-value ] : https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md
16571679[ Exceptions ] : https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md
16581680[ WASI ] : https://github.com/webassembly/wasi
1681+ [ Deterministic Profile ] : https://github.com/WebAssembly/profiles/blob/main/proposals/profiles/Overview.md
16591682
16601683[ Alignment ] : https://en.wikipedia.org/wiki/Data_structure_alignment
16611684[ UTF-8 ] : https://en.wikipedia.org/wiki/UTF-8
0 commit comments