fix(node): validate encoded CPU build flags

This commit is contained in:
Gatefixer
2026-08-06 06:35:58 +00:00
parent 3863189b16
commit fcef32bfd3
3 changed files with 232 additions and 22 deletions
+4
View File
@@ -12,6 +12,10 @@ categories.workspace = true
[lib]
crate-type = ["cdylib"]
[[test]]
name = "x86_64_v2_build_flags"
path = "build_support/x86_64_v2.rs"
[dependencies]
async-trait.workspace = true
arrow-ipc.workspace = true
+8 -22
View File
@@ -5,22 +5,15 @@ extern crate napi_build;
use std::env;
#[path = "build_support/x86_64_v2.rs"]
mod x86_64_v2;
const ENFORCE_BASELINE: &str = "LANCEDB_NODE_ENFORCE_X86_64_V2";
const X86_64_V2_FEATURES: [&str; 9] = [
"cmpxchg16b",
"fxsr",
"popcnt",
"sse",
"sse2",
"sse3",
"sse4.1",
"sse4.2",
"ssse3",
];
fn main() {
napi_build::setup();
println!("cargo:rerun-if-env-changed={ENFORCE_BASELINE}");
println!("cargo:rerun-if-env-changed=CARGO_ENCODED_RUSTFLAGS");
let is_linux_x64 = env::var("CARGO_CFG_TARGET_ARCH").as_deref() == Ok("x86_64")
&& env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux");
@@ -31,15 +24,8 @@ fn main() {
return;
}
let target_features = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
let features_above_v2 = target_features
.split(',')
.filter(|feature| !feature.is_empty() && !X86_64_V2_FEATURES.contains(feature))
.collect::<Vec<_>>();
assert!(
features_above_v2.is_empty(),
"Linux x64 Node addons must use the x86-64-v2 baseline; features above v2: {}",
features_above_v2.join(", ")
);
let encoded_rustflags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or_default();
x86_64_v2::validate_encoded_rustflags(&encoded_rustflags).unwrap_or_else(|error| {
panic!("Linux x64 Node addons must use the x86-64-v2 baseline; {error}")
});
}
+220
View File
@@ -0,0 +1,220 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
use std::collections::{BTreeMap, BTreeSet};
const BASELINE_CPU: &str = "x86-64-v2";
const BASELINE_FEATURES: [&str; 9] = [
"cmpxchg16b",
"fxsr",
"popcnt",
"sse",
"sse2",
"sse3",
"sse4.1",
"sse4.2",
"ssse3",
];
pub(crate) fn validate_encoded_rustflags(encoded: &str) -> Result<(), String> {
let mut target_cpu = None;
let mut feature_states = BTreeMap::new();
let mut required_disables = BTreeSet::new();
let mut unsupported_features = BTreeSet::new();
for option in codegen_options(encoded)? {
let Some((name, value)) = option.split_once('=') else {
continue;
};
match name {
"target-cpu" => target_cpu = Some(value),
"target-feature" => {
for toggle in value.split(',').filter(|toggle| !toggle.is_empty()) {
let (enabled, feature) = match toggle.as_bytes()[0] {
b'+' => (true, &toggle[1..]),
b'-' => (false, &toggle[1..]),
_ => return Err(format!("invalid target feature flag: {toggle}")),
};
feature_states.insert(feature, enabled);
if enabled && !BASELINE_FEATURES.contains(&feature) {
match feature {
// These are inherited from the workspace configuration and
// explicitly canceled by the Node configuration. Account for
// their implied AVX prerequisite as well as the named feature.
"avx" => {
required_disables.insert("avx");
}
"avx2" => {
required_disables.extend(["avx", "avx2"]);
}
"f16c" => {
required_disables.extend(["avx", "f16c"]);
}
"fma" => {
required_disables.extend(["avx", "fma"]);
}
_ => {
unsupported_features.insert(feature);
}
}
}
}
}
// LLVM arguments can independently alter the target feature set and
// cannot be proven safe by inspecting rustc's target options.
"llvm-args" => return Err("LLVM arguments can override the CPU baseline".to_owned()),
_ => {}
}
}
if target_cpu != Some(BASELINE_CPU) {
return Err(format!(
"effective target CPU is {}, expected {BASELINE_CPU}",
target_cpu.unwrap_or("unset")
));
}
if !unsupported_features.is_empty() {
return Err(format!(
"features above v2: {}",
unsupported_features
.into_iter()
.collect::<Vec<_>>()
.join(", ")
));
}
let not_disabled = required_disables
.into_iter()
.filter(|feature| feature_states.get(feature) != Some(&false))
.collect::<Vec<_>>();
if !not_disabled.is_empty() {
return Err(format!(
"inherited features not fully disabled: {}",
not_disabled.join(", ")
));
}
Ok(())
}
fn codegen_options(encoded: &str) -> Result<Vec<&str>, String> {
let arguments = encoded.split('\u{1f}').collect::<Vec<_>>();
let mut options = Vec::new();
let mut index = 0;
while index < arguments.len() {
let argument = arguments[index];
if argument == "-C" || argument == "--codegen" {
index += 1;
let option = arguments
.get(index)
.copied()
.ok_or_else(|| format!("missing value after {argument}"))?;
options.push(option.trim_start_matches('='));
} else if let Some(option) = argument.strip_prefix("-C") {
if !option.is_empty() {
options.push(option.trim_start_matches('='));
}
} else if let Some(option) = argument.strip_prefix("--codegen=") {
options.push(option);
}
index += 1;
}
Ok(options)
}
#[cfg(test)]
mod tests {
use super::*;
fn encoded(arguments: &[&str]) -> String {
arguments.join("\u{1f}")
}
#[test]
fn accepts_merged_workspace_and_node_flags() {
let flags = encoded(&[
"-C",
"target-cpu=haswell",
"-C",
"target-feature=+avx2,+fma,+f16c",
"-C",
"target-cpu=x86-64-v2",
"-C",
"target-feature=-avx,-avx2,-fma,-f16c",
]);
assert_eq!(validate_encoded_rustflags(&flags), Ok(()));
}
#[test]
fn accepts_non_merging_v2_boundary() {
let flags = encoded(&["-Ctarget-cpu=x86-64-v2"]);
assert_eq!(validate_encoded_rustflags(&flags), Ok(()));
}
#[test]
fn accepts_musl_dynamic_crt_configuration() {
let flags = encoded(&[
"-C",
"target-cpu=haswell",
"-C",
"target-feature=-crt-static,+avx2,+fma,+f16c",
"-C",
"target-cpu=x86-64-v2",
"-C",
"target-feature=-crt-static,-avx,-avx2,-fma,-f16c",
]);
assert_eq!(validate_encoded_rustflags(&flags), Ok(()));
}
#[test]
fn rejects_feature_omitted_from_target_cfg() {
let flags = encoded(&["-Ctarget-cpu=x86-64-v2", "-Ctarget-feature=+apxf"]);
assert_eq!(
validate_encoded_rustflags(&flags),
Err("features above v2: apxf".to_owned())
);
}
#[test]
fn rejects_unexpected_above_baseline_feature() {
let flags = encoded(&[
"--codegen=target-cpu=x86-64-v2",
"--codegen",
"target-feature=+bmi2",
]);
assert_eq!(
validate_encoded_rustflags(&flags),
Err("features above v2: bmi2".to_owned())
);
}
#[test]
fn rejects_incompletely_disabled_feature_implications() {
let flags = encoded(&["-Ctarget-cpu=x86-64-v2", "-Ctarget-feature=+avx2,-avx2"]);
assert_eq!(
validate_encoded_rustflags(&flags),
Err("inherited features not fully disabled: avx".to_owned())
);
}
#[test]
fn rejects_llvm_feature_overrides() {
let flags = encoded(&["-Ctarget-cpu=x86-64-v2", "-Cllvm-args=-mattr=+apxf"]);
assert_eq!(
validate_encoded_rustflags(&flags),
Err("LLVM arguments can override the CPU baseline".to_owned())
);
}
}