Skip to content

Commit f4e3587

Browse files
committed
Handle features and target_features in cfg
1 parent 2d4163c commit f4e3587

12 files changed

Lines changed: 209 additions & 67 deletions

File tree

rs/extensions.bzl

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ def _platform(triple, use_experimental_platforms):
3131
def _select(items):
3232
return {k: sorted(v) for k, v in items.items()}
3333

34+
def _cfg_match_info_for_target(target, platform_cfg_attrs, cfg_match_cache):
35+
match_info = cfg_match_cache.get(target)
36+
if match_info:
37+
return match_info
38+
39+
match_info = cfg_matches_expr_for_cfg_attrs(target, platform_cfg_attrs)
40+
cfg_match_cache[target] = match_info
41+
return match_info
42+
3443
def _add_to_dict(d, k, v):
3544
existing = d.get(k, [])
3645
if not existing:
@@ -248,14 +257,17 @@ def _generate_hub_and_spokes(
248257
pkg["local_path"] = local_path
249258
packages.append(pkg)
250259

251-
platform_cfg_attrs = [triple_to_cfg_attrs(triple, [], []) for triple in platform_triples]
260+
platform_cfg_attrs = [triple_to_cfg_attrs(triple) for triple in platform_triples]
261+
platform_cfg_attrs_by_triple = {}
262+
for cfg_attr in platform_cfg_attrs:
263+
platform_cfg_attrs_by_triple[cfg_attr["_triple"]] = cfg_attr
252264

253265
mctx.report_progress("Computing dependencies and features")
254266

255267
feature_resolutions_by_fq_crate = dict()
256268

257269
# TODO(zbarsky): Would be nice to resolve for _ALL_PLATFORMS instead of per-triple, but it's complicated.
258-
cfg_match_cache = {None: platform_triples}
270+
cfg_match_cache = {None: struct(matches = platform_triples, uses_feature_cfg = False)}
259271

260272
versions_by_name = dict()
261273
for package_index in range(len(packages)):
@@ -451,15 +463,13 @@ def _generate_hub_and_spokes(
451463
dep["feature_resolutions"] = feature_resolutions_by_fq_crate[dep_fq]
452464

453465
target = dep.get("target")
454-
match = cfg_match_cache.get(target)
455-
if not match:
456-
match = cfg_matches_expr_for_cfg_attrs(target, platform_cfg_attrs)
457-
458-
# TODO(zbarsky): Figure out how to do this optimization safely.
459-
#if len(match) == len(platform_cfg_attrs):
460-
# match = match_all
461-
cfg_match_cache[target] = match
462-
dep["target"] = set(match)
466+
match_info = _cfg_match_info_for_target(target, platform_cfg_attrs, cfg_match_cache)
467+
if match_info.uses_feature_cfg:
468+
dep["target_expr"] = target
469+
dep["feature_sensitive"] = True
470+
dep["target"] = set(platform_triples)
471+
else:
472+
dep["target"] = set(match_info.matches)
463473

464474
_date(mctx, "set up resolutions")
465475

@@ -521,16 +531,9 @@ def _generate_hub_and_spokes(
521531
versions.add(dep_fq)
522532

523533
target = dep.get("target")
524-
match = cfg_match_cache.get(target)
525-
if not match:
526-
match = cfg_matches_expr_for_cfg_attrs(target, platform_cfg_attrs)
527-
528-
# TODO(zbarsky): Figure out how to do this optimization safely.
529-
#if len(match) == len(platform_cfg_attrs):
530-
# match = match_all
531-
cfg_match_cache[target] = match
534+
match_info = _cfg_match_info_for_target(target, platform_cfg_attrs, cfg_match_cache)
532535

533-
for triple in match:
536+
for triple in match_info.matches:
534537
workspace_dep_labels_by_triple[triple].add(":" + dep_name)
535538
feature_resolutions.features_enabled[triple].update(features)
536539

@@ -555,7 +558,7 @@ def _generate_hub_and_spokes(
555558

556559
_date(mctx, "set up initial deps!")
557560

558-
resolve(mctx, packages, feature_resolutions_by_fq_crate, debug)
561+
resolve(mctx, packages, feature_resolutions_by_fq_crate, platform_cfg_attrs_by_triple, debug)
559562

560563
# Validate that we aren't trying to enable any `dep:foo` features that were not even in the lockfile.
561564
for package in packages:
@@ -839,14 +842,8 @@ RESOLVED_PLATFORMS = select({{
839842
bazel_target = "//" + paths.join(workspace_package, _normalize_path(dep["path"]).removeprefix(repo_root + "/"))
840843

841844
target = dep.get("target")
842-
match = cfg_match_cache.get(target)
843-
if not match:
844-
match = cfg_matches_expr_for_cfg_attrs(target, platform_cfg_attrs)
845-
846-
# TODO(zbarsky): Figure out how to do this optimization safely.
847-
#if len(match) == len(platform_cfg_attrs):
848-
# match = match_all
849-
cfg_match_cache[target] = match
845+
match_info = _cfg_match_info_for_target(target, platform_cfg_attrs, cfg_match_cache)
846+
match = match_info.matches
850847

851848
kind = dep["kind"]
852849
if kind == "dev":

rs/private/cfg_parser.bzl

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def cfg_parse(expr):
7171
frames = [{"fn": "__ROOT__", "args": []}]
7272
pending_ident = None
7373
pending_eq_key = None
74+
uses_feature_cfg = False
7475

7576
for t in tokens:
7677
kind = t["t"]
@@ -90,7 +91,7 @@ def cfg_parse(expr):
9091
if not pending_eq_key:
9192
fail("cfg parse error: string literal not expected here.")
9293
if pending_eq_key == "feature":
93-
fail("Feature evaluation in cfg is unsupported!")
94+
uses_feature_cfg = True
9495
frames[-1]["args"].append({
9596
"kind": "eq",
9697
"key": pending_eq_key,
@@ -138,7 +139,7 @@ def cfg_parse(expr):
138139
fail("cfg parse error: empty expression.")
139140
fail("cfg parse error: multiple top-level expressions; wrap with all(...)/any(...).")
140141

141-
return root_args[0]
142+
return root_args[0], uses_feature_cfg
142143

143144
############################################
144145
# Triple → cfg attribute derivation
@@ -192,7 +193,18 @@ def _abi_from_env(env):
192193
return abi_piece
193194
return ""
194195

195-
def triple_to_cfg_attrs(triple, features, target_features):
196+
def _target_has_feature(ctx, feature):
197+
# x86_64 baseline implies SSE2.
198+
if feature == "sse2":
199+
return ctx["target_arch"] == "x86_64"
200+
201+
# AArch64 baseline implies NEON.
202+
if feature == "neon":
203+
return ctx["target_arch"] == "aarch64"
204+
205+
return False
206+
207+
def triple_to_cfg_attrs(triple):
196208
parts = triple.split("-")
197209
arch_part = _get(parts, 0, "")
198210
vendor_part = _get(parts, 1, "unknown")
@@ -221,22 +233,17 @@ def triple_to_cfg_attrs(triple, features, target_features):
221233
"false": False,
222234
"unix": fam == "unix",
223235
"windows": fam == "windows",
224-
225-
# feature sets
226-
#"__features__": dict(((f, True) for f in features)),
227-
#"__tfeatures__": dict(((tf, True) for tf in target_features)),
228236
}
229237

230238
############################################
231239
# Evaluator (non-recursive; explicit stack)
232240
############################################
233241

234-
def _eval_eq(ctx, key, value):
242+
def _eval_eq(ctx, key, value, features):
235243
if key == "feature":
236-
return ctx.get("__features__", {}).get(value, False)
244+
return value in features
237245
if key == "target_feature":
238-
return ctx.get("__tfeatures__",
239-
{}).get(value, False)
246+
return _target_has_feature(ctx, value)
240247
known = [
241248
"target_os","target_family","target_arch","target_env",
242249
"target_vendor","target_endian","target_pointer_width","target_abi",
@@ -251,7 +258,7 @@ def _eval_pred(ctx, name):
251258
return ctx.get(name, False)
252259

253260

254-
def _cfg_eval(ast, ctx):
261+
def _cfg_eval(ast, ctx, features=[]):
255262
todo = [{"op": "VISIT", "node": ast}]
256263
results = []
257264
for _ in range(200000):
@@ -265,7 +272,7 @@ def _cfg_eval(ast, ctx):
265272
if kind == "pred":
266273
results.append(_eval_pred(ctx, node["name"]))
267274
elif kind == "eq":
268-
results.append(_eval_eq(ctx, node["key"], node["value"]))
275+
results.append(_eval_eq(ctx, node["key"], node["value"], features))
269276
else:
270277
children = node["args"]
271278
n = len(children)
@@ -299,20 +306,28 @@ def _cfg_eval(ast, ctx):
299306
fail("cfg eval error: unexpected result stack size.")
300307
return results[0]
301308

302-
def cfg_matches(expr, triple, features=[], target_features=[]):
303-
ast = cfg_parse(expr)
304-
ctx = triple_to_cfg_attrs(triple, features, target_features)
305-
return _cfg_eval(ast, ctx)
309+
def cfg_matches(expr, triple, features=[]):
310+
ast, _ = cfg_parse(expr)
311+
ctx = triple_to_cfg_attrs(triple)
312+
return _cfg_eval(ast, ctx, features)
306313

307-
def cfg_matches_expr_for_triples(expr, triples, features=[], target_features=[]):
308-
cfg_attrs = [triple_to_cfg_attrs(triple, features, target_features) for triple in triples]
309-
return cfg_matches_expr_for_cfg_attrs(expr, cfg_attrs, features, target_features)
314+
def cfg_matches_expr_for_triples(expr, triples, features=[]):
315+
cfg_attrs = [triple_to_cfg_attrs(triple) for triple in triples]
316+
return cfg_matches_expr_for_cfg_attrs(expr, cfg_attrs, features)
310317

311-
def cfg_matches_expr_for_cfg_attrs(expr, cfg_attrs, features=[], target_features=[]):
318+
def cfg_matches_expr_for_cfg_attrs(expr, cfg_attrs, features=[]):
312319
if expr.startswith("cfg("):
313-
return cfg_matches_ast_for_triples(cfg_parse(expr), cfg_attrs)
314-
# Cargo target table keys that aren't cfg(...) are literal triples.
315-
return [cfg_attr["_triple"] for cfg_attr in cfg_attrs if cfg_attr["_triple"] == expr]
316-
317-
def cfg_matches_ast_for_triples(ast, cfg_attrs):
318-
return [cfg_attr["_triple"] for cfg_attr in cfg_attrs if _cfg_eval(ast, cfg_attr)]
320+
ast, uses_feature_cfg = cfg_parse(expr)
321+
return struct(
322+
matches = cfg_matches_ast_for_triples(ast, cfg_attrs, features),
323+
uses_feature_cfg = uses_feature_cfg,
324+
)
325+
else:
326+
# Cargo target table keys that aren't cfg(...) are literal triples.
327+
return struct(
328+
matches = [cfg_attr["_triple"] for cfg_attr in cfg_attrs if cfg_attr["_triple"] == expr],
329+
uses_feature_cfg = False,
330+
)
331+
332+
def cfg_matches_ast_for_triples(ast, cfg_attrs, features=[]):
333+
return [cfg_attr["_triple"] for cfg_attr in cfg_attrs if _cfg_eval(ast, cfg_attr, features)]

rs/private/cfg_parser_test.bzl

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
2-
load(":cfg_parser.bzl", "cfg_matches", "cfg_matches_expr_for_triples")
2+
load(":cfg_parser.bzl", "cfg_matches", "cfg_matches_expr_for_triples", "triple_to_cfg_attrs", "cfg_matches_expr_for_cfg_attrs")
33

44
def _cfg(expr):
55
return "cfg(%s)" % expr
@@ -48,23 +48,51 @@ def _cfg_parser_smoke_test_impl(ctx):
4848
asserts.true(env, cfg_matches(_cfg("any(true, false)"), mac))
4949
asserts.true(env, cfg_matches(_cfg("all(true)"), mac))
5050
asserts.false(env, cfg_matches(_cfg("all(true, false)"), mac))
51+
asserts.true(env, cfg_matches(_cfg('feature = "serde"'), mac, features = ["serde"]))
52+
asserts.false(env, cfg_matches(_cfg('feature = "serde"'), mac))
53+
asserts.true(env, cfg_matches(_cfg('target_feature = "sse2"'), linux_gnu))
54+
asserts.false(env, cfg_matches(_cfg('target_feature = "sse2"'), mac))
5155

5256
triples = [mac, linux_gnu, linux_musl, win, win_gnu, win_gnullvm]
5357

5458
results = cfg_matches_expr_for_triples(_cfg('all(unix, any(target_env = "gnu", target_env = "musl"))'), triples)
55-
asserts.equals(env, results, [linux_gnu, linux_musl])
59+
asserts.equals(env, results.matches, [linux_gnu, linux_musl])
5660

5761
results = cfg_matches_expr_for_triples(
5862
_cfg('any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86")'),
5963
triples)
60-
asserts.equals(env, results, triples)
64+
asserts.equals(env, results.matches, triples)
6165

6266
# Cargo dependencies can target a specific triple instead of a cfg expression.
6367
results = cfg_matches_expr_for_triples(win_gnullvm, triples)
64-
asserts.equals(env, results, [win_gnullvm])
68+
asserts.equals(env, results.matches, [win_gnullvm])
6569

6670
results = cfg_matches_expr_for_triples(_cfg('all(target_os = "windows", any(target_env = "msvc", target_env = "gnu", target_env = "gnullvm"))'), triples)
67-
asserts.equals(env, results, [win, win_gnu, win_gnullvm])
71+
asserts.equals(env, results.matches, [win, win_gnu, win_gnullvm])
72+
73+
results = cfg_matches_expr_for_triples(_cfg('feature = "serde"'), triples, features = ["serde"])
74+
asserts.equals(env, results.matches, triples)
75+
76+
results = cfg_matches_expr_for_triples(_cfg('feature = "serde"'), triples)
77+
asserts.equals(env, results.matches, [])
78+
79+
info = cfg_matches_expr_for_cfg_attrs(
80+
_cfg('all(feature = "serde", target_feature = "sse2")'),
81+
[triple_to_cfg_attrs(linux_gnu)],
82+
)
83+
asserts.true(env, info.uses_feature_cfg)
84+
asserts.equals(env, info.matches, [])
85+
86+
info = cfg_matches_expr_for_cfg_attrs(win_gnullvm, [triple_to_cfg_attrs(win_gnullvm)])
87+
asserts.false(env, info.uses_feature_cfg)
88+
asserts.equals(env, info.matches, [win_gnullvm])
89+
90+
info = cfg_matches_expr_for_cfg_attrs(
91+
_cfg('target_feature = "sse2"'),
92+
[triple_to_cfg_attrs(linux_gnu)],
93+
)
94+
asserts.false(env, info.uses_feature_cfg)
95+
asserts.equals(env, info.matches, [linux_gnu])
6896

6997
return unittest.end(env)
7098

rs/private/resolver.bzl

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
load("//rs/private:cfg_parser.bzl", "cfg_matches_expr_for_cfg_attrs")
2+
13
def _count(feature_resolutions_by_fq_crate):
24
n = 0
35
for feature_resolutions in feature_resolutions_by_fq_crate.values():
@@ -13,7 +15,22 @@ def _count(feature_resolutions_by_fq_crate):
1315
# No need to count aliases, they only get set when deps are set.
1416
return n
1517

16-
def _resolve_one_round(packages, dirty_package_indices, debug):
18+
def _dep_target_matches_triple(dep, triple, package_feature_set, cfg_attrs_by_triple):
19+
remaining = dep["target"]
20+
if triple not in remaining:
21+
return False
22+
23+
if not dep.get("feature_sensitive", False):
24+
return True
25+
26+
cfg_attr = cfg_attrs_by_triple[triple]
27+
return bool(cfg_matches_expr_for_cfg_attrs(
28+
dep["target_expr"],
29+
[cfg_attr],
30+
features = package_feature_set,
31+
).matches)
32+
33+
def _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, debug):
1734
new_dirty_package_indices = set()
1835

1936
for index in dirty_package_indices:
@@ -31,6 +48,7 @@ def _resolve_one_round(packages, dirty_package_indices, debug):
3148
package,
3249
features_enabled,
3350
feature_resolutions,
51+
cfg_attrs_by_triple,
3452
debug,
3553
):
3654
package_changed = True
@@ -50,9 +68,16 @@ def _resolve_one_round(packages, dirty_package_indices, debug):
5068
prefixed_dep_alias = "dep:" + dep_name
5169
optional = dep.get("optional", False)
5270

53-
to_remove = None
54-
match = dep["target"]
71+
if dep.get("feature_sensitive"):
72+
match = set([
73+
triple
74+
for triple in dep["target"]
75+
if _dep_target_matches_triple(dep, triple, features_enabled[triple], cfg_attrs_by_triple)
76+
])
77+
else:
78+
match = dep["target"]
5579

80+
to_remove = None
5681
for triple in match:
5782
if optional:
5883
features_for_triple = features_enabled[triple]
@@ -96,6 +121,7 @@ def _propagate_feature_enablement(
96121
package,
97122
features_enabled,
98123
feature_resolutions,
124+
cfg_attrs_by_triple,
99125
debug):
100126
possible_features = feature_resolutions.possible_features
101127

@@ -128,7 +154,7 @@ def _propagate_feature_enablement(
128154

129155
found = False
130156
for dep in feature_resolutions.possible_deps:
131-
if dep_name == dep["name"] and triple in dep["target"]:
157+
if dep_name == dep["name"] and _dep_target_matches_triple(dep, triple, feature_set, cfg_attrs_by_triple):
132158
found = True
133159
dep_optional = dep.get("optional", False)
134160
if not optional_marker or not dep_optional or dep_name in feature_set or ("dep:" + dep_name) in feature_set:
@@ -151,13 +177,13 @@ def _propagate_feature_enablement(
151177

152178
_MAX_ROUNDS = 50
153179

154-
def resolve(mctx, packages, feature_resolutions_by_fq_crate, debug):
180+
def resolve(mctx, packages, feature_resolutions_by_fq_crate, cfg_attrs_by_triple, debug):
155181
# Do some rounds of mutual resolution; bail when no more changes
156182
dirty_package_indices = range(len(packages))
157183
for i in range(_MAX_ROUNDS):
158184
mctx.report_progress("Running round %s of dependency/feature resolution" % i)
159185

160-
dirty_package_indices = _resolve_one_round(packages, dirty_package_indices, debug)
186+
dirty_package_indices = _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, debug)
161187
if not dirty_package_indices:
162188
if debug:
163189
count = _count(feature_resolutions_by_fq_crate)

0 commit comments

Comments
 (0)