Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions rs/private/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
load("@bazel_lib//:bzl_library.bzl", "bzl_library")
load("@bazel_lib//lib:write_source_files.bzl", "write_source_files")
load("@bazel_skylib//rules:common_settings.bzl", "bool_setting")
load("@rules_rust//rust:rust_stdlib_filegroup.bzl", "rust_stdlib_filegroup")
load("//rs/platforms:triples.bzl", "ALL_TARGET_TRIPLES")
load(":all_crate_deps_test.bzl", "all_crate_deps_tests")
load(":bindgen_test.bzl", "bindgen_tests")
load(":bpf_linker_repository_test.bzl", "bpf_linker_repository_tests")
load(":cargo_toml_utils_test.bzl", "cargo_toml_utils_tests")
load(":cargo_workspace_graph_test.bzl", "cargo_workspace_graph_tests")
load(":cfg_oracle.bzl", "cfg_oracle_data")
load(":cfg_parser_test.bzl", "cfg_parser_tests")
load(":lint_flags_test.bzl", "lint_flags_tests")
load(":registry_utils_test.bzl", "registry_utils_tests")
Expand Down Expand Up @@ -36,6 +39,19 @@ cargo_workspace_graph_tests()

cfg_parser_tests()

cfg_oracle_data(
name = "generated_cfg_target_data",
tags = ["manual"],
triples = ALL_TARGET_TRIPLES,
)

write_source_files(
name = "update_cfg_target_data",
files = {
"cfg_target_data.bzl": ":generated_cfg_target_data.bzl",
},
)

lint_flags_tests()

registry_utils_tests()
Expand Down Expand Up @@ -360,6 +376,17 @@ bzl_library(
name = "cfg_parser",
srcs = ["cfg_parser.bzl"],
visibility = ["//rs:__subpackages__"],
deps = [":cfg_target_data"],
)

bzl_library(
name = "cfg_target_data",
srcs = ["cfg_target_data.bzl"],
)

bzl_library(
name = "cfg_oracle",
srcs = ["cfg_oracle.bzl"],
)

bzl_library(
Expand Down
43 changes: 43 additions & 0 deletions rs/private/cfg_oracle.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Hermetically generates Cargo cfg oracle data from the registered rustc."""

def _cfg_oracle_data_impl(ctx):
rustc_info = ctx.attr._rustc[DefaultInfo]
rustc_files = rustc_info.files.to_list()
if len(rustc_files) != 1:
fail("expected exactly one rustc executable, got {}".format(rustc_files))

output = ctx.outputs.output
args = ctx.actions.args()
args.add("--rustc", rustc_files[0])
args.add("--output", output)
args.add_all(ctx.attr.triples, before_each = "--triple")

ctx.actions.run(
arguments = [args],
executable = ctx.executable._generator,
mnemonic = "GenerateCargoCfgOracle",
outputs = [output],
progress_message = "Generating Cargo cfg oracle data for %{label}",
tools = [depset(transitive = [rustc_info.files, rustc_info.default_runfiles.files])],
)

return [DefaultInfo(files = depset([output]))]

cfg_oracle_data = rule(
implementation = _cfg_oracle_data_impl,
attrs = {
"triples": attr.string_list(mandatory = True),
"_generator": attr.label(
cfg = "exec",
default = Label("//tools/cfg_oracle:cfg_oracle_generator"),
executable = True,
),
"_rustc": attr.label(
cfg = "exec",
default = Label("@rules_rust//rust/toolchain:current_rustc_files"),
),
},
outputs = {
"output": "%{name}.bzl",
},
)
158 changes: 39 additions & 119 deletions rs/private/cfg_parser.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
def _get(xs, index, default):
if index < len(xs):
return xs[index]
return default
"""Parses and evaluates Cargo cfg predicates for Rust target triples."""

load(":cfg_target_data.bzl", "CFG_ATOMS", "CFG_ATOM_IDS_BY_TRIPLE")

def _emit_pending(frames, pending_ident, pending_eq_key):
# Moves any pending identifier into a predicate node in the current frame.
Expand Down Expand Up @@ -67,6 +66,15 @@ def _cfg_tokenize(expr):
############################################

def cfg_parse(expr):
"""Parses a cfg predicate into its evaluator representation.

Args:
expr: The cfg predicate body to parse.

Returns:
A tuple containing the parsed syntax tree and whether it uses package
feature cfgs.
"""
tokens = _cfg_tokenize(expr)
frames = [{"fn": "__ROOT__", "args": []}]
pending_ident = None
Expand Down Expand Up @@ -141,114 +149,36 @@ def cfg_parse(expr):

return root_args[0], uses_feature_cfg

############################################
# Triple → cfg attribute derivation
############################################

def _normalize_os(os_raw):
if os_raw == "darwin":
return "macos"
return os_raw

def _normalize_arch(arch_raw):
# RISC-V target triples encode the enabled ISA extensions in their first
# component, while Rust's cfg(target_arch) exposes only the register width.
if arch_raw.startswith("riscv32"):
return "riscv32"
if arch_raw.startswith("riscv64"):
return "riscv64"
return arch_raw

def _family_for_os(os_name):
if os_name == "windows":
return "windows"
if os_name in [
"linux", "macos", "ios", "freebsd", "netbsd", "openbsd", "dragonfly",
"android", "solaris", "illumos", "aix", "haiku", "hurd",
]:
return "unix"
return ""

def _family_for_arch_and_os(arch, os_name):
if arch.startswith("wasm"):
return "wasm"
return _family_for_os(os_name)

def _pointer_width_for_arch(arch):
# Common targets
arch64 = ["s390x","bpfel","bpfeb"]
if "64" in arch or arch in arch64:
return "64"

arch32 = [
"i686","i586","i386","x86","arm","armv7","thumbv7","thumbv6","mips","mipsel",
"powerpc","ppc","sparc","riscv32","wasm32","m68k","loongarch32",
]
if "32" in arch or arch in arch32:
return "32"

return "64"

def _endian_for_arch(arch):
big_set = ["m68k","s390x","sparc","sparc64","powerpc","powerpc64"]
if arch.endswith("be") or arch.endswith("eb") or arch in big_set:
return "big"
if arch.startswith("mips") and (not arch.endswith("el")):
return "big"

# Most contemporary targets are little-endian:
return "little"

def _abi_from_env(env):
# Very rough: surface a few commonly referenced ABIs
abi_pieces = ["eabi", "eabihf", "elf", "gnuabi64"]
for abi_piece in abi_pieces:
if abi_piece in env:
return abi_piece
return ""

def _target_has_feature(ctx, feature):
# x86_64 baseline implies SSE2.
if feature == "sse2":
return ctx["target_arch"] == "x86_64"

# AArch64 baseline implies NEON.
if feature == "neon":
return ctx["target_arch"] == "aarch64"

return False

def triple_to_cfg_attrs(triple):
parts = triple.split("-")
arch_part = _normalize_arch(_get(parts, 0, ""))
vendor_part = _get(parts, 1, "unknown")
os_raw_part = _get(parts, 2, "none")
env_part = "-".join(parts[3:])
os_norm = _normalize_os(os_raw_part)
fam = _family_for_arch_and_os(arch_part, os_norm)
width = _pointer_width_for_arch(arch_part)
endian = _endian_for_arch(arch_part)
abi_guess = _abi_from_env(env_part)

return {
def _cfg_context(triple, atoms):
ctx = {
"_triple": triple,

"target_arch": arch_part,
"target_vendor": vendor_part,
"target_os": os_norm,
"target_env": env_part,
"target_family": fam,
"target_endian": endian,
"target_pointer_width": width,
"target_abi": abi_guess,

# convenience booleans for bare predicates
"true": True,
"false": False,
"unix": fam == "unix",
"windows": fam == "windows",
"wasm": fam == "wasm",
}
for atom in atoms:
pieces = atom.split("=", 1)
if len(pieces) == 1:
ctx[atom] = True
continue

key = pieces[0]
encoded_value = pieces[1]
value = encoded_value[1:-1]
key_values = ctx.get(key)
if key_values == None:
key_values = set()
ctx[key] = key_values
key_values.add(value)
return ctx

def cfg_atoms_for_triple(triple):
atom_ids = CFG_ATOM_IDS_BY_TRIPLE.get(triple)
if atom_ids == None:
fail("Unsupported target triple '{}'; expected one of ALL_TARGET_TRIPLES".format(triple))
return [CFG_ATOMS[atom_id] for atom_id in atom_ids]

def triple_to_cfg_attrs(triple):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will run once per-cargo-closure. I wonder if its worth deduping the construciton of these contexts by lifting it higher in the graph and passing those in instead of the triples (since triples can be retrieved from them anyway?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contexts are currently constructed once in resolve_cargo_workspace_members for each Cargo closure, then reused by the resolver and workspace dependency rendering; target-expression results are cached as well. Lifting them further would only deduplicate across closures with the same triple set, so I left that plumbing unchanged pending evidence that cross-closure construction is material.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect a typical bazel repo to have 5-15 closures (there are some internal ones for rules_rs, though I should probably merge to a single one...). so yeah maybe not a huge deal

return _cfg_context(triple, cfg_atoms_for_triple(triple))

############################################
# Evaluator (non-recursive; explicit stack)
Expand All @@ -257,17 +187,7 @@ def triple_to_cfg_attrs(triple):
def _eval_eq(ctx, key, value, features):
if key == "feature":
return value in features
if key == "target_feature":
return _target_has_feature(ctx, value)
known = [
"target_os","target_family","target_arch","target_env",
"target_vendor","target_endian","target_pointer_width","target_abi",
]
if key in known:
return ctx.get(key, "") == value
# Unknown keys evaluate to False
# fail("Unknown key %s" % key)
return False
return value in ctx.get(key, ())

def _eval_pred(ctx, name):
return ctx.get(name, False)
Expand Down
38 changes: 35 additions & 3 deletions rs/private/cfg_parser_test.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Tests for Cargo cfg parsing and target cfg derivation."""

load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
load(":cfg_parser.bzl", "cfg_matches", "cfg_matches_expr_for_cfg_attrs", "cfg_matches_expr_for_triples", "triple_to_cfg_attrs")
load("//rs/platforms:triples.bzl", "ALL_TARGET_TRIPLES")
load(":cfg_parser.bzl", "cfg_atoms_for_triple", "cfg_matches", "cfg_matches_expr_for_cfg_attrs", "cfg_matches_expr_for_triples", "triple_to_cfg_attrs")
load(":cfg_target_data.bzl", "CFG_ATOMS", "CFG_ATOM_IDS_BY_TRIPLE")

def _cfg(expr):
return "cfg(%s)" % expr
Expand Down Expand Up @@ -38,10 +42,11 @@ def _cfg_parser_smoke_test_impl(ctx):
asserts.true(env, cfg_matches(_cfg('target_family = "windows"'), win))
asserts.true(env, cfg_matches(_cfg('target_pointer_width = "64"'), win))
asserts.true(env, cfg_matches(_cfg('target_env = "gnu"'), win_gnu))
asserts.true(env, cfg_matches(_cfg('target_env = "gnullvm"'), win_gnullvm))
asserts.true(env, cfg_matches(_cfg('target_env = "gnu"'), win_gnullvm))
asserts.true(env, cfg_matches(_cfg('target_abi = "llvm"'), win_gnullvm))

# Wasm facts
asserts.true(env, cfg_matches(_cfg("wasm"), wasm))
asserts.false(env, cfg_matches(_cfg("wasm"), wasm))
asserts.false(env, cfg_matches(_cfg("unix"), wasm))
asserts.false(env, cfg_matches(_cfg("windows"), wasm))
asserts.true(env, cfg_matches(_cfg('target_arch = "wasm32"'), wasm))
Expand Down Expand Up @@ -120,8 +125,35 @@ def _cfg_parser_smoke_test_impl(ctx):

cfg_parser_smoke_test = unittest.make(_cfg_parser_smoke_test_impl)

def _cfg_parser_oracle_test_impl(ctx):
env = unittest.begin(ctx)

asserts.equals(env, sorted(ALL_TARGET_TRIPLES), sorted(CFG_ATOM_IDS_BY_TRIPLE.keys()))

for triple in ALL_TARGET_TRIPLES:
expected_atoms = [CFG_ATOMS[atom_id] for atom_id in CFG_ATOM_IDS_BY_TRIPLE[triple]]
expected_set = set(expected_atoms)
asserts.equals(
env,
expected_atoms,
cfg_atoms_for_triple(triple),
"computed cfg atoms differ for {}".format(triple),
)
for atom in CFG_ATOMS:
asserts.equals(
env,
atom in expected_set,
cfg_matches(_cfg(atom), triple),
"cfg({}) differs for {}".format(atom, triple),
)

return unittest.end(env)

cfg_parser_oracle_test = unittest.make(_cfg_parser_oracle_test_impl)

def cfg_parser_tests():
return unittest.suite(
"cfg_parser_tests",
cfg_parser_oracle_test,
cfg_parser_smoke_test,
)
Loading
Loading