Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 + tools/ci) with a "Connect to a Different Server" menu item and popout that lets the desktop app switch to any self-hosted Fluxer instance, plus fixes for well-known discovery on single-domain self-hosted deployments and a false-positive ERR_ABORTED on same-origin client redirects during the switch. Defaults to chat.fluxr.chat and uses an isolated userData directory from the official build.
547 lines
21 KiB
Rust
547 lines
21 KiB
Rust
use std::{env, fs, path};
|
|
|
|
fn var(k: &str) -> String {
|
|
env::var(k).unwrap()
|
|
}
|
|
|
|
fn use_masm() -> bool {
|
|
env::var("CARGO_CFG_TARGET_ENV") == Ok("msvc".to_string())
|
|
&& var("CARGO_CFG_TARGET_ARCH") == "x86_64"
|
|
&& var("HOST").contains("-windows-")
|
|
}
|
|
|
|
fn include_amx() -> bool {
|
|
let arch = var("CARGO_CFG_TARGET_ARCH");
|
|
let os = var("CARGO_CFG_TARGET_OS");
|
|
os == "macos"
|
|
|| (env::var("CARGO_FEATURE_APPLE_AMX_IOS").is_ok() && os == "ios" && arch == "aarch64")
|
|
}
|
|
|
|
fn include_sme() -> bool {
|
|
let arch = var("CARGO_CFG_TARGET_ARCH");
|
|
let os = var("CARGO_CFG_TARGET_OS");
|
|
arch == "aarch64" && (os == "macos" || os == "linux")
|
|
}
|
|
|
|
// Probe whether the target assembler can actually assemble SME instructions.
|
|
// Old binutils (e.g. the Debian stretch aarch64 cross-toolchain used in CI)
|
|
// predate SME and reject the mnemonics even with `.arch armv9-a+sme2`, which
|
|
// breaks the build. When the probe fails we skip the SME kernels entirely;
|
|
// the matching `tract_sme` cfg keeps the Rust side from referencing the
|
|
// (now absent) kernel symbols, and dispatch falls back to the portable path.
|
|
fn assembler_supports_sme() -> bool {
|
|
cc::Build::new()
|
|
.file("arm64/sme/dummy_sme.S")
|
|
.cargo_metadata(false)
|
|
.cargo_warnings(false)
|
|
.warnings(false)
|
|
.try_compile("tract_sme_probe")
|
|
.is_ok()
|
|
}
|
|
|
|
// Probe whether the target assembler can encode FEAT_DotProd `sdot` (the
|
|
// indexed int8 form used by arm64simd_mmm_i32_8x8_dot). Old binutils — notably
|
|
// the Debian stretch aarch64 cross-toolchain in CI — predate FEAT_DotProd and
|
|
// reject `.cpu ...+dotprod` / `sdot` outright. When the probe fails we skip the
|
|
// SDOT kernel and the `tract_arm64_dotprod` cfg; the runtime falls back to the
|
|
// SMLAL 8x8 i32 kernel.
|
|
fn assembler_supports_dotprod() -> bool {
|
|
cc::Build::new()
|
|
.file("arm64/arm64simd/dummy_dotprod.S")
|
|
.cargo_metadata(false)
|
|
.cargo_warnings(false)
|
|
.warnings(false)
|
|
.try_compile("tract_dotprod_probe")
|
|
.is_ok()
|
|
}
|
|
|
|
// Probe whether the target assembler can encode `vpdpbusd ymm` (AVX-512 VNNI
|
|
// with AVX-512 VL, i.e. the 256-bit form). binutils gained this in ~2.30
|
|
// (2018); the Debian stretch toolchain ships 2.28 and rejects the mnemonic.
|
|
// When the probe fails we skip the VNNI kernel and the `tract_avx512vnni` cfg;
|
|
// the runtime falls back to the AVX2 i32 path.
|
|
fn assembler_supports_avx512vnni() -> bool {
|
|
cc::Build::new()
|
|
.file("x86_64/avx512vnni/dummy_vnni.S")
|
|
.cargo_metadata(false)
|
|
.cargo_warnings(false)
|
|
.warnings(false)
|
|
.try_compile("tract_avx512vnni_probe")
|
|
.is_ok()
|
|
}
|
|
|
|
fn include_sve() -> bool {
|
|
// SVE/SVE2 lives on ARMv9 server/mobile cores (Neoverse V1+/N2+, Cortex-X2+,
|
|
// Graviton 3/4) — Linux aarch64. No Apple silicon has SVE.
|
|
var("CARGO_CFG_TARGET_ARCH") == "aarch64" && var("CARGO_CFG_TARGET_OS") == "linux"
|
|
}
|
|
|
|
// Probe whether the C compiler supports SVE intrinsics (arm_sve.h + `+sve`).
|
|
// Old toolchains (e.g. the Debian stretch cross-gcc) lack them; when the probe
|
|
// fails we skip the SVE kernels and the `tract_sve` cfg, so the Rust side never
|
|
// references the (absent) symbols and dispatch falls back to NEON.
|
|
fn compiler_supports_sve() -> bool {
|
|
let out_dir = path::PathBuf::from(var("OUT_DIR"));
|
|
let probe = out_dir.join("sve_probe.c");
|
|
fs::write(&probe, "#include <arm_sve.h>\nint p(void){ return (int)svcntw(); }\n").unwrap();
|
|
cc::Build::new()
|
|
.file(&probe)
|
|
.flag("-march=armv8.2-a+sve")
|
|
.cargo_metadata(false)
|
|
.cargo_warnings(false)
|
|
.warnings(false)
|
|
.try_compile("tract_sve_probe")
|
|
.is_ok()
|
|
}
|
|
|
|
fn jump_table() -> Vec<String> {
|
|
println!("cargo:rerun-if-changed=src/frame/mmm/fuse.rs");
|
|
std::fs::read_to_string("src/frame/mmm/fuse.rs")
|
|
.unwrap()
|
|
.lines()
|
|
.filter(|l| l.contains("// jump_to:"))
|
|
.map(|l| l.split("jump_to:").nth(1).unwrap().to_owned())
|
|
.collect()
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct ConfigForHalf {
|
|
extra_flags: Vec<String>,
|
|
needs_pragma: bool,
|
|
}
|
|
|
|
impl ConfigForHalf {
|
|
fn new(extra_flags: Vec<String>, needs_pragma: bool) -> ConfigForHalf {
|
|
ConfigForHalf { extra_flags, needs_pragma }
|
|
}
|
|
|
|
fn all() -> Vec<ConfigForHalf> {
|
|
let mut configs = vec![];
|
|
for extra_flags in
|
|
[vec![], vec!["-march=armv8.2-a".to_string()], vec!["-mcpu=cortex-a55".to_string()]]
|
|
{
|
|
for needs_pragma in [false, true] {
|
|
configs.push(ConfigForHalf::new(extra_flags.clone(), needs_pragma))
|
|
}
|
|
}
|
|
configs
|
|
}
|
|
|
|
fn cc(&self) -> cc::Build {
|
|
let mut cc = cc::Build::new();
|
|
for flag in &self.extra_flags {
|
|
cc.flag(flag);
|
|
}
|
|
cc
|
|
}
|
|
|
|
fn works(&self) -> bool {
|
|
let filename = if self.needs_pragma {
|
|
"arm64/arm64fp16/dummy_fmla_pragma.S"
|
|
} else {
|
|
"arm64/arm64fp16/dummy_fmla_no_pragma.S"
|
|
};
|
|
self.cc().file(filename).try_compile("dummy").is_ok()
|
|
}
|
|
|
|
pub fn probe() -> Option<ConfigForHalf> {
|
|
Self::all().iter().find(|c| c.works()).cloned()
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let target = var("TARGET");
|
|
let arch = var("CARGO_CFG_TARGET_ARCH");
|
|
let os = var("CARGO_CFG_TARGET_OS");
|
|
let out_dir = path::PathBuf::from(var("OUT_DIR"));
|
|
|
|
let suffix = env!("CARGO_PKG_VERSION").replace(['-', '.'], "_");
|
|
make_extern_kernel_decl_macro(&out_dir, &suffix);
|
|
|
|
// `tract_sme` is set below only when both include_sme() and the assembler
|
|
// SME probe succeed; declare it so rustc's unexpected-cfg lint stays quiet.
|
|
println!("cargo:rustc-check-cfg=cfg(tract_sme)");
|
|
// Set below only when include_sve() and the SVE compiler probe both pass.
|
|
println!("cargo:rustc-check-cfg=cfg(tract_sve)");
|
|
// Set below only when the aarch64 assembler probe for `sdot` passes.
|
|
println!("cargo:rustc-check-cfg=cfg(tract_arm64_dotprod)");
|
|
// Set below only when the x86_64 assembler probe for vpdpbusd ymm passes.
|
|
println!("cargo:rustc-check-cfg=cfg(tract_avx512vnni)");
|
|
|
|
match arch.as_ref() {
|
|
"x86_64" => {
|
|
let mut files = preprocess_files("x86_64/fma", &[], &suffix, false);
|
|
// The VNNI kernel is compiled separately (conditional on a probe) to
|
|
// avoid breaking old assemblers. Remove it from the main file list.
|
|
files.retain(|f| {
|
|
!f.file_name().and_then(|n| n.to_str()).map_or(false, |n| n.contains("avx512vnni"))
|
|
});
|
|
files.extend(preprocess_files("x86_64/avx512", &[], &suffix, false));
|
|
|
|
if os == "windows" {
|
|
if use_masm() {
|
|
let mut lib_exe = cc::windows_registry::find(&target, "lib.exe")
|
|
.expect("Could not find lib.exe");
|
|
lib_exe
|
|
.arg(format!("/out:{}", out_dir.join("x86_64_fma.lib").to_str().unwrap()));
|
|
for f in files {
|
|
let mut obj = f.clone();
|
|
obj.set_extension("o");
|
|
let mut ml_exe = cc::windows_registry::find(&target, "ml64.exe")
|
|
.expect("Could not find ml64.exe");
|
|
if !ml_exe
|
|
.arg("/Fo")
|
|
.arg(&obj)
|
|
.arg("/c")
|
|
.arg(&f)
|
|
.status()
|
|
.unwrap()
|
|
.success()
|
|
{
|
|
for (i, l) in std::fs::read_to_string(&f).unwrap().lines().enumerate() {
|
|
println!("{i:8} {l}");
|
|
}
|
|
panic!();
|
|
}
|
|
lib_exe.arg(obj);
|
|
}
|
|
assert!(lib_exe.status().unwrap().success());
|
|
println!("cargo:rustc-link-search=native={}", out_dir.to_str().unwrap());
|
|
println!("cargo:rustc-link-lib=static=x86_64_fma");
|
|
} else {
|
|
cc::Build::new()
|
|
.files(files)
|
|
.flag("-mfma")
|
|
.flag("-mf16c")
|
|
.compile("x86_64_fma");
|
|
|
|
// clang at least (dunno about gcc) outputs .asm files in the
|
|
// root directory that we need to clean up so we don't pollute
|
|
// the build output/working directory
|
|
let _ = fs::remove_file("fma_mmm_f32_16x6.asm");
|
|
let _ = fs::remove_file("fma_mmm_i32_8x8.asm");
|
|
let _ = fs::remove_file("fma_sigmoid_f32.asm");
|
|
let _ = fs::remove_file("fma_tanh_f32.asm");
|
|
}
|
|
} else {
|
|
cc::Build::new().files(files).flag("-mfma").compile("x86_64_fma");
|
|
}
|
|
// VNNI kernel compiled separately so old assemblers (binutils < 2.30,
|
|
// e.g. Debian stretch) that can't encode `vpdpbusd ymm` don't break
|
|
// the whole x86_64 build. The `tract_avx512vnni` cfg gates the
|
|
// matching Rust extern declarations and dispatch registration.
|
|
//
|
|
// The template stays in x86_64/fma/ (alongside dispatcher.j2 and the
|
|
// other partials it includes) so the jinja env can resolve its includes.
|
|
if assembler_supports_avx512vnni() {
|
|
let tmpl = path::Path::new("x86_64/fma/avx512vnni_mmm_i32_8x8.S.j2");
|
|
let out = out_dir.join(format!("avx512vnni_mmm_i32_8x8_{suffix}.S"));
|
|
preprocess_file(tmpl, &out, &[], &suffix, false);
|
|
cc::Build::new().file(&out).flag("-mfma").compile("x86_64_avx512vnni");
|
|
println!("cargo:rustc-cfg=tract_avx512vnni");
|
|
}
|
|
}
|
|
"arm" | "armv7" => {
|
|
let files = preprocess_files("arm32/armvfpv2", &[], &suffix, false);
|
|
cc::Build::new().files(files).flag("-marm").flag("-mfpu=vfp").compile("armvfpv2");
|
|
let files = preprocess_files(
|
|
"arm32/armv7neon",
|
|
&[("core", vec!["cortexa7", "cortexa9", "generic"])],
|
|
&suffix,
|
|
false,
|
|
);
|
|
cc::Build::new().files(files).flag("-marm").flag("-mfpu=neon").compile("armv7neon");
|
|
}
|
|
"aarch64" => {
|
|
let mut files = preprocess_files(
|
|
"arm64/arm64simd",
|
|
&[("core", vec!["a53", "a55", "gen"])],
|
|
&suffix,
|
|
false,
|
|
);
|
|
// The SDOT kernel is compiled separately (conditional on a probe) so
|
|
// old assemblers (binutils < 2.30, e.g. Debian stretch) that can't
|
|
// encode `sdot` don't break the whole arm64simd build. Remove it
|
|
// from the main file list.
|
|
files.retain(|f| {
|
|
!f.file_name().and_then(|n| n.to_str()).map_or(false, |n| n.contains("_dot"))
|
|
});
|
|
cc::Build::new().files(files).compile("arm64simd");
|
|
// The template stays in arm64/arm64simd/ (alongside the jinja partials
|
|
// it includes) so the env can resolve its includes. The
|
|
// `tract_arm64_dotprod` cfg gates the matching Rust extern + dispatch.
|
|
if assembler_supports_dotprod() {
|
|
let tmpl = path::Path::new("arm64/arm64simd/arm64simd_mmm_i32_8x8_dot.S.j2");
|
|
let out = out_dir.join(format!("arm64simd_mmm_i32_8x8_dot_{suffix}.S"));
|
|
preprocess_file(tmpl, &out, &[], &suffix, false);
|
|
cc::Build::new().file(&out).compile("arm64simd_dot");
|
|
println!("cargo:rustc-cfg=tract_arm64_dotprod");
|
|
}
|
|
if include_amx() {
|
|
let files = preprocess_files("arm64/apple_amx", &[], &suffix, false);
|
|
cc::Build::new().files(files).compile("appleamx");
|
|
}
|
|
if include_sme() && assembler_supports_sme() {
|
|
let files = preprocess_files("arm64/sme", &[], &suffix, false);
|
|
cc::Build::new().files(files).compile("sme");
|
|
println!("cargo:rustc-cfg=tract_sme");
|
|
}
|
|
if include_sve() && compiler_supports_sve() {
|
|
// VLA SVE kernels (C intrinsics, fixed symbols — not suffix-templated).
|
|
cc::Build::new()
|
|
.file("arm64/sve/sve_mmm_f32.c")
|
|
.file("arm64/sve/sve_mmv_f32_64x1.c")
|
|
.file("arm64/sve/sve_mmm_i32.c")
|
|
.file("arm64/sve/sve_mmm_i32_64x1.c")
|
|
.flag("-march=armv8.2-a+sve")
|
|
.compile("tract_sve_kernels");
|
|
// f16 kernels need native FP16 arithmetic (+fp16); compiled
|
|
// separately so the +sve-only kernels above never gain fp16
|
|
// codegen. Runtime-gated on has_fp16() as well as SVE2.
|
|
cc::Build::new()
|
|
.file("arm64/sve/sve_mmm_f16.c")
|
|
.file("arm64/sve/sve_mmv_f16_64x1.c")
|
|
.flag("-march=armv8.2-a+sve+fp16")
|
|
.compile("tract_sve_f16_kernels");
|
|
println!("cargo:rustc-cfg=tract_sve");
|
|
}
|
|
if std::env::var("CARGO_FEATURE_NO_FP16").is_err() {
|
|
let config =
|
|
ConfigForHalf::probe().expect("No configuration found for fp16 support");
|
|
let files = preprocess_files(
|
|
"arm64/arm64fp16",
|
|
&[("core", vec!["a55", "gen"])],
|
|
&suffix,
|
|
config.needs_pragma,
|
|
);
|
|
config.cc().files(files).compile("arm64fp16")
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
type Variant = (&'static str, Vec<&'static str>);
|
|
|
|
fn preprocess_files(
|
|
input: impl AsRef<path::Path>,
|
|
variants: &[Variant],
|
|
suffix: &str,
|
|
needs_pragma: bool,
|
|
) -> Vec<path::PathBuf> {
|
|
let out_dir = path::PathBuf::from(var("OUT_DIR"));
|
|
let mut files = vec![];
|
|
let dir_entries = {
|
|
let mut dir_entries: Vec<fs::DirEntry> =
|
|
input.as_ref().read_dir().unwrap().map(|f| f.unwrap()).collect();
|
|
dir_entries.sort_by_key(|a| a.path());
|
|
dir_entries
|
|
};
|
|
for f in dir_entries {
|
|
let fname = f.path().file_name().unwrap().to_str().unwrap().to_owned();
|
|
if fname.ends_with(".S.j2") {
|
|
let tmpl_file = fname;
|
|
let concerned_variants: Vec<&Variant> =
|
|
variants.iter().filter(|v| tmpl_file.contains(v.0)).collect();
|
|
let expanded_variants = concerned_variants.iter().map(|pair| pair.1.len()).product();
|
|
for v in 0..expanded_variants {
|
|
let mut tmpl_file = tmpl_file.clone();
|
|
let mut id = v;
|
|
let mut globals = vec![];
|
|
for variable in variants {
|
|
let key = variable.0;
|
|
let value = variable.1[id % variable.1.len()];
|
|
globals.push((key, value));
|
|
tmpl_file = tmpl_file.replace(key, value);
|
|
id /= variable.1.len();
|
|
}
|
|
let out_name = tmpl_file.strip_suffix(".S.j2").unwrap();
|
|
let file = out_dir.join(format!("{out_name}.S"));
|
|
preprocess_file(f.path(), &file, &globals, suffix, needs_pragma);
|
|
files.push(file);
|
|
}
|
|
}
|
|
}
|
|
files
|
|
}
|
|
|
|
/// Replace `//` assembly comments with `;` for MSVC assembler.
|
|
/// Must be called on rendered output (not on Jinja2 source, which uses `//` for integer division).
|
|
fn strip_comments(s: &str) -> String {
|
|
s.lines().map(|line| line.replace("//", ";")).collect::<Vec<String>>().join("\n")
|
|
}
|
|
|
|
fn preprocess_file(
|
|
template: impl AsRef<path::Path>,
|
|
output: impl AsRef<path::Path>,
|
|
variants: &[(&'static str, &'static str)],
|
|
suffix: &str,
|
|
needs_pragma: bool,
|
|
) {
|
|
println!("cargo:rerun-if-changed={}", template.as_ref().to_string_lossy());
|
|
let family = var("CARGO_CFG_TARGET_FAMILY");
|
|
let os = var("CARGO_CFG_TARGET_OS");
|
|
|
|
let msvc = use_masm();
|
|
println!("cargo:rerun-if-changed={}", template.as_ref().to_string_lossy());
|
|
let input = fs::read_to_string(&template).unwrap();
|
|
let l = if os == "macos" {
|
|
"L"
|
|
} else if family == "windows" {
|
|
""
|
|
} else {
|
|
".L"
|
|
}
|
|
.to_owned();
|
|
let long = if msvc { "dd" } else { ".long" };
|
|
let g = if os == "macos" || os == "ios" || os == "watchos" || os == "tvos" { "_" } else { "" };
|
|
let align = if msvc { "align" } else { ".align" };
|
|
let offset = if msvc { "offset" } else { "rip + " };
|
|
|
|
let mut env = build_jinja_env(template.as_ref().parent().unwrap());
|
|
|
|
let main_name = template.as_ref().file_name().unwrap().to_str().unwrap();
|
|
env.add_template_owned(main_name.to_string(), input).unwrap_or_else(|e| {
|
|
eprintln!("Parsing {}: {e}", template.as_ref().to_string_lossy());
|
|
panic!();
|
|
});
|
|
|
|
let tmpl = env.get_template(main_name).unwrap();
|
|
|
|
let mut ctx = std::collections::BTreeMap::<String, minijinja::Value>::new();
|
|
ctx.insert("msvc".into(), msvc.into());
|
|
ctx.insert("needs_pragma".into(), needs_pragma.into());
|
|
ctx.insert("family".into(), family.into());
|
|
ctx.insert("os".into(), os.into());
|
|
ctx.insert("L".into(), l.into());
|
|
ctx.insert("G".into(), g.into());
|
|
ctx.insert("suffix".into(), suffix.into());
|
|
ctx.insert("long".into(), long.into());
|
|
ctx.insert("jump_table".into(), minijinja::Value::from_serialize(&jump_table()));
|
|
ctx.insert("align".into(), align.into());
|
|
ctx.insert("offset".into(), offset.into());
|
|
|
|
for (k, v) in variants {
|
|
ctx.insert(k.to_string(), (*v).into());
|
|
}
|
|
|
|
if include_amx() {
|
|
let (amx_set, amx_clr) = amx_globals();
|
|
ctx.insert("AMX_SET".into(), amx_set.into());
|
|
ctx.insert("AMX_CLR".into(), amx_clr.into());
|
|
}
|
|
|
|
match tmpl.render(&ctx) {
|
|
Ok(rendered) => {
|
|
let rendered = if msvc { strip_comments(&rendered) } else { rendered };
|
|
fs::write(&output, rendered).unwrap();
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Rendering {}: {e:#}", template.as_ref().to_string_lossy());
|
|
panic!();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_jinja_env(template_dir: &path::Path) -> minijinja::Environment<'static> {
|
|
let mut env = minijinja::Environment::new();
|
|
|
|
// Custom filters
|
|
env.add_filter("float16", float16_filter);
|
|
env.add_filter("setting", setting_filter);
|
|
env.add_filter("lsl", lsl_filter);
|
|
env.add_filter("u", unsigned_filter);
|
|
|
|
// Custom function: amx("op", gpr) -> assembly .word directive
|
|
env.add_function("amx", amx_function);
|
|
|
|
// Load all partials (.j2 = Jinja2 macros/includes, .S.raw = raw assembly with brace escaping)
|
|
for f in walkdir::WalkDir::new(template_dir) {
|
|
let f = f.unwrap();
|
|
if f.path().is_dir() {
|
|
continue;
|
|
}
|
|
|
|
let fname = f.path().file_name().unwrap().to_str().unwrap().to_owned();
|
|
let text = std::fs::read_to_string(f.path()).unwrap_or_else(|_| panic!("file {f:?}"));
|
|
let text = if fname.ends_with(".S.raw") {
|
|
Some(text.replace("{{", "{").replace("}}", "}"))
|
|
} else if fname.ends_with(".j2") && !fname.ends_with(".S.j2") {
|
|
Some(text)
|
|
} else {
|
|
None
|
|
};
|
|
if let Some(text) = text {
|
|
let key = f
|
|
.path()
|
|
.strip_prefix(template_dir)
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap()
|
|
.to_owned()
|
|
.replace('\\', "/");
|
|
println!("cargo:rerun-if-changed={}", f.path().to_string_lossy().replace('\\', "/"));
|
|
env.add_template_owned(key, text).unwrap_or_else(|e| {
|
|
eprintln!("Parsing partial {}: {e}", f.path().to_string_lossy());
|
|
panic!();
|
|
});
|
|
}
|
|
}
|
|
|
|
env
|
|
}
|
|
|
|
fn make_extern_kernel_decl_macro(out_dir: &path::Path, suffix: &str) {
|
|
let macro_decl = r#"
|
|
macro_rules! extern_kernel {
|
|
(fn $name: ident($($par_name:ident : $par_type: ty ),*) -> $rv: ty) => {
|
|
paste! {
|
|
unsafe extern "C" { pub fn [<$name _ _suffix>]($(par_name: $par_type),*) -> $rv; }
|
|
pub use [<$name _ _suffix>] as $name;
|
|
}
|
|
}
|
|
}"#
|
|
.replace("_suffix", suffix);
|
|
std::fs::write(out_dir.join("extern_kernel_macro.rs"), macro_decl).unwrap();
|
|
}
|
|
|
|
// --- Custom filters and functions ---
|
|
|
|
fn float16_filter(value: f64) -> String {
|
|
let bits = half::f16::from_f32(value as f32).to_bits();
|
|
format!(".short {bits}")
|
|
}
|
|
|
|
fn setting_filter(value: i64, bit: i64) -> String {
|
|
let result = value | (1i64 << bit);
|
|
result.to_string()
|
|
}
|
|
|
|
fn lsl_filter(value: i64, shift: i64) -> String {
|
|
let result = value << shift;
|
|
result.to_string()
|
|
}
|
|
|
|
fn unsigned_filter(value: i64) -> String {
|
|
let result = value as u64;
|
|
result.to_string()
|
|
}
|
|
|
|
fn amx_function(op: String, gpr: u32) -> String {
|
|
let ops = [
|
|
"ldx", "ldy", "stx", "sty", "ldz", "stz", "ldzi", "stzi", "extrx", "extry", "fma64",
|
|
"fms64", "fma32", "fms32", "mac16", "fma16", "fms16", "setclr", "vecint", "vecfp",
|
|
"matint", "matfp", "genlut",
|
|
];
|
|
let op_id = ops.iter().position(|x| *x == op.as_str()).unwrap();
|
|
format!(".word 0x{:x} \t\t\t\t// AMX {} x{}\n", 0x201000 + (op_id << 5) + gpr as usize, op, gpr)
|
|
}
|
|
|
|
fn amx_nop_op_imm5(op: usize, imm5: usize) -> String {
|
|
format!("nop\nnop\nnop\n.word 0x{:x}\n", (0x201000 + (op << 5) + imm5))
|
|
}
|
|
|
|
fn amx_globals() -> (String, String) {
|
|
(amx_nop_op_imm5(17, 0), amx_nop_op_imm5(17, 1))
|
|
}
|