{"id":21015582,"url":"https://github.com/lostbeard/blazorwasmsimddetectexample","last_synced_at":"2025-05-15T05:32:10.708Z","repository":{"id":200552378,"uuid":"705783217","full_name":"LostBeard/BlazorWASMSIMDDetectExample","owner":"LostBeard","description":"This .Net 8 Blazor WASM project demonstrates a way of detecting SIMD support and using it if available.","archived":false,"fork":false,"pushed_at":"2024-11-08T14:35:58.000Z","size":38537,"stargazers_count":14,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-12T01:36:47.842Z","etag":null,"topics":["blazor","blazor-webassembly","csharp","dotnet","simd","webassembly"],"latest_commit_sha":null,"homepage":"https://lostbeard.github.io/BlazorWASMSIMDDetectExample/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LostBeard.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["LostBeard"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"custom":null}},"created_at":"2023-10-16T17:21:06.000Z","updated_at":"2025-02-20T12:51:18.000Z","dependencies_parsed_at":"2023-10-17T07:58:50.452Z","dependency_job_id":"e4a3a71a-fd11-423b-96b1-5bc77599e033","html_url":"https://github.com/LostBeard/BlazorWASMSIMDDetectExample","commit_stats":null,"previous_names":["lostbeard/blazorwasmsimddetectexample"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LostBeard%2FBlazorWASMSIMDDetectExample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LostBeard%2FBlazorWASMSIMDDetectExample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LostBeard%2FBlazorWASMSIMDDetectExample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LostBeard%2FBlazorWASMSIMDDetectExample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LostBeard","download_url":"https://codeload.github.com/LostBeard/BlazorWASMSIMDDetectExample/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254282235,"owners_count":22045123,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blazor","blazor-webassembly","csharp","dotnet","simd","webassembly"],"created_at":"2024-11-19T10:10:28.156Z","updated_at":"2025-05-15T05:32:10.276Z","avatar_url":"https://github.com/LostBeard.png","language":"HTML","funding_links":["https://github.com/sponsors/LostBeard"],"categories":[],"sub_categories":[],"readme":"# Blazor WASM SIMD Detect Example\n\nBlazor Web App SIMD Detect Example  \n[BlazorWebAppSIMDDetectExample](https://github.com/LostBeard/BlazorWebAppSIMDDetectExample)\n\nBlazor WebAssembly Standalone SIMD Detect Example (this repo)  \n[BlazorWASMSIMDDetectExample](https://github.com/LostBeard/BlazorWASMSIMDDetectExample)\n\n[Live Demo](https://lostbeard.github.io/BlazorWASMSIMDDetectExample/)\n\nIf you have done a lot of testing with Blazor WASM you may eventually hit some compatibility issues you weren't expecting. This .Net 8 Blazor WASM project demonstrates a way of detecting SIMD support and using it if available.\n\n# SIMD\n[Single Instruction, Multiple Data](https://v8.dev/features/simd) support has been added to Blazor WASM and is now enabled by default in .Net 8, and for good reason. Enabling SIMD brings some large speed improvements in many areas of Blazor WASM. There are many articles that praise the benefits of SIMD. While the linked articles below do not specifically mention Blazor, they all talk about the benefits SIMD can bring to C#.\n\nSIMD and C# articles:    \n- [Use SIMD-accelerated numeric types](https://learn.microsoft.com/en-us/dotnet/standard/simd)\n- [.Net 9 AVX10v1 Support](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/runtime#avx10v1-support)  \n- [LINQ on steroids with SIMD](https://steven-giesel.com/blogPost/faf06188-bae9-484d-804d-a42d58d18cad)  \n- [SIMD, a parallel processing at hardware level in C#](https://dev.to/mstbardia/simd-a-parallel-processing-at-hardware-level-in-c-42p4)  \n- [LINQ Internals: Speed Optimizations](https://antao-almada.medium.com/linq-internals-speed-optimizations-1d99b53750bb)  \n- [Optimizing string.Count all the way from LINQ to hardware accelerated vectorized instructions](https://sergiopedri.medium.com/optimizing-string-count-all-the-way-from-linq-to-hardware-accelerated-vectorized-instructions-186816010ad9)  \n- [Faster Guid comparisons using Vectors (SIMD) in .NET](https://www.meziantou.net/faster-guid-comparisons-using-vectors-simd-in-dotnet.htm)\n\n\n## The problem - Inconsistent SIMD support\nSIMD WASM support is far from universal and a lack of support kills any Blazor WASM app that requires it before it starts.\n\nA lack of WASM SIMD support can be caused by:\n- old browsers. Convincing end users to upgrade for your site is not a great option.\n- old hardware. An AMD Phenom 2 X6 has 6 cores and runs at a base speed of 3.2 GHz but no browser running on it can support SIMD because the CPU does not support it. This, and a lot of hardware like it, is still in use and is quite capable.\n\nI also have an updated test phone running Android 9 \"Pie\" with the latest Firefox 118 and SIMD is not supported. Chrome on the same device supports SIMD. That same version of Firefox on a tablet running Android 11 \"Red Velvet Cake\" supports SIMD.\n\nSo it is obvious that SIMD support is a bit fractured.\n\nHere are 2 options to handle SIMD compatibility issues.\n## Option 1 - Disable SIMD support\nThis is the simplest option and only requires adding the flag ```\u003cWasmEnableSIMD\u003efalse\u003c/WasmEnableSIMD\u003e``` to your project's .csproj file inside a ```\u003cPropertyGroup\u003e```.  This is the easiest and most compatible way to get around a lack of SIMD support but you lose the ability to take advantage if it is supported. `\u003cWasmEnableExceptionHandling\u003efalse\u003c/WasmEnableExceptionHandling\u003e` is also recommended for compatibility builds.\n\nI also recommend disabling ```BlazorWebAssemblyJiterpreter``` in your compatibility build. Testing on systems that did not support SIMD with SIMD disabled builds would get an exception ```MONO_WASM: get_Cache:0 code generation failed: CompileError: at offset 161: bad type U ...``` and also the message ```MONO_WASM: Disabling jiterpreter after 2 failures```. Setting ```\u003cBlazorWebAssemblyJiterpreter\u003efalse\u003c/BlazorWebAssemblyJiterpreter\u003e``` fixes it.\n\n## Option 2 - Detect SIMD support and load a supported build (method this project demos)\n- Modify your index.html to detect SIMD support and load a compatibility build if needed. \n- Create a compatibility build with SIMD and BlazorWebAssemblyJiterpreter disabled.\n\n### HTML file and Blazor startup\nModify the Blazor startup in the main html file. Depending on the template you used it could be\nindex.html if a Blazor WebAssembly Standalone app or App.razor in the server project if a Blazor Web App.\n\nThe code below will detect SIMD support and use the appropriate build folder.  \nModify the html file like below.\n```html\n\u003c!-- autostart is set to false so we can detect SIMD support and load the appropriate build --\u003e\n\u003c!-- the below script can also be \"_framework/blazor.web.js\" depending on the hosting model --\u003e\n\u003cscript src=\"_framework/blazor.webassembly.js\" autostart=\"false\"\u003e\u003c/script\u003e\n\u003c!--\n    WASM Feature Detect - from GoogleChromeLabs\n    CDN UMD Version: https://unpkg.com/wasm-feature-detect/dist/umd/index.js\n    Repo: https://github.com/GoogleChromeLabs/wasm-feature-detect\n--\u003e\n\u003cscript webworker-enabled src=\"wasm-feature-detect.1.5.1.js\"\u003e\u003c/script\u003e\n\u003c!-- \n    The below script tag is used to detect SIMD support on the running device and load the appropriate build \n    If SIMD is not supported it loads _frameworkCompat/ instead of _framework/ \n--\u003e\n\u003cscript webworker-enabled\u003e\n    // Blazor WASM will fail to load if BigInt64Array or BigUint64Array is not found, but it does not use them on startup\n    if (!globalThis.BigInt64Array) globalThis.BigInt64Array = function () { };\n    if (!globalThis.BigUint64Array) globalThis.BigUint64Array = function () { };\n\n    (async () =\u003e {\n        var url = new URL(location.href);\n        let verboseStart = url.searchParams.get('verboseStart') === '1';\n        var forceCompatMode = url.searchParams.get('forceCompatMode') === '1';\n        var supportsSimd = await wasmFeatureDetect.simd();\n        if (verboseStart) console.log('supportsSimd', supportsSimd);\n        // compat mode build could be built without wasm exception support if needed and detected here\n        var supportsExceptions = await wasmFeatureDetect.exceptions();\n        if (verboseStart) console.log('supportsExceptions', supportsExceptions);\n        var useCompatMode = !supportsSimd || !supportsExceptions;\n        if (forceCompatMode) {\n            if (verboseStart) console.log('forceCompatMode', forceCompatMode);\n            useCompatMode = true;\n        }\n        if (verboseStart) console.log('useCompatMode', useCompatMode);\n        // Blazor United (.Net 8 Blazor Web App) Blazor.start settings are slightly different than Blazor WebAssembly (Blazor WebAssembly Standalone App)\n        var getRuntimeType = function () {\n            for (var script of document.scripts) {\n                if (script.src.indexOf('_framework/blazor.web.js') !== -1) return 'united';\n                if (script.src.indexOf('_framework/blazor.webassembly.js') !== -1) return 'wasm';\n            }\n            return '';\n        }\n        var runtimeType = getRuntimeType();\n        // customize the resource loader for the runtime that is loaded\n        // https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/startup?view=aspnetcore-8.0#load-boot-resources\n        var webAssemblyConfig = {\n            loadBootResource: function (type, name, defaultUri, integrity) {\n                if (verboseStart) console.log(`Loading: '${type}', '${name}', '${defaultUri}', '${integrity}'`);\n                if (useCompatMode) {\n                    let newUrl = defaultUri.replace('_framework/', '_frameworkCompat/');\n                    return newUrl;\n                }\n            },\n        };\n        if (runtimeType === 'wasm') {\n            // Blazor WebAssembly Standalone App\n            Blazor.start(webAssemblyConfig);\n        } else if (runtimeType === 'united') {\n            // Blazor Web App (formally Blazor United)\n            Blazor.start({ webAssembly: webAssemblyConfig });\n        } else {\n            // Fallback supports both known Blazor WASM runtimes\n            // Modified loader that will work with both United and WASM runtimes (doesn't require detection)\n            webAssemblyConfig.webAssembly = webAssemblyConfig;\n            Blazor.start(webAssemblyConfig);\n        }\n    })();\n\u003c/script\u003e\n```\n\n### ASP.Net Core Hosted\nIf using ASP.Net Core hosted Blazor WASM, the server needs to be told to serve the ```.dat``` file type or some files will not be served from _frameworkCompat resulting in ```File not found``` errors in the browser.\n\nIn the server project Program.cs file modify the ```app.UseStaticFiles``` call to allow serving .dat files.\n```cs\n// Enable the .dat file extension (required to serve icudt.dat from _frameworkCompat/\nvar provider = new FileExtensionContentTypeProvider();\nprovider.Mappings[\".dat\"] = \"application/octet-stream\";\napp.UseStaticFiles(new StaticFileOptions\n{\n    ContentTypeProvider = provider\n});\n```\n\n### Blazor WASM Project Configuration\nAdd ReleaseCompat configuration rule to the Blazor WASM .csproj file (used during publish)\n```xml\n\u003cPropertyGroup Condition=\" '$(Configuration)' == 'ReleaseCompat' \"\u003e\n    \u003cWasmEnableSIMD\u003efalse\u003c/WasmEnableSIMD\u003e\n    \u003cBlazorWebAssemblyJiterpreter\u003efalse\u003c/BlazorWebAssemblyJiterpreter\u003e\n    \u003cWasmEnableExceptionHandling\u003efalse\u003c/WasmEnableExceptionHandling\u003e\n\u003c/PropertyGroup\u003e\n```\n\n### Publish\n\nExample publish.bat to build first with SIMD support, and then without SIMD support for compatibility. This batch script is located in the project folder.\n(If using ASP.Net Core hosted Blazor WASM this file would be in the Server's project folder)\n```batch\nREM Normal build with SIMD and BlazorWebAssemblyJiterpreter enabled (.Net 8 RC 2 defaults)\ndotnet publish --nologo --configuration Release --output \"bin\\Publish\"\n\nREM ReleaseCompat build with SIMD and BlazorWebAssemblyJiterpreter disabled\ndotnet publish --nologo --no-restore --configuration ReleaseCompat --output \"bin\\PublishCompat\"\n\nREM Combine builds\nREM Copy the 'wwwroot\\_framework' folder contents from the 2nd build to 'wwwroot\\_frameworkCompat' in the 1st build\nxcopy /I /E /Y \"bin\\PublishCompat\\wwwroot\\_framework\" \"bin\\Publish\\wwwroot\\_frameworkCompat\"\n```\n\nDeploy your modified 1st build. Some extra changes, that are not covered here, would be needed for PWAs with service workers and caching. See [Progressive Web Apps](#progressive-web-apps) below for more information about detecting SIMD in PWAs.\n\nNote: The ```webworker-enabled``` attribute on the ```\u003cscript\u003e``` tags enables those scripts in [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) when using [SpawnDev.BlazorJS.WebWorkers](https://github.com/LostBeard/SpawnDev.BlazorJS#spawndevblazorjswebworkers).\n\n\n## Progressive Web Apps\nOption 2 above does not work completely with PWAs. Some additional changes are needed to support caching in service workers.\n\nExample modified service-worker.published.js  \n```js\n// WASM feature detection requires an async call so the code is wrapped in an async function.\n(async function () {\n    // Caution! Be sure you understand the caveats before publishing an application with\n    // offline support. See https://aka.ms/blazor-offline-considerations\n\n    // Use SIMD support detection to decide which build assets to cache\n    // Below line is disabled in place of SIMD detection which will determine which assets list to load\n    // self.importScripts('./service-worker-assets.js');\n    self.importScripts('./wasm-feature-detect.1.5.1.js');\n    var supportsSimd = await wasmFeatureDetect.simd();\n    var supportsExceptions = await wasmFeatureDetect.exceptions();\n    var useCompatMode = !supportsSimd || !supportsExceptions;\n    var serviceWorkerAssetsFile = useCompatMode ? './service-worker-assets-compat.js' : './service-worker-assets.js';\n    self.importScripts(serviceWorkerAssetsFile);\n    if (useCompatMode) {\n        // update the url of the assets to use _frameworkCompat/ instead of _framework/\n        self.assetsManifest.assets.forEach(function(o) {\n            if (o.url.startsWith('_framework/')) {\n                o.url = o.url.replace('_framework/', '_frameworkCompat/');\n            }\n        });\n    }\n\n   // The code below is standard unmodified service worker code from the Blazor WASM template\n\n    self.addEventListener('install', event =\u003e event.waitUntil(onInstall(event)));\n    self.addEventListener('activate', event =\u003e event.waitUntil(onActivate(event)));\n    self.addEventListener('fetch', event =\u003e event.respondWith(onFetch(event)));\n\n    const cacheNamePrefix = 'offline-cache-';\n    const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;\n    const offlineAssetsInclude = [/\\.dll$/, /\\.pdb$/, /\\.wasm/, /\\.html/, /\\.js$/, /\\.json$/, /\\.css$/, /\\.woff$/, /\\.png$/, /\\.jpe?g$/, /\\.gif$/, /\\.ico$/, /\\.blat$/, /\\.dat$/];\n    const offlineAssetsExclude = [/^service-worker\\.js$/];\n\n    // Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'.\n    const base = \"/\";\n    const baseUrl = new URL(base, self.origin);\n    const manifestUrlList = self.assetsManifest.assets.map(asset =\u003e new URL(asset.url, baseUrl).href);\n\n    async function onInstall(event) {\n        console.info('Service worker: Install');\n        // Fetch and cache all matching items from the assets manifest\n        const assetsRequests = self.assetsManifest.assets\n            .filter(asset =\u003e offlineAssetsInclude.some(pattern =\u003e pattern.test(asset.url)))\n            .filter(asset =\u003e !offlineAssetsExclude.some(pattern =\u003e pattern.test(asset.url)))\n            .map(asset =\u003e new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));\n        await caches.open(cacheName).then(cache =\u003e cache.addAll(assetsRequests));\n    }\n\n    async function onActivate(event) {\n        console.info('Service worker: Activate');\n        // Delete unused caches\n        const cacheKeys = await caches.keys();\n        await Promise.all(cacheKeys\n            .filter(key =\u003e key.startsWith(cacheNamePrefix) \u0026\u0026 key !== cacheName)\n            .map(key =\u003e caches.delete(key)));\n    }\n\n    async function onFetch(event) {\n        let cachedResponse = null;\n        if (event.request.method === 'GET') {\n            // For all navigation requests, try to serve index.html from cache,\n            // unless that request is for an offline resource.\n            // If you need some URLs to be server-rendered, edit the following check to exclude those URLs\n            const shouldServeIndexHtml = event.request.mode === 'navigate'\n                \u0026\u0026 !manifestUrlList.some(url =\u003e url === event.request.url);\n\n            const request = shouldServeIndexHtml ? 'index.html' : event.request;\n            const cache = await caches.open(cacheName);\n            cachedResponse = await cache.match(request);\n        }\n        return cachedResponse || fetch(event.request);\n    }\n})()\n```\n\nAlso, one more command is needed in the publish.bat file to copy over the compatibility build's service-worker-assets.js file.\n\nUpdated publish.bat  \n```batch\nREM Normal build with SIMD and BlazorWebAssemblyJiterpreter enabled (.Net 8 RC 2 defaults)\ndotnet publish --nologo --configuration Release --output \"bin\\Publish\"\n\nREM ReleaseCompat build with SIMD and BlazorWebAssemblyJiterpreter disabled\ndotnet publish --nologo --no-restore --configuration ReleaseCompat --output \"bin\\PublishCompat\"\n\nREM Combine builds\nREM Copy the 'wwwroot\\_framework' folder contents from the 2nd build to 'wwwroot\\_frameworkCompat' in the 1st build\nxcopy /I /E /Y \"bin\\PublishCompat\\wwwroot\\_framework\" \"bin\\Publish\\wwwroot\\_frameworkCompat\"\n\nREM If building a PWA app with server-worker-assets.js the service-worker script needs to be modified to also detect SIMD and cache the appropriate build\nREM Copy the service-worker-assets.js from the 2nd build to 'service-worker-assets-compat.js' of the 1st build\ncopy /Y \"bin\\PublishCompat\\wwwroot\\service-worker-assets.js\" \"bin\\Publish\\wwwroot\\service-worker-assets-compat.js\"\n```\n\n## Testing\nThis project is tested with BrowserStack. \"[BrowserStack.com](https://www.browserstack.com/open-source) loves Open Source.\" It is a great resource for testing web apps and desktop apps on multiple platforms. Most open source projects, even popular ones, have zero or little funding. BrowserStack supports Open Source with free testing for open source projects.\n\nTest results on various platforms will be listed here once they have been tested.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flostbeard%2Fblazorwasmsimddetectexample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flostbeard%2Fblazorwasmsimddetectexample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flostbeard%2Fblazorwasmsimddetectexample/lists"}