1# This file is licensed under the Apache License v2.0 with LLVM Exceptions. 2# See https://llvm.org/LICENSE.txt for license information. 3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 4"""BUILD extensions for MLIR table generation.""" 5 6load("@bazel_skylib//lib:paths.bzl", "paths") 7 8TdInfo = provider( 9 "Holds TableGen files and the dependencies and include paths necessary to" + 10 " build them.", 11 fields = { 12 "transitive_sources": "td files transitively used by this rule.", 13 "transitive_includes": ( 14 "include arguments to add to the final TableGen invocation. These" + 15 " are the absolute directory paths that will be added with '-I'." 16 ), 17 }, 18) 19 20# For now we allow anything that provides DefaultInfo to just forward its files. 21# In particular, this allows filegroups to be used. This is mostly to ease 22# transition. In the future, the TdInfo provider will be required. 23# TODO(gcmn): Switch to enforcing TdInfo provider. 24def _get_dep_transitive_srcs(dep): 25 """Extract TdInfo.transitive_sources, falling back to DefaultInfo.files.""" 26 if TdInfo in dep: 27 return dep[TdInfo].transitive_sources 28 return dep[DefaultInfo].files 29 30def _get_dep_transitive_includes(dep): 31 """Extract TdInfo.transitive_includes, falling back to an empty depset().""" 32 if TdInfo in dep: 33 return dep[TdInfo].transitive_includes 34 return depset() 35 36def _get_transitive_srcs(srcs, deps): 37 """Obtain the source files for a target and its transitive dependencies. 38 39 Args: 40 srcs: a list of source files 41 deps: a list of targets that are direct dependencies 42 Returns: 43 a collection of the transitive sources 44 """ 45 return depset( 46 direct = srcs, 47 transitive = [_get_dep_transitive_srcs(dep) for dep in deps], 48 ) 49 50def _get_transitive_includes(includes, deps): 51 """Obtain the includes paths for a target and its transitive dependencies. 52 53 Args: 54 includes: a list of include paths 55 deps: a list of targets that are direct dependencies 56 Returns: 57 a collection of the transitive include paths 58 """ 59 return depset( 60 direct = includes, 61 transitive = [_get_dep_transitive_includes(dep) for dep in deps], 62 ) 63 64def _prefix_roots(ctx, includes): 65 """Map the given includes to be relative to all root directories. 66 67 This will expand them to be relative to all the root directories available 68 in the execution environment for ctx.run (bin and genfiles in addition to 69 the normal source root) 70 """ 71 prefixed_includes = [] 72 for include in includes: 73 prefixed_includes.append(include) 74 prefixed_includes.append(paths.join(ctx.genfiles_dir.path, include)) 75 prefixed_includes.append(paths.join(ctx.bin_dir.path, include)) 76 return prefixed_includes 77 78def _resolve_includes(ctx, includes): 79 """Resolves include paths to paths relative to the execution root. 80 81 Relative paths are interpreted as relative to the current label's package. 82 Absolute paths are interpreted as relative to the current label's workspace 83 root.""" 84 package = ctx.label.package 85 workspace_root = ctx.label.workspace_root 86 workspace_root = workspace_root if workspace_root else "." 87 resolved_includes = [] 88 for include in includes: 89 if paths.is_absolute(include): 90 include = include.lstrip("/") 91 else: 92 include = paths.join(package, include) 93 include = paths.join(workspace_root, include) 94 resolved_includes.extend(_prefix_roots(ctx, [include])) 95 return resolved_includes 96 97def _td_library_impl(ctx): 98 trans_srcs = _get_transitive_srcs(ctx.files.srcs, ctx.attr.deps) 99 trans_includes = _get_transitive_includes( 100 _resolve_includes(ctx, ctx.attr.includes), 101 ctx.attr.deps, 102 ) 103 return [ 104 DefaultInfo(files = trans_srcs), 105 TdInfo( 106 transitive_sources = trans_srcs, 107 transitive_includes = trans_includes, 108 ), 109 ] 110 111td_library = rule( 112 _td_library_impl, 113 attrs = { 114 "srcs": attr.label_list(allow_files = True), 115 "includes": attr.string_list( 116 doc = "Include paths to be added to the final TableGen tool" + 117 " invocation. Relative paths are interpreted as relative to" + 118 " the current label's package. Absolute paths are" + 119 " interpreted as relative to the current label's workspace", 120 ), 121 # TODO(gcmn): limit to TdInfo providers. 122 "deps": attr.label_list( 123 doc = "Dependencies providing TableGen source files and include" + 124 " paths.", 125 ), 126 }, 127) 128 129def _gentbl_rule_impl(ctx): 130 td_file = ctx.file.td_file 131 132 trans_srcs = _get_transitive_srcs( 133 ctx.files.td_srcs + [td_file], 134 ctx.attr.deps, 135 ) 136 137 # Note that we have two types of includes here. The deprecated ones expanded 138 # only by "_prefix_roots" are already relative to the execution root, i.e. 139 # may contain an `external/<workspace_name>` prefix if the current workspace 140 # is not the main workspace (where workspace_name is something configured 141 # per-project and therefore generally not known). Note that dirname also 142 # already includes this prefix. The new style of includes have it prepended 143 # automatically by `_resolve_includes` to avoid BUILD files having to depend 144 # on project specific configurations and Bazel implementation details. 145 trans_includes = _get_transitive_includes( 146 _resolve_includes(ctx, ctx.attr.includes + ["/"]) + 147 _prefix_roots(ctx, ctx.attr.td_includes + [td_file.dirname]), 148 ctx.attr.deps, 149 ) 150 151 args = ctx.actions.args() 152 args.add_all(ctx.attr.opts) 153 args.add(td_file) 154 args.add_all(trans_includes, before_each = "-I") 155 156 args.add("-o", ctx.outputs.out.path) 157 158 ctx.actions.run( 159 outputs = [ctx.outputs.out], 160 inputs = trans_srcs, 161 executable = ctx.executable.tblgen, 162 arguments = [args], 163 mnemonic = "TdGenerate", 164 ) 165 166 return [DefaultInfo()] 167 168gentbl_rule = rule( 169 _gentbl_rule_impl, 170 doc = "Generates tabular code from a table definition file.", 171 # Match genrule behavior 172 output_to_genfiles = True, 173 attrs = { 174 "tblgen": attr.label( 175 doc = "The TableGen executable with which to generate `out`.", 176 executable = True, 177 cfg = "exec", 178 ), 179 "td_file": attr.label( 180 doc = "The TableGen file to run through `tblgen`.", 181 allow_single_file = True, 182 mandatory = True, 183 ), 184 "td_srcs": attr.label_list( 185 doc = "Additional TableGen files included by `td_file`. It is not" + 186 " necessary to list td_file here (though not an error).", 187 allow_files = True, 188 ), 189 # TODO(gcmn): limit to TdInfo providers. 190 "deps": attr.label_list( 191 doc = "Dependencies providing TableGen source files and include" + 192 " paths.", 193 ), 194 "out": attr.output( 195 doc = "The output file for the TableGen invocation.", 196 mandatory = True, 197 ), 198 "opts": attr.string_list( 199 doc = "Additional command line options to add to the TableGen" + 200 " invocation. For include arguments, prefer to use" + 201 " `includes`.", 202 ), 203 "includes": attr.string_list( 204 doc = "Include paths to be added to the final TableGen tool" + 205 " invocation. Relative paths are interpreted as relative to" + 206 " the current label's package. Absolute paths are" + 207 " interpreted as relative to the current label's workspace." + 208 " Includes are applied from all roots available in the" + 209 " execution environment (source, genfiles, and bin" + 210 " directories). The execution roots themselves and the " + 211 " directory of td_file are always added.", 212 ), 213 "td_includes": attr.string_list( 214 doc = "Include paths to add to the TableGen invocation. Paths are" + 215 " interpreted as relative to the current label's workspace" + 216 " root and applied from all roots available in the" + 217 " execution environment (source, genfiles, and bin" + 218 " directories). Deprecated. Use `includes` instead.", 219 ), 220 }, 221) 222 223# TODO(gcmn): Figure out how to reduce duplication with _gentbl_rule_impl 224def _gentbl_test_impl(ctx): 225 td_file = ctx.file.td_file 226 227 trans_srcs = _get_transitive_srcs( 228 ctx.files.td_srcs + [td_file], 229 ctx.attr.deps, 230 ) 231 232 # Note that we have two types of includes here. The deprecated ones expanded 233 # only by "_prefix_roots" are already relative to the execution root, i.e. 234 # may contain an `external/<workspace_name>` prefix if the current workspace 235 # is not the main workspace (where workspace_name is something configured 236 # per-project and therefore generally not known). Note that dirname also 237 # already includes this prefix. The new style of includes have it prepended 238 # automatically by `_resolve_includes` to avoid BUILD files having to depend 239 # on project specific configurations and Bazel implementation details. 240 trans_includes = _get_transitive_includes( 241 _resolve_includes(ctx, ctx.attr.includes + ["/"]) + 242 _prefix_roots(ctx, ctx.attr.td_includes + [td_file.dirname]), 243 ctx.attr.deps, 244 ) 245 246 test_args = [ctx.executable.tblgen.short_path] 247 test_args.extend(ctx.attr.opts) 248 test_args.append(td_file.path) 249 test_args.extend(["-I " + include for include in trans_includes.to_list()]) 250 251 test_args.extend(["-o", "/dev/null"]) 252 253 ctx.actions.write( 254 ctx.outputs.executable, 255 content = " ".join(test_args), 256 is_executable = True, 257 ) 258 259 return [ 260 coverage_common.instrumented_files_info( 261 ctx, 262 source_attributes = ["td_file", "td_srcs"], 263 dependency_attributes = ["tblgen", "deps"], 264 ), 265 DefaultInfo( 266 runfiles = ctx.runfiles( 267 [ctx.executable.tblgen], 268 transitive_files = trans_srcs, 269 ), 270 ), 271 ] 272 273gentbl_test = rule( 274 _gentbl_test_impl, 275 test = True, 276 doc = "A shell test that tests the given TablegGen invocation. Note" + 277 " that unlike gentbl_rule, this builds and invokes `tblgen` in the" + 278 " target configuration. Takes all the same arguments as gentbl_rule" + 279 " except for `out` (as it does not generate any output)", 280 attrs = { 281 "tblgen": attr.label( 282 doc = "The TableGen executable run in the shell command. Note" + 283 " that this is built in the target configuration.", 284 executable = True, 285 cfg = "target", 286 ), 287 "td_file": attr.label( 288 doc = "See gentbl_rule.td_file", 289 allow_single_file = True, 290 mandatory = True, 291 ), 292 "td_srcs": attr.label_list( 293 doc = "See gentbl_rule.td_srcs", 294 allow_files = True, 295 ), 296 "deps": attr.label_list(doc = "See gentbl_rule.deps"), 297 "opts": attr.string_list(doc = "See gentbl_rule.opts"), 298 "includes": attr.string_list(doc = "See gentbl_rule.includes"), 299 "td_includes": attr.string_list(doc = "See gentbl_rule.td_includes"), 300 }, 301) 302 303def gentbl_filegroup( 304 name, 305 tblgen, 306 td_file, 307 tbl_outs, 308 td_srcs = [], 309 td_includes = [], 310 includes = [], 311 deps = [], 312 test = False, 313 skip_opts = [], 314 **kwargs): 315 """Create multiple TableGen generated files using the same tool and input. 316 317 All generated outputs are bundled in a file group with the given name. 318 319 Args: 320 name: The name of the generated filegroup rule for use in dependencies. 321 tblgen: The binary used to produce the output. 322 td_file: The primary table definitions file. 323 tbl_outs: A list of tuples ([opts], out), where each 'opts' is a list of 324 options passed to tblgen, each option being a string, and 'out' is the 325 corresponding output file produced. 326 td_srcs: See gentbl_rule.td_srcs 327 includes: See gentbl_rule.includes 328 td_includes: See gentbl_rule.td_includes 329 deps: See gentbl_rule.deps 330 test: Whether to create a shell test that invokes the tool too. 331 skip_opts: Files generated using these opts in tbl_outs will be excluded 332 from the generated filegroup. 333 **kwargs: Extra keyword arguments to pass to all generated rules. 334 """ 335 336 llvm_project_execroot_path = Label("//mlir:tblgen.bzl").workspace_root 337 338 # TODO(gcmn): Update callers to td_library and explicit includes and drop 339 # this hardcoded include. 340 hardcoded_includes = [ 341 paths.join(llvm_project_execroot_path, "mlir/include"), 342 ] 343 344 for (opts, out) in tbl_outs: 345 first_opt = opts[0] if opts else "" 346 rule_suffix = "_{}_{}".format( 347 first_opt.replace("-", "_").replace("=", "_"), 348 str(hash(" ".join(opts))), 349 ) 350 gentbl_name = "%s_%s_genrule" % (name, rule_suffix) 351 gentbl_rule( 352 name = gentbl_name, 353 td_file = td_file, 354 tblgen = tblgen, 355 opts = opts, 356 td_srcs = td_srcs, 357 deps = deps, 358 includes = includes, 359 td_includes = td_includes + hardcoded_includes, 360 out = out, 361 **kwargs 362 ) 363 364 if test: 365 # Also run the generator in the target configuration as a test. This 366 # means it gets run with asserts and sanitizers and such when they 367 # are enabled and is counted in coverage. 368 gentbl_test( 369 name = "%s_test" % (gentbl_name,), 370 td_file = td_file, 371 tblgen = tblgen, 372 opts = opts, 373 td_srcs = td_srcs, 374 deps = deps, 375 includes = includes, 376 td_includes = td_includes + hardcoded_includes, 377 # Shell files not executable on Windows. 378 # TODO(gcmn): Support windows. 379 tags = ["no_windows"], 380 **kwargs 381 ) 382 383 included_srcs = [f for (opts, f) in tbl_outs if not any([skip_opt in opts for skip_opt in skip_opts])] 384 native.filegroup( 385 name = name, 386 srcs = included_srcs, 387 **kwargs 388 ) 389 390def gentbl_cc_library( 391 name, 392 tblgen, 393 td_file, 394 tbl_outs, 395 td_srcs = [], 396 td_includes = [], 397 includes = [], 398 deps = [], 399 strip_include_prefix = None, 400 test = False, 401 **kwargs): 402 """Create multiple TableGen generated files using the same tool and input. 403 404 All generated outputs are bundled in a cc_library rule. 405 406 Args: 407 name: The name of the generated cc_library rule for use in dependencies. 408 tblgen: The binary used to produce the output. 409 td_file: The primary table definitions file. 410 tbl_outs: A list of tuples ([opts], out), where each 'opts' is a list of 411 options passed to tblgen, each option being a string, and 'out' is the 412 corresponding output file produced. 413 td_srcs: See gentbl_rule.td_srcs 414 includes: See gentbl_rule.includes 415 td_includes: See gentbl_rule.td_includes 416 deps: See gentbl_rule.deps 417 strip_include_prefix: attribute to pass through to cc_library. 418 test: whether to create a shell test that invokes the tool too. 419 **kwargs: Extra keyword arguments to pass to all generated rules. 420 """ 421 422 filegroup_name = name + "_filegroup" 423 gentbl_filegroup( 424 name = filegroup_name, 425 tblgen = tblgen, 426 td_file = td_file, 427 tbl_outs = tbl_outs, 428 td_srcs = td_srcs, 429 td_includes = td_includes, 430 includes = includes, 431 deps = deps, 432 test = test, 433 skip_opts = ["-gen-op-doc"], 434 **kwargs 435 ) 436 native.cc_library( 437 name = name, 438 # strip_include_prefix does not apply to textual_hdrs. 439 # https://github.com/bazelbuild/bazel/issues/12424 440 hdrs = [":" + filegroup_name] if strip_include_prefix else [], 441 strip_include_prefix = strip_include_prefix, 442 textual_hdrs = [":" + filegroup_name], 443 **kwargs 444 ) 445