diff --git a/.github/workflows/mlir.yaml b/.github/workflows/mlir.yaml new file mode 100644 index 00000000..f4b754b4 --- /dev/null +++ b/.github/workflows/mlir.yaml @@ -0,0 +1,33 @@ +name: MLIR + +on: workflow_dispatch + +jobs: + build-mlir-emscripten: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup emscripten cache + uses: actions/cache@v4 + with: + path: ~/.emscripten-cache + key: ${{ runner.os }}-emscripten + restore-keys: | + ${{ runner.os }}-emscripten + + - name: Install dependencies + run: | + npm ci + + - name: Build + run: | + npm run build:mlir + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: mlir + path: | + build.em/llvm-project/install/ diff --git a/CMakePresets.json b/CMakePresets.json index 6fe5bd67..4dd516b3 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -43,6 +43,29 @@ "rhs": "" } }, + { + "name": "emscripten-mlir", + "displayName": "emscripten with MLIR", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build.em", + "toolchainFile": "$env{EMSCRIPTEN_ROOT}/cmake/Modules/Platform/Emscripten.cmake", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "YES", + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build.em/install/usr", + "CMAKE_INTERPROCEDURAL_OPTIMIZATION": "YES", + "CXX_ENABLE_FLATBUFFERS": "NO", + "CXX_INSTALL_WASI_SYSROOT": "YES", + "CXX_ENABLE_MLIR": "YES", + "LLVM_DIR": "${sourceDir}/build.em/llvm-project/install/lib/cmake/llvm", + "MLIR_DIR": "${sourceDir}/build.em/llvm-project/install/lib/cmake/mlir" + }, + "condition": { + "type": "notEquals", + "lhs": "$env{EMSCRIPTEN_ROOT}", + "rhs": "" + } + }, { "name": "wasi", "displayName": "wasi", diff --git a/package.json b/package.json index 7ef96ab9..de131543 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "build:storybook": "npm run build-storybook -w @robertoraggi/cxx-storybook", "build:emscripten": "npm run build -w cxx-frontend", "build:wasi": "zx scripts/build-wasi.mjs", + "build:mlir": "zx scripts/build-mlir.mjs", "storybook": "npm run storybook -w @robertoraggi/cxx-storybook", "setup-venv": "zx scripts/setup-venv.mjs", "update-tests": "zx scripts/update-tests.mjs", diff --git a/scripts/build-mlir.mjs b/scripts/build-mlir.mjs new file mode 100644 index 00000000..58378f73 --- /dev/null +++ b/scripts/build-mlir.mjs @@ -0,0 +1,161 @@ +// Copyright (c) 2025 Roberto Raggi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import * as zx from "zx"; +import { $ } from "zx"; + +$.verbose = true; + +async function download({ pkg, version, outdir = "." }) { + const baseUrl = `https://github.com/llvm/llvm-project/releases/download/llvmorg-${version}/`; + const fileName = `${pkg}-${version}.src.tar.xz`; + const exists = await zx.fs.exists(zx.path.join(outdir, fileName)); + if (!exists) { + const url = `${baseUrl}${fileName}`; + const response = await zx.fetch(url); + if (!response.ok) { + throw new Error(`Failed to download ${url}: ${response.statusText}`); + } + const payload = await response.arrayBuffer(); + await zx.fs.writeFile(zx.path.join(outdir, fileName), Buffer.from(payload)); + } + + // unpack + if (!(await zx.fs.exists(zx.path.join(outdir, pkg)))) { + await zx.fs.mkdir(zx.path.join(outdir, pkg), { recursive: true }); + await $`tar jxf ${outdir}/${fileName} -C ${outdir}/${pkg} --strip-components=1`.quiet(); + } +} + +async function downloadLLVM({ packages, version, outdir }) { + for (const pkg of packages) { + await download({ pkg, version, outdir }); + } +} + +async function main() { + const version = "20.1.2"; + const packages = ["cmake", "third-party", "llvm", "mlir"]; + + const llvm_source_dir = zx.path.resolve( + zx.path.join("build.em", "llvm-project") + ); + + const llvm_build_dir = zx.path.resolve( + zx.path.join("build.em", "llvm-project", "build") + ); + + const llvm_install_dir = zx.path.resolve( + zx.path.join("build.em", "llvm-project", "install") + ); + + const llvm_cmake_options = [ + "-G", + "Ninja", + "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_CXX_FLAGS=-DLLVM_BUILD_STATIC", + "-DCMAKE_EXE_LINKER_FLAGS=-sNODERAWFS -sEXIT_RUNTIME -sALLOW_MEMORY_GROWTH", + "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", + "-DLLVM_BUILD_EXTERNAL_COMPILER_RT=OFF", + "-DLLVM_BUILD_TOOLS=OFF", + "-DLLVM_ENABLE_EH=OFF", + "-DLLVM_ENABLE_FFI=OFF", + "-DLLVM_ENABLE_PROJECTS=mlir", + "-DLLVM_ENABLE_RTTI=OFF", + "-DLLVM_ENABLE_RUNTIMES=", + "-DLLVM_ENABLE_Z3_SOLVER=OFF", + "-DLLVM_INCLUDE_DOCS=OFF", + "-DLLVM_INCLUDE_TESTS=OFF", + "-DLLVM_INSTALL_UTILS=OFF", + "-DLLVM_LINK_LLVM_DYLIB=OFF", + "-DLLVM_OPTIMIZED_TABLEGEN=OFF", + "-DLLVM_TARGETS_TO_BUILD=WebAssembly", + ]; + + await zx.fs.mkdir(llvm_source_dir, { recursive: true }); + await zx.fs.mkdir(llvm_build_dir, { recursive: true }); + await zx.fs.mkdir(llvm_install_dir, { recursive: true }); + + await zx.fs.mkdir(llvm_source_dir, { recursive: true }); + await zx.fs.writeFile(zx.path.join(llvm_source_dir, ".gitignore"), "*"); + + await downloadLLVM({ version, packages, outdir: llvm_source_dir }); + + await $`emcmake cmake ${llvm_cmake_options} -S ${llvm_source_dir}/llvm -B ${llvm_build_dir}/llvm -DCMAKE_INSTALL_PREFIX=${llvm_install_dir}`; + + await $`cmake --build ${llvm_build_dir}/llvm --target install`; + + // fixup installation + + const executables = [ + "llvm-tblgen", + "mlir-pdll", + "mlir-tblgen", + "tblgen-to-irdl", + ]; + + // copy wasm payloads and create stubs for the executables + for (const app of executables) { + await zx.fs.copyFile( + zx.path.join(llvm_build_dir, "llvm", "bin", app + ".wasm"), + zx.path.join(llvm_install_dir, "bin", app + ".wasm") + ); + + await zx.fs.writeFile( + zx.path.join(llvm_install_dir, "bin", app), + `#!/usr/bin/env node\nrequire("./${app}.js");` + ); + + await zx.fs.chmod(zx.path.join(llvm_install_dir, "bin", app), 0o755); + } + + // fixup + const cmake_files = [ + zx.path.join( + llvm_install_dir, + "lib", + "cmake", + "llvm", + "LLVMExports-release.cmake" + ), + + zx.path.join( + llvm_install_dir, + "lib", + "cmake", + "mlir", + "MLIRTargets-release.cmake" + ), + ]; + + // replace .js with "" in the cmake files + for (const file of cmake_files) { + // replace .js with "" + const content = await zx.fs.readFile(file, "utf8"); + const newContent = content.replace(/\.js/g, ""); + await zx.fs.writeFile(file, newContent); + } +} + +try { + await main(); +} catch (e) { + console.error(e.message); +}