Current section
Files
Jump to
Current section
Files
build.zig
const std = @import("std");
const builtin = @import("builtin");
const os = builtin.os.tag;
fn resolveLlvmConfigPath(b: *std.Build) []const u8 {
return b.option([]const u8, "llvm-config", "Path to llvm-config") orelse
b.graph.environ_map.get("LLVM_CONFIG_PATH") orelse
b.findProgram(&.{"llvm-config"}, &.{
b.pathResolve(&.{ "priv", "llvm-prebuilt", "bin" }),
"/opt/homebrew/opt/llvm/bin",
"/usr/local/opt/llvm/bin",
"/opt/local/libexec/llvm/bin",
}) catch "llvm-config";
}
fn createCapiModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, mlir_include_dir: []const u8) *std.Build.Module {
const header_generator = b.addSystemCommand(&.{"elixir"});
// Header discovery must observe additions and removals in both include trees.
header_generator.has_side_effects = true;
header_generator.addFileArg(b.path("native/tools/capi/gen_header.exs"));
header_generator.addArg("--mlir-include-dir");
header_generator.addDirectoryArg(.{ .cwd_relative = mlir_include_dir });
header_generator.addArg("--beaver-include-dir");
header_generator.addDirectoryArg(b.path("native/include"));
header_generator.addArg("--output");
const capi_header = header_generator.addOutputFileArg("beaver_capi.h");
const ast_dump = b.addSystemCommand(&.{ b.graph.zig_exe, "cc", "-E", "-Xclang", "-ast-dump=json", "-x", "c-header" });
ast_dump.addArg("-I");
ast_dump.addDirectoryArg(b.path("native/include"));
ast_dump.addArg("-I");
ast_dump.addDirectoryArg(.{ .cwd_relative = mlir_include_dir });
ast_dump.addArgs(&.{ "-MD", "-MF" });
_ = ast_dump.addDepFileOutputArg("capi_ast.d");
ast_dump.addFileArg(capi_header);
const capi_ast = ast_dump.captureStdOut(.{ .basename = "capi_ast.json" });
const manifest_generator = b.addSystemCommand(&.{"elixir"});
manifest_generator.addFileArg(b.path("native/tools/capi/gen_manifest.exs"));
manifest_generator.addArg("--ast");
manifest_generator.addFileArg(capi_ast);
manifest_generator.addArg("--declaration");
const declaration_manifest = manifest_generator.addOutputFileArg("capi_manifest.json");
manifest_generator.addArg("--callback-bridge");
const callback_bridge_manifest = manifest_generator.addOutputFileArg("capi_callback_bridge.json");
b.getInstallStep().dependOn(&b.addInstallFile(declaration_manifest, "capi_manifest.json").step);
b.getInstallStep().dependOn(&b.addInstallFile(callback_bridge_manifest, "capi_callback_bridge.json").step);
const translate_c = b.addTranslateC(.{
.root_source_file = capi_header,
.target = target,
.optimize = optimize,
});
translate_c.defineCMacro("_NO_CRT_STDIO_INLINE", "1");
translate_c.addIncludePath(b.path("native/include"));
translate_c.addIncludePath(.{ .cwd_relative = mlir_include_dir });
return translate_c.createModule();
}
fn createNativeModule(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
root: []const u8,
capi_module: *std.Build.Module,
kinda_module: *std.Build.Module,
mlir_include_dir: []const u8,
) *std.Build.Module {
const module = b.createModule(.{
.root_source_file = b.path(root),
.target = target,
.optimize = optimize,
});
module.addImport("kinda", kinda_module);
module.addImport("c_api", capi_module);
module.addIncludePath(.{ .cwd_relative = "native/include" });
// add these to get ZLS working properly
module.addIncludePath(.{ .cwd_relative = mlir_include_dir });
if (os == .linux or os == .macos) {
module.link_libc = true;
}
return module;
}
fn createNativePartitionLib(
b: *std.Build,
name: []const u8,
root: []const u8,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
capi_module: *std.Build.Module,
kinda_module: *std.Build.Module,
mlir_include_dir: []const u8,
linkage: std.builtin.LinkMode,
mlir_lib_dir: []const u8,
) *std.Build.Step.Compile {
const lib = b.addLibrary(.{
.name = name,
.linkage = linkage,
.root_module = createNativeModule(b, target, optimize, root, capi_module, kinda_module, mlir_include_dir),
});
if (linkage == .dynamic) {
// Partition dylibs call the MLIR C API directly, so link them against
// libMLIRBeaver. Resolving those symbols only through the NIF shim's
// dependency does not work on Linux: glibc does not put sibling
// dependencies in a partition's symbol scope.
lib.root_module.linkSystemLibrary("MLIRBeaver", .{ .use_pkg_config = .no });
lib.linker_allow_shlib_undefined = true;
lib.root_module.addLibraryPath(.{ .cwd_relative = mlir_lib_dir });
lib.root_module.addRPathSpecial(if (os == .linux) "$ORIGIN" else "@loader_path");
}
return lib;
}
fn createCMakeStep(b: *std.Build, llvm_cmake_dir: []const u8, mlir_cmake_dir: []const u8, optimize: std.builtin.OptimizeMode) *std.Build.Step {
const step = b.step("cmake", "Build and install CMake targets");
const cmake_build_dir = b.pathJoin(&.{ b.install_path, "cmake_build" });
const cmake_cache_path = b.pathJoin(&.{ cmake_build_dir, "CMakeCache.txt" });
// cmake_build command
const cmake_configure = b.addSystemCommand(&.{ "cmake", "-S", "native", "-G", "Ninja", "-B", cmake_build_dir });
cmake_configure.addArgs(&.{
b.fmt("-DLLVM_DIR={s}", .{llvm_cmake_dir}),
b.fmt("-DMLIR_DIR={s}", .{mlir_cmake_dir}),
b.fmt("-DCMAKE_INSTALL_PREFIX={s}", .{b.install_path}),
b.fmt("-DCMAKE_INSTALL_MESSAGE={s}", .{"LAZY"}),
b.fmt("-DCMAKE_BUILD_TYPE={s}", .{switch (optimize) {
.Debug => "Debug",
.ReleaseSafe => "RelWithDebInfo",
.ReleaseFast => "Release",
.ReleaseSmall => "MinSizeRel",
}}),
});
const cmake_build_install = b.addSystemCommand(&.{ "cmake", "--build", cmake_build_dir, "--target", "install" });
step.dependOn(&cmake_build_install.step);
std.Io.Dir.accessAbsolute(b.graph.io, cmake_cache_path, .{}) catch {
step.dependOn(&cmake_configure.step);
cmake_build_install.step.dependOn(&cmake_configure.step);
};
return step;
}
/// Creates ODS extraction steps. Returns both a container step for manual execution
/// and the final extraction step for proper dependency management.
///
/// Important: The container_step does not depend on cmake. Depending on the container_step
/// instead of the extraction_step will lead to premature execution since the dependency
/// chain is broken.
fn createODSExtractionStep(b: *std.Build, generated_dir: []const u8, mlir_include_dir: []const u8, cmake_step: *std.Build.Step) *std.Build.Step {
const step = b.step("ods_extraction", "Extract ODS information as JSON");
const create_include_dir = b.addSystemCommand(&.{
"mkdir", "-p", generated_dir,
});
const dump_ods = b.addSystemCommand(&.{
"sh",
"-c",
b.fmt(
\\elixir native/tools/ods-extract/dump_ods.exs --mlir-include-dir "{s}" \
\\| xargs "{s}/tools/ods-extract" \
\\ -I "{s}" \
\\ -I "{s}/mlir/Dialect/ArmSME/IR" \
\\ -I "{s}/mlir/Dialect/IRDL/IR" \
\\ -I "{s}/mlir/Dialect/UB/IR" \
\\ -write-if-changed -o "{s}/ods_dump.json"
, .{ mlir_include_dir, b.install_path, mlir_include_dir, mlir_include_dir, mlir_include_dir, mlir_include_dir, generated_dir }),
});
dump_ods.step.dependOn(cmake_step);
dump_ods.step.dependOn(&create_include_dir.step);
step.dependOn(&dump_ods.step);
return step;
}
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
var optimize = b.standardOptimizeOption(.{});
if (b.graph.environ_map.get("MIX_ENV")) |env| {
if (std.mem.eql(u8, env, "test")) {
optimize = .Debug;
} else if (std.mem.eql(u8, env, "dev") or std.mem.eql(u8, env, "prod")) {
optimize = .ReleaseSafe;
}
}
// Environment variables and paths
const llvm_config_path = resolveLlvmConfigPath(b);
const generated_dir = b.pathJoin(&.{ b.install_path, "generated" });
const llvm_lib_dir_raw = b.run(&.{ llvm_config_path, "--libdir" });
const llvm_lib_dir = std.mem.trim(u8, llvm_lib_dir_raw, " \t\n\r");
const llvm_cmake_dir = b.pathJoin(&.{ llvm_lib_dir, "cmake", "llvm" });
const mlir_cmake_dir = b.pathJoin(&.{ llvm_lib_dir, "cmake", "mlir" });
const llvm_install_dir = b.pathResolve(&.{ llvm_lib_dir, ".." });
const mlir_include_dir = b.pathJoin(&.{ llvm_install_dir, "include" });
std.log.info("Using LLVM installation: {s}", .{llvm_install_dir});
b.addSearchPrefix(llvm_install_dir);
// add install path to search prefixes because CMake will shared this path as its install prefix
b.addSearchPrefix(b.install_path);
const capi_module = createCapiModule(b, target, optimize, mlir_include_dir);
const cmake_step = createCMakeStep(b, llvm_cmake_dir, mlir_cmake_dir, optimize);
// ODS extraction step (depends on cmake_build)
const ods_extraction_step = createODSExtractionStep(b, generated_dir, mlir_include_dir, cmake_step);
b.getInstallStep().dependOn(ods_extraction_step);
const kinda = b.dependency("kinda", .{});
const kinda_module = kinda.module("kinda");
// Native source is partitioned into dynamic libraries so each domain is
// cached independently: a source edit in one domain only recompiles that
// partition and the final shim, instead of forcing the comptime-heavy CAPI
// registry and kind surfaces to rebuild.
const core_lib = createNativePartitionLib(b, "beaver_core", "native/src/core.zig", target, optimize, capi_module, kinda_module, mlir_include_dir, .dynamic, llvm_lib_dir);
const conversion_lib = createNativePartitionLib(b, "beaver_conversion", "native/src/conversion_root.zig", target, optimize, capi_module, kinda_module, mlir_include_dir, .dynamic, llvm_lib_dir);
const callback_bridge_lib = createNativePartitionLib(b, "beaver_callback_bridge", "native/src/callback_bridge_root.zig", target, optimize, capi_module, kinda_module, mlir_include_dir, .dynamic, llvm_lib_dir);
const rewrite_pattern_lib = createNativePartitionLib(b, "beaver_rewrite_pattern", "native/src/rewrite_pattern_root.zig", target, optimize, capi_module, kinda_module, mlir_include_dir, .dynamic, llvm_lib_dir);
// Partition libraries link against libMLIRBeaver installed by the CMake
// step into the install prefix. On Linux, an explicit library path is
// required for the DT_NEEDED entry to be recorded (a bare search prefix is
// dropped by the linker); the CMake step must also run before they link.
const mlir_lib_dir = b.pathJoin(&.{ b.install_path, "lib" });
for ([_]*std.Build.Step.Compile{ core_lib, conversion_lib, callback_bridge_lib, rewrite_pattern_lib }) |partition| {
partition.root_module.addLibraryPath(.{ .cwd_relative = mlir_lib_dir });
partition.step.dependOn(cmake_step);
}
// Default target: the NIF shim links the partitions and assembles their
// exported NIF tables into a single library entry at load time.
const lib = b.addLibrary(.{
.name = "BeaverNIF",
.linkage = .dynamic,
.root_module = createNativeModule(b, target, optimize, "native/src/main.zig", capi_module, kinda_module, mlir_include_dir),
});
lib.root_module.linkLibrary(core_lib);
lib.root_module.linkLibrary(conversion_lib);
lib.root_module.linkLibrary(callback_bridge_lib);
lib.root_module.linkLibrary(rewrite_pattern_lib);
b.installArtifact(core_lib);
b.installArtifact(conversion_lib);
b.installArtifact(callback_bridge_lib);
b.installArtifact(rewrite_pattern_lib);
lib.step.dependOn(cmake_step);
std.log.info("Setting optimization mode for {s}: {any}", .{ lib.name, lib.root_module.optimize });
if (os == .linux) {
lib.root_module.addRPathSpecial("$ORIGIN");
}
if (os == .macos) {
lib.root_module.addRPathSpecial("@loader_path");
}
lib.root_module.linkSystemLibrary("MLIRBeaver", .{ .use_pkg_config = .no });
lib.linker_allow_shlib_undefined = true;
// copy runtime libs
b.installDirectory(.{ .source_dir = .{ .cwd_relative = llvm_lib_dir }, .install_dir = .prefix, .install_subdir = "lib", .include_extensions = &.{ ".so", ".dylib", ".dll" } });
b.installArtifact(lib);
const check = b.step("check", "Check if compiles");
check.dependOn(b.getInstallStep());
}