Skip to content

[wasm] Add Vector128 and PackedSimd support to the jiterpreter; add PackedSimd to the interpreter - #82773

Merged
kg merged 4 commits into
dotnet:mainfrom
kg:wasm-jiterpreter-simd
May 7, 2023
Merged

[wasm] Add Vector128 and PackedSimd support to the jiterpreter; add PackedSimd to the interpreter#82773
kg merged 4 commits into
dotnet:mainfrom
kg:wasm-jiterpreter-simd

Conversation

@kg

@kgkg commented Feb 28, 2023

Copy link
Copy Markdown
Contributor

This updates the jiterpreter to implement simd intrinsics that were added to the interp, which removes the need for most traces using those opcodes to bail out. It operates in one of two modes:

  1. Semi-native SIMD using WASM v128 opcodes. If code generation fails while this is active, it will automatically be switched off
  2. Emulated SIMD using the same C functions that the interp uses to implement the opcodes, for equivalent behavior. These are called directly as wasm imports instead of via an indirect call + table lookup like in the interp.

Not all of the SIMD opcodes are implemented directly, so even with v128 opcodes turned on some of them will be dispatched to the interpreter's C. I stuck to ones I could be relatively confident I was implementing correctly, since the wasm spec's documentation for its vector opcodes is written in cipher.

Along with this I added interp opcodes for most of the PackedSimd API, with C interp intrinsics auto-generated for each one by wrapping clang's matching intrinsics.

This PR also updates genmintops to generate SIMD tables, and inlines trace entry logic because we saw a performance regression when it was outlined.

This PR also improves and unifies the imported function logic in jiterpreter-support so it's easier to work with.

@kgkg added NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) arch-wasm WebAssembly architecture area-Codegen-Jiterpreter-mono labels Feb 28, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to 'arch-wasm': @lewing
See info in area-owners.md if you want to be subscribed.

Issue Details

This updates the jiterpreter to implement some of the new SIMD opcodes that were added to the interp, which removes the need for most traces using those opcodes to bail out. It operates in one of two modes:

  1. Semi-native SIMD using WASM v128 opcodes. If code generation fails while this is active, it will automatically be switched off
  2. Emulated SIMD using the same C functions that the interp uses to implement the opcodes, for equivalent behavior.

I probably need to add a dedicated feature detection path that compiles a test wasm module containing v128 opcodes to sense the availability of SIMD, but the automated fallback may be good enough.

Right now the C functions are invoked using call_indirect, so they're virtual calls. They could be static if I overhaul jiterpreter-support to allow defining new imports during code generation. On the other hand, the interpreter also does a virtual call, so this is at least not any worse.

Not all of the SIMD opcodes are implemented directly, so even with v128 opcodes turned on some of them will be dispatched to the interpreter's C. I stuck to ones I could be relatively confident I was implementing correctly, since the wasm spec's documentation for its vector opcodes is written in cipher.

This PR also updates genmintops to generate SIMD tables.

There is some stray merge damage left in the PR right now I need to figure out how to resolve, and I need to decide whether the v128 opcodes should be enabled by default when merging this. I'm inclined to leave them switched off, so the baseline is just calling the interp C helpers and we can see if that's an improvement by itself.

Author:kg
Assignees:-
Labels:

NO-MERGE, arch-wasm, area-Codegen-Jiterpreter-mono

Milestone:-

@kg

kg commented Mar 2, 2023

Copy link
Copy Markdown
ContributorAuthor

browser-bench timings with this rebased on main:

measurementjiterp (v128)jiterp (no v128)no interp simd
Json, non-ASCII text serialize2.9236ms3.0078ms2.9899ms
Json, non-ASCII text deserialize5.5887ms5.7569ms5.5366ms
Json, small serialize0.1248ms0.1266ms0.1287ms
Json, small deserialize0.2142ms0.2190ms0.2100ms
Json, large serialize33.3725ms34.3400ms34.7770ms
Json, large deserialize56.4239ms58.3258ms55.8387ms
Span, Reverse bytes0.0210ms0.0736ms0.0892ms
Span, Reverse chars0.1310ms0.1310ms0.0914ms
Span, IndexOf bytes0.7161us0.8906us0.8870us
Span, IndexOf chars0.1101ms0.1329ms0.0840ms
Span, SequenceEqual bytes0.0325ms0.0248ms0.0889ms
Span, SequenceEqual chars0.0647ms0.0496ms0.1781ms
String, Normalize1.4029ms1.3966ms1.4207ms
String, Normalize ASCII0.1752ms0.1732ms0.1752ms
Vector, Create Vector1280.0942us0.0928us0.0902us
Vector, Add 2 Vector128's0.1076us0.1062us0.1133us
Vector, Multiply 2 Vector128's0.1076us0.1065us0.1133us
Vector, Dot product int0.1004us0.0998us0.1069us
Vector, Dot product ulong0.0891us0.0878us0.0891us
Vector, Dot product float0.1117us0.1095us0.1109us
Vector, Dot product double0.0916us0.0909us0.0916us
Vector, Sum sbyte0.1411us0.1407us0.1417us
Vector, Sum short0.1100us0.1109us0.1111us
Vector, Sum uint0.0907us0.0898us0.0912us
Vector, Sum double0.0898us0.0893us0.0900us
Vector, Min float0.1532us0.1549us0.1592us
Vector, Max float0.1673us0.1636us0.1646us
Vector, Min double0.1197us0.1180us0.1197us
Vector, Max double0.1209us0.1194us0.1237us
Vector, Normalize float0.2620us0.2593us0.4157us

My opinion is pretty mixed based on this. It looks like there are some workloads in our bench suite here that vectorize, and the performance improves (especially if we enable native use of wasm v128 opcodes). But a lot of stuff seems to hold steady or even be worse - presumably cases where the interpreter intrinsics support isn't broad enough, so saying 'v128 is hardware accelerated'! hurts us.

Not sure what the right approach to take is with this. Maybe if we had wider support for Vector128 at the interpreter intrinsic level we'd see more gains?

@BrzVlad what do you think? I think for now it makes sense to leave INTERP_ENABLE_SIMD guarded with #if !HOST_BROWSER && __GNUC__, and to land the jiterpreter support so that we have a starting point so we can consider enabling it later. The downside is that it could potentially bitrot in this state since it would never run.

@BrzVlad

BrzVlad commented Mar 3, 2023

Copy link
Copy Markdown
Member

@kg I don't think the way you test is correct, since the numbers make no sense. It is like you are testing the same thing. What does no interp simd mean ? Are you enabling simd in csproj ? (WasmEnableSIMD, I think the linker trims out vectorized bcl code if not enabled)

Also I don't think we should bother with perf investigations of this until we get some reference numbers from the microbenchmark suite on desktop interp

@kg

kg commented Mar 3, 2023

Copy link
Copy Markdown
ContributorAuthor

@kg I don't think the way you test is correct, since the numbers make no sense. It is like you are testing the same thing. What does no interp simd mean ? Are you enabling simd in csproj ? (WasmEnableSIMD, I think the linker trims out vectorized bcl code if not enabled)

I enable interp SIMD for the first two columns by changing
#if !HOST_BROWSER && __GNUC__
to
#if __GNUC__
which does cause interpreter simd opcodes to appear in various places as expected.

I'll retest by editing the browser-bench csproj to add that property; because SIMD had been enabled on main I thought it would be sufficient to set the constant in the interp. If the code is being trimmed out that would explain not seeing much of a difference - the opcodes I'm seeing must be for stuff that escaped trimming.

@kg

kg commented Mar 3, 2023

Copy link
Copy Markdown
ContributorAuthor

I rebuilt the runtime with WasmEnableSIMD set to true, then rebuilt browser-bench with it set as well. Then I did a second rebuild of browser-bench with it set to false. The results from a run are effectively the same, I don't think that msbuild property is working in non-AOT. I have trimming disabled too.

@lewing

Copy link
Copy Markdown
Member

@kg I merged main can you double check the legacy_cwraps bit

@kg

kg commented Mar 5, 2023

Copy link
Copy Markdown
ContributorAuthor

@kg I merged main can you double check the legacy_cwraps bit

Looks right

@kg

kg commented Mar 8, 2023

Copy link
Copy Markdown
ContributorAuthor

I still have no explanation for why this performs the way it does.

measurementtime (this PR)time (this+AOT)time (main)
Json, non-ASCII text serialize3.8978ms0.2363ms4.1681ms
Json, non-ASCII text deserialize6.2145ms0.4287ms6.3337ms
Json, small serialize0.1357ms0.0126ms0.1324ms
Json, small deserialize0.2251ms0.0328ms0.2198ms
Json, large serialize36.5108ms3.5608ms35.9710ms
Json, large deserialize59.1910ms9.3782ms58.2045ms
Span, Reverse bytes0.0219ms0.0021ms0.1244ms
Span, Reverse chars0.1289ms0.0053ms0.4433ms
Span, IndexOf bytes0.7610us0.1028us0.0010ms
Span, IndexOf chars0.1153ms0.0028ms0.0864ms
Span, SequenceEqual bytes0.0400ms0.0022ms0.0295ms
Span, SequenceEqual chars0.0797ms0.0042ms0.0590ms
String, Normalize1.4766ms0.7871ms1.4346ms
String, Normalize ASCII0.1815ms0.0061ms0.1811ms
Vector, Create Vector1280.0975us0.0449us0.0930us
Vector, Add 2 Vector128's0.1103us0.0479us0.1089us
Vector, Multiply 2 Vector128's0.1117us0.0453us0.1100us
Vector, Dot product int0.1060us0.0458us0.1036us
Vector, Dot product ulong0.0933us0.0453us0.0910us
Vector, Dot product float0.1162us0.0449us0.1152us
Vector, Dot product double0.0971us0.0447us0.0940us
Vector, Sum sbyte0.1494us0.0449us0.1449us
Vector, Sum short0.1165us0.0447us0.1150us
Vector, Sum uint0.0961us0.0446us0.0945us
Vector, Sum double0.0947us0.0447us0.0922us
Vector, Min float0.2321us0.0451us0.2200us
Vector, Max float0.2233us0.0447us0.1786us
Vector, Min double0.1850us0.0451us0.1859us
Vector, Max double0.1447us0.0450us0.1349us
Vector, Normalize float0.2761us0.0493us0.4228us

A handful of things like Span.Reverse and Vector.Normalize are obviously improved, some are worse like Span.IndexOf, and some of these timing samples just make no sense and make me wonder if browser-bench is broken on my hardware.

EDIT: Ah, the ones that look way wrong are a msec/usec disconnect. Wish it didn't do that.

@BrzVlad

Copy link
Copy Markdown
Member

I think it is best to investigate why Vector, Add 2 Vector128's is not improved since it seems the simplest and most outrageous case. You could add verbose logging to see exactly what C#/jiterp trace code is being executed and if it is correct. If my understanding is correct you are also not falling back to interp C vector methods, but are emitting directly wasm simd opcode ? If that is the case, not having a perf improvement is absurd.

@kg

kg commented Apr 15, 2023

Copy link
Copy Markdown
ContributorAuthor

The custom conditional select and bitwise equality/inequality opcodes in the jiterpreter side are broken somehow, so I've disabled them. I'm not sure what's going on there since tests pass, but unless they're disabled the beginning of xharness output XML ends up going missing. This means that the interpreter C helpers get used instead, which isn't the end of the world.

@kg
kgforce-pushed the wasm-jiterpreter-simd branch 2 times, most recently from 8d8a71d to f97e897CompareApril 15, 2023 06:30
@kg

kg commented Apr 15, 2023

Copy link
Copy Markdown
ContributorAuthor

This is now blocked on real failures in the vectorized IndexOfAny code that didn't run before due to lack of PackedSimd. I am unable to come up with a satisfactory explanation for why it's not working. (Incidentally, the set of tests that fail locally is different from the set that fail on CI, and the values are different, which makes me think some sort of memory corruption or uninitialized memory.)


[07:08:44] info: Starting: System.Memory.Tests.dll
[07:10:23] fail: [FAIL] System.SpanTests.ReadOnlySpanTests.DefaultFilledIndexOfMany_Char
[07:10:23] info: Assert.Equal() Failure
[07:10:23] info: Expected: 0
[07:10:23] info: Actual: -1
[07:10:23] info: at System.SpanTests.ReadOnlySpanTests.DefaultFilledIndexOfMany_Char()
[07:10:23] info: at System.Reflection.MethodInvoker.InterpretedInvoke(Object obj, IntPtr* args)
[07:10:23] info: at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
[07:11:13] fail: [FAIL] System.SpanTests.SpanTests.IndexOfAny_LastIndexOfAny_AlgComplexity_Chars
[07:11:13] info: Assert.Equal() Failure
[07:11:13] info: Expected: 7168
[07:11:13] info: Actual: -1
[07:11:13] info: at System.SpanTests.SpanTests.RunLastIndexOfAnyAlgComplexityTest[Char](Char[] needle)
[07:11:13] info: at System.SpanTests.SpanTests.RunIndexOfAnyLastIndexOfAnyAlgComplexityTest[Char]()
[07:11:13] info: at System.SpanTests.SpanTests.IndexOfAny_LastIndexOfAny_AlgComplexity_Chars()
[07:11:13] info: at System.Reflection.MethodInvoker.InterpretedInvoke(Object obj, IntPtr* args)
[07:11:13] info: at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
[07:11:14] info: Finished: System.Memory.Tests.dll

While I'm mildly suspicious of the vectorized code since it was previously being guarded, I don't see anything wrong with it. It's possible we're now exercising some part of the interp v128 code that we weren't before, but it doesn't seem to heavily use those APIs. I double and triple checked that I am using the correct clang intrinsic for the relevant opcode, and even experimented with using different intrinsics anyway in case I misread something. It also fails even if jiterpreter is disabled, so that isn't a factor.

@kg

kg commented Apr 15, 2023

Copy link
Copy Markdown
ContributorAuthor

Disabling the interp v128 intrinsics doesn't seem to fix it either (I verified that doing this results in mostly-scalar code with wasm packedsimd opcodes scattered inside). So that seems to indicate that somehow the PackedSimd operation(s) are the problem.

@kg

kg commented Apr 17, 2023

Copy link
Copy Markdown
ContributorAuthor

Radek pointed out that this failure could be #84775 . It could be that there is something wrong with the way interp aligns v128 arguments or return values, or my copy-paste produced some incorrect alignment.

kg added 2 commits May 5, 2023 17:13
Fix bugs in the jiterpreter-support import management implementation
Enable interpreter SIMD for WASM
Minor cleanups
Fix merge
Checkpoint PackedSimd
Implement a few PackedSimd methods in the interp
Add packedsimd ops to jiterpreter
Move the interp simd opcode -> wasm opcode mapping into the C tables
Add intrinsic ids for most of the remaining packedsimd methods
Map most of the PackedSimd methods to intrinsics
Update genmintops
Fix merge damage
Fix build
Add more wasm opcodes
Add more wasm opcodes
Add bitmask intrinsics
Add missing opcodes to transform-simd
Use HOST_BROWSER instead of HOST_WASM to fix wasi build
Implement the pack-n-elements vector instructions
Disable bitselect because it's broken somehow
Simplify vector packing
Add browser-bench measurements for packing vectors
Disable more opcodes
Disable more opcodes
Fix PackedSimd feature detection on non-wasm targets
Maybe fix linux interp assertion
Don't fail transform for unsupported PackedSimd methods on non-browser targets
Fix i64 popcnt
Add basic R4 v128 intrinsics (add/sub/div/mul)
Re-enable more jiterpreter simd
@kg
kgforce-pushed the wasm-jiterpreter-simd branch from 8e00956 to 28bbc99CompareMay 6, 2023 00:36
@kgkg removed the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label May 6, 2023
@kg

kg commented May 6, 2023

Copy link
Copy Markdown
ContributorAuthor

I added some basic R4 vector ops to the interp and changed some asserts so that it's possible to run the SIMD version of pavel's raytracer. It completes in ~16k msec with these changes compared to ~31k msec before, nearly a 50% reduction.

The crash issues and test failures I was hitting before seem to have at least partially been caused by an arglist alignment issue that is now fixed on main, so hopefully tests will pass now...

@kg

kg commented May 6, 2023

Copy link
Copy Markdown
ContributorAuthor

The remaining issue on this PR seems to be that once we report PackedSimd support, writing XML for the testResults.xml file ends up dropping a few characters at the start or end of the stream. It's not clear to me why this would happen.

@vargaz

Copy link
Copy Markdown
Contributor

Can this be turned off by default so the PR can be merged ?

@kg

kg commented May 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Can this be turned off by default so the PR can be merged ?

I could probably comment out the PackedSimd support, but it makes this PR much less useful since the BCL uses it now. I'm fine with doing that assuming none of the CI tests fail and the only remaining problem is the xml, since it means we're not merging tremendously broken code.

@kg

kg commented May 6, 2023

Copy link
Copy Markdown
ContributorAuthor

All the failures I saw were the test results xml size problem, so I added runtime options to govern interp v128 and packedsimd support and set them both to FALSE for browser.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecture

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@kg@BrzVlad@lewing@vargaz