Skip to content

Float32x4 value discrepancy between ReleaseX64 AOT and other modes #39551

Open

Description

@fizaaluthra

fuzz.dart.txt

Isolate (/b/s/w/it4yAfDg/dart_fuzzSXRIPK) FP NO-FFI FLAT : AOT-O3-ReleaseX64 - KBC-CMP-SLOWPATH-DebugSIMARM64: !DIVERGENCE! 1.72:1809754098 (output)

-- BEGIN REPRODUCE  --

dartfuzz.dart --fp --no-ffi --flat --seed 1809754098 /b/s/w/it4yAfDg/dart_fuzzSXRIPK/fuzz.dart

-- RUN 1 --

DART_CONFIGURATION='ReleaseX64' DART_VM_FLAGS='--enable-asserts' /b/s/w/ir/pkg/vm/tool/precompiler2 --optimization_level=3 /b/s/w/it4yAfDg/dart_fuzzSXRIPK/fuzz.dart /b/s/w/it4yAfDg/dart_fuzzSXRIPK/snapshot
/b/s/w/ir/pkg/vm/tool/dart_precompiled_runtime2 /b/s/w/it4yAfDg/dart_fuzzSXRIPK/snapshot

-- RUN 2 --

/b/s/w/ir/pkg/vm/tool/gen_kernel --gen-bytecode --platform=/b/s/w/ir/out/DebugSIMARM64/vm_platform_strong.dill -o /b/s/w/it4yAfDg/dart_fuzzSXRIPK/out.dill /b/s/w/it4yAfDg/dart_fuzzSXRIPK/fuzz.dart
/b/s/w/ir/out/DebugSIMARM64/dart --use-bytecode-compiler --use-slow-path --old_gen_heap_size=128 /b/s/w/it4yAfDg/dart_fuzzSXRIPK/out.dill

-- END REPRODUCE  --

ReleaseX64 AOT disagrees with other execution modes.

Reduced the failure to the following code snippet:

import 'dart:typed_data';
Float32x4 var3;
main () {
  var3 = (-(((Float32x4.splat(0.1502790812204874)).reciprocal())));
  print(var3);
}

Difference in output:

1c1
< [-6.653320, -6.653320, -6.653320, -6.653320]
---
> [-6.654286, -6.654286, -6.654286, -6.654286]

cc @bkonyi

Activity

  1. added
    area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.
    dartfuzzFound with Dart fuzzing (DartFuzz, libFuzzer, etc.)
    on Nov 27, 2019
  2. fizaaluthra commented on Nov 27, 2019

    @fizaaluthra
    Author

    The divergence seems to be caused by .reciprocal().
    var3 = Float32x4.splat(0.3) works fine but var3 = Float32x4.splat(0.3).reciprocal() causes a divergence.

  3. fizaaluthra commented on Nov 27, 2019

    @fizaaluthra
    Author

    Another one:

    Isolate (/b/s/w/it9kskCt/dart_fuzzERZTVU) FP NO-FFI FLAT : AOT-DebugX64 - JIT-SLOWPATH-DET-DebugSIMARM64: !DIVERGENCE! 1.72:4206347390 (output)
    
    -- BEGIN REPRODUCE  --
    
    dartfuzz.dart --fp --no-ffi --flat --seed 4206347390 /b/s/w/it9kskCt/dart_fuzzERZTVU/fuzz.dart
    
    -- RUN 1 --
    
    DART_CONFIGURATION='DebugX64' DART_VM_FLAGS='--enable-asserts' /b/s/w/ir/pkg/vm/tool/precompiler2 /b/s/w/it9kskCt/dart_fuzzERZTVU/fuzz.dart /b/s/w/it9kskCt/dart_fuzzERZTVU/snapshot
    /b/s/w/ir/pkg/vm/tool/dart_precompiled_runtime2 /b/s/w/it9kskCt/dart_fuzzERZTVU/snapshot
    
    -- RUN 2 --
    
    /b/s/w/ir/out/DebugSIMARM64/dart --use-slow-path --deterministic --old_gen_heap_size=128 /b/s/w/it9kskCt/dart_fuzzERZTVU/fuzz.dart
    
    -- END REPRODUCE  --
    

    Reduced to the following code snippet:

    import 'dart:typed_data';
    Float32x4 var3;
    main () {
      var3 = Float32x4.splat(0.3).reciprocalSqrt();
      print(var3);
    }
    

    Difference in output:

    1c1
    < [1.825928, 1.825928, 1.825928, 1.825928]
    ---
    > [1.825742, 1.825742, 1.825742, 1.825742]
    
  4. self-assigned this
    on Dec 16, 2019
  5. sstrickl commented on Dec 16, 2019

    @sstrickl
    Contributor

    While looking into this, I found an intra-architecture difference as well. Take this code, which adds a print between the splat and reciprocal calls:

    import 'dart:typed_data';
    Float32x4 var3;
    main () {
      var3 = Float32x4.splat(0.1502790812204874);
      print(var3);
      var3 = -var3.reciprocal();
      print(var3);
    }

    and the code from the bug report, with extra parentheses elided (so no semantic changes):

    import 'dart:typed_data';
    Float32x4 var3;
    main () {
      var3 = -Float32x4.splat(0.1502790812204874).reciprocal();
      print(var3);
    }

    The diff when running on ReleaseX64 in AOT mode:

    1,2c1
    < [0.150279, 0.150279, 0.150279, 0.150279]
    < [-6.654286, -6.654286, -6.654286, -6.654286]
    ---
    > [-6.653320, -6.653320, -6.653320, -6.653320]
    

    This difference does not occur in JIT mode, so am now looking at the difference in AOT code generation on x64 for these two snippets.

  6. sstrickl commented on Dec 16, 2019

    @sstrickl
    Contributor

    After a quick glance, you can also get the same result as the second (faulty) code snippet with this one, which adds the intermediate print via a local variable instead of via the static field:

    import 'dart:typed_data';
    Float32x4 var3;
    main () {
      final tmp = Float32x4.splat(0.1502790812204874);
      print(tmp);
      var3 = -tmp.reciprocal();
      print(var3);
    }
  7. sstrickl commented on Dec 16, 2019

    @sstrickl
    Contributor

    Given that, the difference is whether the reciprocal is recognized and replaced with a StaticCall to a recognized function, or whether it is kept as a PolymorphicInstanceCall. Looking at the recognized version of Float32x4Reciprocal now.

  8. sstrickl commented on Dec 16, 2019

    @sstrickl
    Contributor

    So the recognized version of reciprocal turns into a rcpps call in assembly, whereas the unrecognized method is basically NativeCall(Float32x4_reciprocal), so a call to the C implementation in runtime/lib/simd128.cc.

  9. mkustermann commented on Dec 16, 2019

    @mkustermann
    Member
    #include <immintrin.h>
    #include <stdio.h>
    
    float doubleInvertUsingSimd(float a) {
      float input[4] = { a, a, a, a };
      __m128 result = _mm_rcp_ps(_mm_rcp_ps(_mm_load_ps(input)));
    
      float output[4];
      _mm_store_ps(output, result);
    
      return output[0];
    }
    
    float doubleInvertUsingC(float arg) {
      return 1.0f / (1.0f / arg);
    }
    
    int main(int a, char** b) {
      float arg = 0.1502790812204874;
      printf("%.15f\n", doubleInvertUsingSimd(arg));
      printf("%.15f\n", doubleInvertUsingC(arg));
      return 0;
    }

    produces

    0.150299072265625
    0.150279074907303
    

    with only one inversion

    6.653320312500000
    6.654286384582520
    

    So the SIMD instructions simply perform the reciprocal with lower precision

  10. assigned and unassigned on Dec 16, 2019
  11. sstrickl commented on Dec 16, 2019

    @sstrickl
    Contributor

    Martin mentions #37335, which is a difference when running on 32 vs. 64 bit, and was the reason for adding the --no-fp flag (c2af59f) when comparing 32-bit vs. 64-bit modes.

    In this case, this is the same word-size architecture, it's just a case where we use SIMD instructions vs. falling back onto a C implementation of the same, and the SIMD instructions are not semantically equivalent. That is, here we may have a permissible difference due to floating point size, and the problem is that we're requiring strict (output) equality for floating point values instead of approximate equality.

    One possibility is to try running with both --enable-simd-inline=true (the default) and --enable-simd-inline=false when FP instructions are enabled and make sure that the false version precisely matches, and allow a reasonable amount of difference for any floating point outputs on the SIMD version (instead of strict textual equality). Going to assign to Ben to consider this and whether this or a different option is the right path going forward.

  12. bkonyi commented on Dec 26, 2019

    @bkonyi
    Contributor

    I'm not surprised that we're seeing these differences if we're running through different code paths. Ideally we'll want to allow for some delta when we compare floats in the final results but we'll need to figure out how to best do that since we're doing a naive diff at the moment. Thanks for investigating @sstrickl
    and @mkustermann!

  13. removed their assignment
    on Jan 27, 2022
  14. modulovalue commented on Aug 22, 2026

    @modulovalue
    Contributor

    The Wasm SIMD API surface is meant to be deterministic across platforms. Our reciprocal and reciprocalSqrt APIs are not. They are more efficient than the naive implementations, but at the cost of determinism.

    For non-deterministic SIMD operations that trade determinism for performance we have the Relaxed SIMD API surface. However, they neither include a relaxed reciprocal nor a reciprocalSqrt. The discussion for that can be found here: WebAssembly/relaxed-simd#4

    I think we have the following options here:

    1. We do nothing and accept non-determinism in our SIMD APIs.
      => leads to inconsistent and confusing behavior as captured by this issue
    2. We make reciprocal and reciprocalSqrt deterministic.
      => it looks like we couldn't be more efficient than what a user can write so having a dedicated API for it wouldn't carry its weight.
    3. We deprecate and remove those APIs.

    I'm in favor of deprecating and removing those APIs and moving the discussion of how to expose those more efficient non-deterministic intrinsics/operations into the future if/when we eventually discuss how to expose the relaxed SIMD API surface. @lrhn @mkustermann thoughts?

  15. modulovalue commented on Sep 9, 2026

    @modulovalue
    Contributor

    Looks like the divergence goes further than just precision. I'll look into this as part of #64248.

    reciprocalSqrt(-0.0) returns nan unoptimized and -infinity optimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.dartfuzzFound with Dart fuzzing (DartFuzz, libFuzzer, etc.)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions