Skip to content
Snippets Groups Projects

tests: Test how constant folding works on SM1.

Merged Giovanni Mascellani requested to merge giomasce/vkd3d:yellowstone into master
4 unresolved threads

While reviewing !671 (merged) I wrote a few tests that are probably not too fundamental, but since now they exist it makes sense to keep them. !671 (merged) doesn't fix nor break any of them, anyway.

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
14
15 % The uint value is never casted to int on SM1-3, just casted to float and treated as such
16 [pixel shader]
17 float4 main() : SV_TARGET
18 {
19 int x = 3000000000u;
20 return float4(x, 0.0, 0.0, 0.0);
21 }
22
23 [test]
24 draw quad
25 if(sm<4) todo probe all rgba (3.0e+009, 0.0, 0.0, 0.0)
26 if(sm>=4) probe all rgba (-1.29496730e+009, 0.0, 0.0, 0.0)
27
28 % On SM1-5 (FXC) the literal is considered signed independently of its value and casted to uint;
29 % on SM1-3 compilation fails because the uint it would be negative
  • Francisco Casas
    Francisco Casas @fcasas started a thread on an outdated change in commit d700f851
  • 37 [test]
    38 draw quad
    39 if(sm>=4) probe all rgba (3.0e+009, 0.0, 0.0, 0.0)
    40
    41 [pixel shader]
    42 float4 main() : SV_TARGET
    43 {
    44 int x = 3000000000;
    45 return float4(x, 0.0, 0.0, 0.0);
    46 }
    47
    48 [test]
    49 draw quad
    50 probe all rgba (-1.29496730e+009, 0.0, 0.0, 0.0)
    51
    52 % These two make sense
    • Interesting experiments!

      This is making me having two separate hypothesis for SM1:

      A) Constant propagation skips casts from int to uint and vice-versa, which explains the result of the second test.

      B) Native must be doing something similar to my pass from !671 (merged) 2/3 but before constant propagation. This means that constant folding casts the constants to float before performing the operation, and then casts the result back to the expected type.

      So in this test:

      [pixel shader fail(sm<4) todo(sm<4)]
      float4 main() : SV_TARGET
      {
          uint x = 3000000000 + 3000000000;
          return float4(x, 0.0, 0.0, 0.0);
      }
      
      [test]
      draw quad
      todo probe all rgba (2.14748365e+009, 0.0, 0.0, 0.0)
      • Each 3000000000 is stored as int, so each overflows to -1294967296.
      • In the presence of arithmetic operators, constant folding turns them to float.
      • The + is performed with floating-point arithmetic resulting in -1294967296.0f - 1294967296.0f = -2589934592.0f.
      • The result is turned back to int which in C results in 2147483648 (INT_MAX) because it cannot be represented as int (yep, it is UB).

      I am approving the merge request, check my minor comment nitpicks if you got the chance before it gets merged.

    • Please disregard my hypothesis B. I tried that and it didn't work. In fact you even mentioned:

      % Constant folding is done with uint semantics before converting to float;
      % so constant folding seems to happen before converting integer operations to floats

      but maybe there is a lot more going on, for instance, notice this shader: https://shader-playground.timjones.io/df675e96acb6d16bb77dc0ebdf69dfb2

      Constant folding is even preempting catastrophic cancellation.

    • Okay, I think that what happens is that float constants use double arithmetic! And this is also true for SM4.

      This test can be added, albeit I am not sure if in sm1-const-folding.shader_test.

      % Float constants use double arithmetic on all shader models.
      [pixel shader]
      float4 main() : SV_TARGET
      {
          float x = (200000000000.0f + 1.0f) - 200000000000.0f ;
          return x;
      }
      
      [test]
      draw quad
      probe all rgba (1.0, 1.0, 1.0, 1.0)
    • Please register or sign in to reply
  • Francisco Casas approved this merge request

    approved this merge request

    • I did some additional testing, and it's more complicated than that.

      I think I alluded to this elsewhere, but the native compiler has a concept of an "ambiguous" integer type, which is neither int nor uint. You can tell this by doing something like

      float func(int x) { return 1; }
      float func(uint x) { return 2; }
      
      float4 main() : SV_TARGET
      {
          return func(1);
      }

      Ambiguous ints are automatically promoted when in an expression with other types, and can of course be implicitly cast, but an expression comprised only of ambiguous ints is still ambiguous.

      Different integer constants have different types:

      uint: 1u, 0x1, 01, 0x0, 4294967295u, 4294967296u, 4294967296lu int: 3000000000, -3000000000, 2147483648, -2147483648 ambiguous: 1, 0, 00, 000, -0, -1, 2147483647, -2147483647, 4294967296l, 1 + 2, 1 / 2 empty: 4294967296

      Note "empty": that last token lexes as if it were a space. Fun!

      uint adds with overflow, as you'd expect. Int and ambiguous int behaves like an integer type in bounds, but an expression that would overflow (in either direction) yields INT_MIN. Note that, contrary to Francisco's hypothesis, it does not act like a float type: expressions like 2147483645 + 1 do have accuracy to the nearest integer. Also, "1 / 2" equals zero even if converted to float.

      Of course the rules are different for sm6; I haven't checked those yet.

    • What if the ambiguous int type is actually an ambiguous number type -- meaning that it also includes floats and doubles -- and it is represented internally as double? That would explain why there is no loss of accuracy for that value.

      Also, "1 / 2" equals zero even if converted to float.

      Maybe there is an exception and / for ambiguous numbers is always integer division, to be consistent with C.

    • What if the ambiguous int type is actually an ambiguous number type -- meaning that it also includes floats and doubles -- and it is represented internally as double? That would explain why there is no loss of accuracy for that value.

      That seems unlikely if overflowing bounds always yields INT_MIN. E.g. "return (2147483647 + 1) - 1;" yields INT_MIN.

      Note also that floats have their own "ambiguous" type. 1.0f is float; 1.0h is half; 1.0 is ambiguous.

    • Similarly (3 / 2) * 3 yields 3, not 4. I think that pretty clearly proves it's converted to integer, at the very least after every step.

    • Zeb, thanks for looking deeper into this. Personally I do not think there is anything urgent for us to do here and I do not plan to spend much more time on this issue, so I would like to know whether you want to upstream yourself some tests which better document your interpretation; if not I'll keep working on this MR (because I think the tests here are valuable, even if likely incomplete), but without necessarily investigating every possible edge case.

    • Sorry for the double comment, but the offer to take ownership of this issue is open for Francisco too, of course, if you have bandwidth/curiosity for that.

    • Testing and implementing weird behaviour like this is my idea of a good time, so yeah, I wouldn't mind taking ownership.

    • Please register or sign in to reply
  • Henri Verbeet approved this merge request

    approved this merge request

  • added 1 commit

    • 4598a871 - tests: Test how constant folding works on SM1.

    Compare with previous version

  • added 5 commits

    Compare with previous version

  • Alexandre Julliard approved this merge request

    approved this merge request

  • Please register or sign in to reply
    Loading