|
1 | 1 | # Copyright (c) Meta Platforms, Inc. and affiliates. |
2 | 2 | # All rights reserved. |
| 3 | +# Copyright 2026 Arm Limited and/or its affiliates. |
3 | 4 | # |
4 | 5 | # This source code is licensed under the BSD-style license found in the |
5 | 6 | # LICENSE file in the root directory of this source tree. |
6 | 7 |
|
7 | 8 | from __future__ import annotations |
8 | 9 |
|
9 | 10 | import os |
| 11 | +import sys |
10 | 12 | import tempfile |
11 | 13 | import unittest |
12 | 14 |
|
| 15 | +from argparse import Namespace |
| 16 | +from pathlib import Path |
| 17 | + |
| 18 | +import executorch.codegen.tools.gen_oplist as gen_oplist |
| 19 | + |
13 | 20 | import yaml |
14 | 21 | from executorch.codegen.gen import ( |
15 | 22 | ComputeCodegenUnboxedKernels, |
16 | 23 | gen_functions_declarations, |
| 24 | + gen_unboxing, |
17 | 25 | parse_yaml_files, |
18 | 26 | translate_native_yaml, |
19 | 27 | ) |
20 | 28 |
|
21 | | -from executorch.codegen.model import ETKernelIndex, ETKernelKey |
22 | | -from torchgen.gen import LineLoader |
| 29 | +from executorch.codegen.model import ETKernelIndex, ETKernelKey, ETParsedYaml |
| 30 | +from torchgen.gen import get_custom_build_selector, LineLoader, make_file_manager |
23 | 31 | from torchgen.model import ( |
24 | 32 | BackendIndex, |
25 | 33 | BackendMetadata, |
@@ -455,6 +463,149 @@ def test_aten_lib_method_variant(self) -> None: |
455 | 463 | ) |
456 | 464 |
|
457 | 465 |
|
| 466 | +class TestGenUnboxing(unittest.TestCase): |
| 467 | + EXPECTED_EMPTY_SELECTION_CPP = [ |
| 468 | + """ |
| 469 | +#include "NativeFunctions.h" // Generated Function import headers |
| 470 | +""", |
| 471 | + """ |
| 472 | +static Kernel kernels_to_register[] = { |
| 473 | + // Generated kernels |
| 474 | +}; |
| 475 | +""", |
| 476 | + ] |
| 477 | + |
| 478 | + def _write_codegen_inputs(self, temp_dir: str) -> tuple[str, str, str]: |
| 479 | + aten_yaml_path = os.path.join(temp_dir, "native_functions.yaml") |
| 480 | + with open(aten_yaml_path, "w") as f: |
| 481 | + f.write(TEST_YAML) |
| 482 | + included_ops_yaml_path = os.path.join(temp_dir, "included_ops.yaml") |
| 483 | + with open(included_ops_yaml_path, "w") as f: |
| 484 | + f.write( |
| 485 | + """ |
| 486 | +- op: add.out |
| 487 | + dispatch: |
| 488 | + CPU: torch::executor::add_out_kernel |
| 489 | +
|
| 490 | +- op: mul.out |
| 491 | + dispatch: |
| 492 | + CPU: torch::executor::mul_out_kernel |
| 493 | +""" |
| 494 | + ) |
| 495 | + tags_yaml_path = os.path.join(temp_dir, "tags.yaml") |
| 496 | + with open(tags_yaml_path, "w") as f: |
| 497 | + f.write( |
| 498 | + """ |
| 499 | +- tag: core |
| 500 | + desc: test |
| 501 | +""" |
| 502 | + ) |
| 503 | + return aten_yaml_path, included_ops_yaml_path, tags_yaml_path |
| 504 | + |
| 505 | + def _assert_selection_generates_empty_buildable_cpp( |
| 506 | + self, |
| 507 | + temp_dir: str, |
| 508 | + selector: SelectiveBuilder, |
| 509 | + parsed_yaml: ETParsedYaml, |
| 510 | + ) -> None: |
| 511 | + self.assertEqual([], parsed_yaml.native_functions) |
| 512 | + main_module = sys.modules["__main__"] |
| 513 | + old_main_file = getattr(main_module, "__file__", None) |
| 514 | + main_module.__file__ = __file__ |
| 515 | + try: |
| 516 | + options = Namespace( |
| 517 | + source_path=str(Path(__file__).parents[1]), |
| 518 | + install_dir=temp_dir, |
| 519 | + dry_run=False, |
| 520 | + ) |
| 521 | + gen_unboxing( |
| 522 | + native_functions=parsed_yaml.native_functions, |
| 523 | + cpu_fm=make_file_manager(options=options), |
| 524 | + selector=selector, |
| 525 | + use_aten_lib=False, |
| 526 | + kernel_index=parsed_yaml.kernel_index, |
| 527 | + manual_registration=False, |
| 528 | + ) |
| 529 | + |
| 530 | + actual_cpp = ( |
| 531 | + Path(temp_dir) / "RegisterCodegenUnboxedKernelsEverything.cpp" |
| 532 | + ).read_text() |
| 533 | + finally: |
| 534 | + if old_main_file is None: |
| 535 | + delattr(main_module, "__file__") |
| 536 | + else: |
| 537 | + main_module.__file__ = old_main_file |
| 538 | + |
| 539 | + for expected in self.EXPECTED_EMPTY_SELECTION_CPP: |
| 540 | + self.assertIn(expected, actual_cpp) |
| 541 | + self.assertNotIn("${", actual_cpp) |
| 542 | + |
| 543 | + def test_empty_operator_selection_generates_buildable_cpp(self) -> None: |
| 544 | + with tempfile.TemporaryDirectory() as temp_dir: |
| 545 | + ( |
| 546 | + aten_yaml_path, |
| 547 | + included_ops_yaml_path, |
| 548 | + tags_yaml_path, |
| 549 | + ) = self._write_codegen_inputs(temp_dir) |
| 550 | + selected_ops_yaml_path = os.path.join(temp_dir, "selected_operators.yaml") |
| 551 | + gen_oplist.main([f"--output_path={selected_ops_yaml_path}"]) |
| 552 | + selector = get_custom_build_selector(None, selected_ops_yaml_path) |
| 553 | + parsed_yaml, _ = parse_yaml_files( |
| 554 | + aten_yaml_path=aten_yaml_path, |
| 555 | + tags_yaml_path=tags_yaml_path, |
| 556 | + native_yaml_path=included_ops_yaml_path, |
| 557 | + custom_ops_yaml_path=None, |
| 558 | + selector=selector, |
| 559 | + use_aten_lib=False, |
| 560 | + ) |
| 561 | + |
| 562 | + self._assert_selection_generates_empty_buildable_cpp( |
| 563 | + temp_dir, selector, parsed_yaml |
| 564 | + ) |
| 565 | + |
| 566 | + def test_operator_selection_from_missing_library_generates_buildable_cpp( |
| 567 | + self, |
| 568 | + ) -> None: |
| 569 | + with tempfile.TemporaryDirectory() as temp_dir: |
| 570 | + ( |
| 571 | + aten_yaml_path, |
| 572 | + included_ops_yaml_path, |
| 573 | + tags_yaml_path, |
| 574 | + ) = self._write_codegen_inputs(temp_dir) |
| 575 | + selector = SelectiveBuilder.from_yaml_dict( |
| 576 | + { |
| 577 | + "operators": { |
| 578 | + "not_included::op.out": { |
| 579 | + "is_root_operator": True, |
| 580 | + "is_used_for_training": True, |
| 581 | + "include_all_overloads": False, |
| 582 | + "debug_info": [], |
| 583 | + }, |
| 584 | + }, |
| 585 | + "custom_classes": [], |
| 586 | + "build_features": [], |
| 587 | + "include_all_non_op_selectives": False, |
| 588 | + "include_all_operators": False, |
| 589 | + "kernel_metadata": {}, |
| 590 | + "et_kernel_metadata": { |
| 591 | + "not_included::op.out": ["default"], |
| 592 | + }, |
| 593 | + } |
| 594 | + ) |
| 595 | + parsed_yaml, _ = parse_yaml_files( |
| 596 | + aten_yaml_path=aten_yaml_path, |
| 597 | + tags_yaml_path=tags_yaml_path, |
| 598 | + native_yaml_path=included_ops_yaml_path, |
| 599 | + custom_ops_yaml_path=None, |
| 600 | + selector=selector, |
| 601 | + use_aten_lib=False, |
| 602 | + ) |
| 603 | + |
| 604 | + self._assert_selection_generates_empty_buildable_cpp( |
| 605 | + temp_dir, selector, parsed_yaml |
| 606 | + ) |
| 607 | + |
| 608 | + |
458 | 609 | class TestComputeCodegenUnboxedKernels(unittest.TestCase): |
459 | 610 | def setUp(self) -> None: |
460 | 611 | ( |
|
0 commit comments