Add native self-hosted instance connection to fluxer_desktop
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.
This commit is contained in:
@@ -0,0 +1,546 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user