Skip to content

Commit 5c6938e

Browse files
authored
Cortex-M: build and resolve FVP test runners per target (#20048)
### Summary The Cortex-M op tests run the .pte on a prebuilt semihosting runner, but build_test_runner.sh wrote every target to one shared directory and the ELF was resolved by board (corstone-300) only. A runner built for one target could therefore be used to run another target's program, silently producing wrong results. Build each target's runner into a target-suffixed directory and resolve the ELF by the test's target. The Arm Serialize/runner_utils ELF lookup gains an optional, defaulted build_dir_suffix so existing Arm-backend corstone tests are unaffected; CortexMSerialize passes the target's canonical cortex-m<variant> string. A target whose runner has not been built now fails with a clear FileNotFoundError naming the missing directory instead of running on a mismatched binary. ### Test plan ``` ./backends/cortex_m/test/build_test_runner.sh --target=cortex-m0plus ./backends/cortex_m/test/build_test_runner.sh --target=cortex-m7 ``` Authored with Claude Code. cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell
1 parent 5af1d7b commit 5c6938e

6 files changed

Lines changed: 46 additions & 15 deletions

File tree

backends/arm/test/runner_utils.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ def _elf_search_roots() -> list[Path]:
892892

893893

894894
def _elf_path_candidates(
895-
target_board: str, use_portable_ops: bool = False
895+
target_board: str, use_portable_ops: bool = False, build_dir_suffix: str = ""
896896
) -> list[Path]:
897897
if target_board not in VALID_TARGET:
898898
raise ValueError(f"Unsupported target: {target_board}")
@@ -901,11 +901,14 @@ def _elf_path_candidates(
901901
if target_board in ("corstone-300", "corstone-320"):
902902
build_dir = Path(
903903
"arm_test",
904-
f"arm_semihosting_executor_runner_{portable_ops_str}{target_board}",
904+
f"arm_semihosting_executor_runner_"
905+
f"{portable_ops_str}{target_board}{build_dir_suffix}",
905906
)
906907
binary_name = "arm_executor_runner"
907908
else:
908-
build_dir = Path("arm_test", f"arm_executor_runner_{portable_ops_str}vkml")
909+
build_dir = Path(
910+
"arm_test", f"arm_executor_runner_{portable_ops_str}vkml{build_dir_suffix}"
911+
)
909912
binary_name = "executor_runner"
910913

911914
candidates: list[Path] = []
@@ -950,9 +953,15 @@ def _resolve_existing_elf_path(elf_candidates: Iterable[Path]) -> Path:
950953
)
951954

952955

953-
def get_elf_path(target_board: str, use_portable_ops: bool = False) -> str:
956+
def get_elf_path(
957+
target_board: str, use_portable_ops: bool = False, build_dir_suffix: str = ""
958+
) -> str:
954959
elf_path = _resolve_existing_elf_path(
955-
_elf_path_candidates(target_board, use_portable_ops=use_portable_ops)
960+
_elf_path_candidates(
961+
target_board,
962+
use_portable_ops=use_portable_ops,
963+
build_dir_suffix=build_dir_suffix,
964+
)
956965
)
957966
return str(elf_path)
958967

backends/arm/test/tester/serialize.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,25 @@ def __init__(
3434
module: Optional[torch.nn.Module],
3535
use_portable_ops: bool = False,
3636
timeout: int = 120,
37+
build_dir_suffix: str = "",
3738
):
3839
"""
3940
Args:
4041
compile_spec: CompileSpecs to be used for serialization.
4142
module: Original Module to be used for serialization. Optional - can be used for reference output generation.
4243
portable_ops: If True tests with compiled in portable ops, default is to test without this to get error if not fully delegated
4344
timeout: Timeout for fvp. Default is 120 seconds.
45+
build_dir_suffix: Suffix appended to the executor-runner build dir
46+
name when resolving the ELF, letting callers select a runner
47+
built for a specific target (e.g. a Cortex-M variant).
4448
"""
4549
super().__init__()
4650
self.module = module
4751
self.timeout = timeout
4852
self.executorch_program_manager: ExecutorchProgramManager | None
4953
self.compile_spec = compile_spec
5054
self.use_portable_ops = use_portable_ops
55+
self.build_dir_suffix = build_dir_suffix
5156

5257
def run(self, artifact: ExecutorchProgramManager, inputs=None) -> None:
5358
super().run(artifact, inputs)
@@ -62,7 +67,9 @@ def run_artifact(self, inputs):
6267
inputs_flattened, _ = tree_flatten(inputs)
6368
intermediate_path = self.compile_spec._get_intermediate_path()
6469
target_board = get_target_board(self.compile_spec)
65-
elf_path = get_elf_path(target_board, self.use_portable_ops)
70+
elf_path = get_elf_path(
71+
target_board, self.use_portable_ops, build_dir_suffix=self.build_dir_suffix
72+
)
6673

6774
if not os.path.exists(elf_path):
6875
raise FileNotFoundError(

backends/cortex_m/target_config.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ def __post_init__(self) -> None:
7878
f"{self.cpu.name}; supported: {allowed}"
7979
)
8080

81+
@property
82+
def target_string(self) -> str:
83+
"""Canonical ``cortex-m<variant>`` string; inverse of
84+
``from_target_string``."""
85+
return "cortex-m" + self.cpu.name[1:].lower()
86+
8187
@property
8288
def backend(self) -> cmsis_nn.Backend:
8389
if self.isa is not None:
@@ -105,6 +111,6 @@ def from_target_string(cls, target: str) -> CortexMTargetConfig:
105111
except KeyError as e:
106112
raise ValueError(
107113
f"Unsupported Cortex-M target string: {target!r}. "
108-
f"Supported: {sorted('cortex-m' + m.name[1:].lower() for m in CortexM)}"
114+
f"Supported: {sorted(cls(cpu=m).target_string for m in CortexM)}"
109115
) from e
110116
return cls(cpu=cpu)

backends/cortex_m/test/build_test_runner.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ ${build_executorch} --devtools --target_cpu="${target_cpu}" --cmake-args="-DCORT
3333
# Build executor runner with selected aten ops and semi hosting
3434
build_dir="${et_root_dir}/arm_test"
3535
build_executor_runner="${et_root_dir}/backends/arm/scripts/build_executor_runner.sh"
36-
build_root_test_dir="${et_root_dir}/arm_test/arm_semihosting_executor_runner_corstone-300"
36+
build_root_test_dir="${et_root_dir}/arm_test/arm_semihosting_executor_runner_corstone-300_${target}"
3737

3838
select_ops_list="\
3939
aten::add.out,\

backends/cortex_m/test/tester.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,22 @@ def __init__(self, target_config: Optional[CortexMTargetConfig] = None):
6969

7070

7171
class CortexMSerialize(Serialize):
72-
def __init__(self):
72+
def __init__(self, target_config: Optional[CortexMTargetConfig] = None):
73+
target_config = target_config or CortexMTargetConfig(cpu=CortexM.M55)
7374
compile_spec = get_u55_compile_spec()
74-
super().__init__(compile_spec, 1024)
75+
# Select the runner built for this target (build_test_runner.sh writes
76+
# one runner per target into a target-suffixed directory).
77+
super().__init__(
78+
compile_spec,
79+
None,
80+
build_dir_suffix=f"_{target_config.target_string}",
81+
)
7582

7683

7784
cortex_m_stage_classes = {
7885
StageType.EXPORT: Export,
7986
StageType.QUANTIZE: CortexMQuantize,
8087
StageType.RUN_PASSES: CortexMRunPasses,
81-
StageType.SERIALIZE: Serialize,
8288
StageType.TO_EDGE: CortexMToEdge,
8389
StageType.TO_EXECUTORCH: ToExecutorch,
8490
StageType.SERIALIZE: CortexMSerialize,
@@ -103,6 +109,9 @@ def __init__(
103109
stage_classes[StageType.RUN_PASSES] = lambda: CortexMRunPasses(
104110
target_config=target_config
105111
)
112+
stage_classes[StageType.SERIALIZE] = lambda: CortexMSerialize(
113+
target_config=target_config
114+
)
106115
super().__init__(module, resolved_example_inputs, stage_classes)
107116

108117
def test_dialect(

examples/arm/run.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -769,16 +769,16 @@ for i in "${!test_model[@]}"; do
769769
echo "Build for ${target} skip generating a .elf and running it"
770770
continue
771771
elif [[ ${target} == cortex-m* ]]; then
772-
# Cortex-M backend uses a shared semihosting executor_runner (built
773-
# by build_test_runner.sh) that loads the .bpte at runtime, rather
774-
# than per-model runners with the PTE baked in.
772+
# Cortex-M backend uses a semihosting executor_runner (built by
773+
# build_test_runner.sh, one per target) that loads the .bpte at
774+
# runtime, rather than per-model runners with the PTE baked in.
775775
if [ "$bundleio" != true ]; then
776776
echo "Error: --target=${target} requires --bundleio (the cortex-m runner loads bundled inputs via semihosting)"
777777
exit 1
778778
fi
779779
set -x
780780
backends/cortex_m/test/build_test_runner.sh --target="${target}"
781-
cortex_m_elf="${et_root_dir}/arm_test/arm_semihosting_executor_runner_corstone-300/arm_executor_runner"
781+
cortex_m_elf="${et_root_dir}/arm_test/arm_semihosting_executor_runner_corstone-300_${target}/arm_executor_runner"
782782
if [ "$build_only" = false ] ; then
783783
backends/arm/scripts/run_fvp.sh --elf="${cortex_m_elf}" --target="${target}" --bundle="${pte_file}"
784784
fi

0 commit comments

Comments
 (0)