@@ -442,8 +442,8 @@ def load(cx, ptr, t):
442442 case S16() : return load_int(cx, ptr, 2 , signed = True )
443443 case S32() : return load_int(cx, ptr, 4 , signed = True )
444444 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 ))
447447 case Char() : return convert_i32_to_char(cx, load_int(cx, ptr, 4 ))
448448 case String() : return load_string(cx, ptr)
449449 case List(t) : return load_list(cx, ptr, t)
@@ -469,51 +469,42 @@ def convert_int_to_bool(i):
469469 return bool (i)
470470```
471471
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.
481480``` python
482481DETERMINISTIC_PROFILE = False # or True
483- THE_HOST_WANTS_TO = True # or False
484482CANONICAL_FLOAT32_NAN = 0x 7fc00000
485483CANONICAL_FLOAT64_NAN = 0x 7ff8000000000000
486484
487- def maybe_scramble_nan32 (f ):
485+ def canonicalize_nan32 (f ):
488486 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 )
493488 assert (math.isnan(f))
494489 return f
495490
496- def maybe_scramble_nan64 (f ):
491+ def canonicalize_nan64 (f ):
497492 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 )
502494 assert (math.isnan(f))
503495 return f
504496
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 ):
506504 return struct.unpack(' !f' , struct.pack(' !I' , i))[0 ] # f32.reinterpret_i32
507505
508- def reinterpret_i64_as_float (i ):
506+ def core_f64_reinterpret_i64 (i ):
509507 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
517508```
518509
519510An ` i32 ` is converted to a ` char ` (a [ Unicode Scalar Value] ) by dynamically
@@ -703,8 +694,8 @@ def store(cx, v, t, ptr):
703694 case S16() : store_int(cx, v, ptr, 2 , signed = True )
704695 case S32() : store_int(cx, v, ptr, 4 , signed = True )
705696 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 )
708699 case Char() : store_int(cx, char_to_i32(v), ptr, 4 )
709700 case String() : store_string(cx, v, ptr)
710701 case List(t) : store_list(cx, v, ptr, t)
@@ -724,13 +715,55 @@ def store_int(cx, v, ptr, nbytes, signed = False):
724715 cx.opts.memory[ptr : ptr+ nbytes] = int .to_bytes(v, nbytes, ' little' , signed = signed)
725716```
726717
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.
729731``` 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 ):
731764 return struct.unpack(' !I' , struct.pack(' !f' , f))[0 ] # i32.reinterpret_f32
732765
733- def reinterpret_float_as_i64 (f ):
766+ def core_i64_reinterpret_f64 (f ):
734767 return struct.unpack(' !Q' , struct.pack(' !d' , f))[0 ] # i64.reinterpret_f64
735768```
736769
@@ -1181,8 +1214,8 @@ def lift_flat(cx, vi, t):
11811214 case S16() : return lift_flat_signed(vi, 32 , 16 )
11821215 case S32() : return lift_flat_signed(vi, 32 , 32 )
11831216 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' ))
11861219 case Char() : return convert_i32_to_char(cx, vi.next(' i32' ))
11871220 case String() : return lift_flat_string(cx, vi)
11881221 case List(t) : return lift_flat_list(cx, vi, t)
@@ -1256,10 +1289,10 @@ def lift_flat_variant(cx, vi, cases):
12561289 have = flat_types.pop(0 )
12571290 x = vi.next(have)
12581291 match (have, want):
1259- case (' i32' , ' f32' ) : return reinterpret_i32_as_float (x)
1292+ case (' i32' , ' f32' ) : return decode_i32_as_float (x)
12601293 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)
12631296 case _ : return x
12641297 c = cases[case_index]
12651298 if c.t is None :
@@ -1367,10 +1400,10 @@ def lower_flat_variant(cx, v, cases):
13671400 for i,have in enumerate (payload):
13681401 want = flat_types.pop(0 )
13691402 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))
13711404 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))
13741407 case _ : pass
13751408 for want in flat_types:
13761409 payload.append(Value(want, 0 ))
0 commit comments