Skip to content

Commit a5b093b

Browse files
committed
Add scripts to build MLIR with Emscripten
assuming emsdk is installed and in PATH, `npm run build:mlir` will build MLIR with the emscripten toolchain.
1 parent d4fe99c commit a5b093b

File tree

4 files changed

+218
-0
lines changed

4 files changed

+218
-0
lines changed

.github/workflows/mlir.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: MLIR
2+
3+
on: workflow_dispatch
4+
5+
jobs:
6+
build-mlir-emscripten:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- uses: actions/checkout@v4
11+
12+
- name: Setup emscripten cache
13+
uses: actions/cache@v4
14+
with:
15+
path: ~/.emscripten-cache
16+
key: ${{ runner.os }}-emscripten
17+
restore-keys: |
18+
${{ runner.os }}-emscripten
19+
20+
- name: Install dependencies
21+
run: |
22+
npm ci
23+
24+
- name: Build
25+
run: |
26+
npm run build:mlir
27+
28+
- name: Upload artifacts
29+
uses: actions/upload-artifact@v4
30+
with:
31+
name: mlir
32+
path: |
33+
build.em/llvm-project/install/

CMakePresets.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,29 @@
4343
"rhs": ""
4444
}
4545
},
46+
{
47+
"name": "emscripten-mlir",
48+
"displayName": "emscripten with MLIR",
49+
"generator": "Ninja",
50+
"binaryDir": "${sourceDir}/build.em",
51+
"toolchainFile": "$env{EMSCRIPTEN_ROOT}/cmake/Modules/Platform/Emscripten.cmake",
52+
"cacheVariables": {
53+
"CMAKE_EXPORT_COMPILE_COMMANDS": "YES",
54+
"CMAKE_BUILD_TYPE": "Release",
55+
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build.em/install/usr",
56+
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": "YES",
57+
"CXX_ENABLE_FLATBUFFERS": "NO",
58+
"CXX_INSTALL_WASI_SYSROOT": "YES",
59+
"CXX_ENABLE_MLIR": "YES",
60+
"LLVM_DIR": "${sourceDir}/build.em/llvm-project/install/lib/cmake/llvm",
61+
"MLIR_DIR": "${sourceDir}/build.em/llvm-project/install/lib/cmake/mlir"
62+
},
63+
"condition": {
64+
"type": "notEquals",
65+
"lhs": "$env{EMSCRIPTEN_ROOT}",
66+
"rhs": ""
67+
}
68+
},
4669
{
4770
"name": "wasi",
4871
"displayName": "wasi",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"build:storybook": "npm run build-storybook -w @robertoraggi/cxx-storybook",
3636
"build:emscripten": "npm run build -w cxx-frontend",
3737
"build:wasi": "zx scripts/build-wasi.mjs",
38+
"build:mlir": "zx scripts/build-mlir.mjs",
3839
"storybook": "npm run storybook -w @robertoraggi/cxx-storybook",
3940
"setup-venv": "zx scripts/setup-venv.mjs",
4041
"update-tests": "zx scripts/update-tests.mjs",

scripts/build-mlir.mjs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) 2025 Roberto Raggi <[email protected]>
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
// SOFTWARE.
20+
21+
import * as zx from "zx";
22+
import { $ } from "zx";
23+
24+
$.verbose = true;
25+
26+
async function download({ pkg, version, outdir = "." }) {
27+
const baseUrl = `https://github.com/llvm/llvm-project/releases/download/llvmorg-${version}/`;
28+
const fileName = `${pkg}-${version}.src.tar.xz`;
29+
const exists = await zx.fs.exists(zx.path.join(outdir, fileName));
30+
if (!exists) {
31+
const url = `${baseUrl}${fileName}`;
32+
const response = await zx.fetch(url);
33+
if (!response.ok) {
34+
throw new Error(`Failed to download ${url}: ${response.statusText}`);
35+
}
36+
const payload = await response.arrayBuffer();
37+
await zx.fs.writeFile(zx.path.join(outdir, fileName), Buffer.from(payload));
38+
}
39+
40+
// unpack
41+
if (!(await zx.fs.exists(zx.path.join(outdir, pkg)))) {
42+
await zx.fs.mkdir(zx.path.join(outdir, pkg), { recursive: true });
43+
await $`tar jxf ${outdir}/${fileName} -C ${outdir}/${pkg} --strip-components=1`.quiet();
44+
}
45+
}
46+
47+
async function downloadLLVM({ packages, version, outdir }) {
48+
for (const pkg of packages) {
49+
await download({ pkg, version, outdir });
50+
}
51+
}
52+
53+
async function main() {
54+
const version = "20.1.2";
55+
const packages = ["cmake", "third-party", "llvm", "mlir"];
56+
57+
const llvm_source_dir = zx.path.resolve(
58+
zx.path.join("build.em", "llvm-project")
59+
);
60+
61+
const llvm_build_dir = zx.path.resolve(
62+
zx.path.join("build.em", "llvm-project", "build")
63+
);
64+
65+
const llvm_install_dir = zx.path.resolve(
66+
zx.path.join("build.em", "llvm-project", "install")
67+
);
68+
69+
const llvm_cmake_options = [
70+
"-G",
71+
"Ninja",
72+
"-DCMAKE_BUILD_TYPE=Release",
73+
"-DCMAKE_CXX_FLAGS=-DLLVM_BUILD_STATIC",
74+
"-DCMAKE_EXE_LINKER_FLAGS=-sNODERAWFS -sEXIT_RUNTIME -sALLOW_MEMORY_GROWTH",
75+
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
76+
"-DLLVM_BUILD_EXTERNAL_COMPILER_RT=OFF",
77+
"-DLLVM_BUILD_TOOLS=OFF",
78+
"-DLLVM_ENABLE_EH=OFF",
79+
"-DLLVM_ENABLE_FFI=OFF",
80+
"-DLLVM_ENABLE_PROJECTS=mlir",
81+
"-DLLVM_ENABLE_RTTI=OFF",
82+
"-DLLVM_ENABLE_RUNTIMES=",
83+
"-DLLVM_ENABLE_Z3_SOLVER=OFF",
84+
"-DLLVM_INCLUDE_DOCS=OFF",
85+
"-DLLVM_INCLUDE_TESTS=OFF",
86+
"-DLLVM_INSTALL_UTILS=OFF",
87+
"-DLLVM_LINK_LLVM_DYLIB=OFF",
88+
"-DLLVM_OPTIMIZED_TABLEGEN=OFF",
89+
"-DLLVM_TARGETS_TO_BUILD=WebAssembly",
90+
];
91+
92+
await zx.fs.mkdir(llvm_source_dir, { recursive: true });
93+
await zx.fs.mkdir(llvm_build_dir, { recursive: true });
94+
await zx.fs.mkdir(llvm_install_dir, { recursive: true });
95+
96+
await zx.fs.mkdir(llvm_source_dir, { recursive: true });
97+
await zx.fs.writeFile(zx.path.join(llvm_source_dir, ".gitignore"), "*");
98+
99+
await downloadLLVM({ version, packages, outdir: llvm_source_dir });
100+
101+
await $`emcmake cmake ${llvm_cmake_options} -S ${llvm_source_dir}/llvm -B ${llvm_build_dir}/llvm -DCMAKE_INSTALL_PREFIX=${llvm_install_dir}`;
102+
103+
await $`cmake --build ${llvm_build_dir}/llvm --target install`;
104+
105+
// fixup installation
106+
107+
const executables = [
108+
"llvm-tblgen",
109+
"mlir-pdll",
110+
"mlir-tblgen",
111+
"tblgen-to-irdl",
112+
];
113+
114+
// copy wasm payloads and create stubs for the executables
115+
for (const app of executables) {
116+
await zx.fs.copyFile(
117+
zx.path.join(llvm_build_dir, "llvm", "bin", app + ".wasm"),
118+
zx.path.join(llvm_install_dir, "bin", app + ".wasm")
119+
);
120+
121+
await zx.fs.writeFile(
122+
zx.path.join(llvm_install_dir, "bin", app),
123+
`#!/usr/bin/env node\nrequire("./${app}.js");`
124+
);
125+
126+
await zx.fs.chmod(zx.path.join(llvm_install_dir, "bin", app), 0o755);
127+
}
128+
129+
// fixup
130+
const cmake_files = [
131+
zx.path.join(
132+
llvm_install_dir,
133+
"lib",
134+
"cmake",
135+
"llvm",
136+
"LLVMExports-release.cmake"
137+
),
138+
139+
zx.path.join(
140+
llvm_install_dir,
141+
"lib",
142+
"cmake",
143+
"mlir",
144+
"MLIRTargets-release.cmake"
145+
),
146+
];
147+
148+
// replace .js with "" in the cmake files
149+
for (const file of cmake_files) {
150+
// replace .js with ""
151+
const content = await zx.fs.readFile(file, "utf8");
152+
const newContent = content.replace(/\.js/g, "");
153+
await zx.fs.writeFile(file, newContent);
154+
}
155+
}
156+
157+
try {
158+
await main();
159+
} catch (e) {
160+
console.error(e.message);
161+
}

0 commit comments

Comments
 (0)