From 51b32dc228b71247d70ee8710af2d169c4e6c071 Mon Sep 17 00:00:00 2001 From: Yinan Xu Date: Sat, 9 May 2026 15:19:43 +0800 Subject: [PATCH] feat(spec2026): add CPU2026 workload support Add a SPEC CPU2026 workload integration under workloads/linux/spec2026 that builds rate and speed benchmarks from an external SPEC tree with runcpu, stages the selected inputs into Linux workload images, and exports firmware, ELF, rootfs, run-script, cfg, gcpt, log, and stamp artifacts under build/images/spec2026rate and build/images/spec2026speed. The helper scripts keep SPEC build output inside this repository, add prepare and ELF-only make targets, support per-case DTB memory profiles and resumable shared builds, and document the export layout in README. --- dts/xiangshan-fpga-noAIA-mem64g.dts.in | 174 +++++ dts/xiangshan-fpga-noAIA-mem8g.dts.in | 3 +- scripts/build-firmware-linux.sh | 3 +- workloads/linux/spec2026/README.md | 51 ++ workloads/linux/spec2026/build.sh | 49 ++ .../linux/spec2026/prepare-spec-workspace.sh | 56 ++ workloads/linux/spec2026/riscv_gcc15_base.cfg | 107 +++ workloads/linux/spec2026/rules.mk | 220 ++++++ workloads/linux/spec2026/spec2026-package.py | 657 ++++++++++++++++++ 9 files changed, 1317 insertions(+), 3 deletions(-) create mode 100644 dts/xiangshan-fpga-noAIA-mem64g.dts.in create mode 100644 workloads/linux/spec2026/README.md create mode 100644 workloads/linux/spec2026/build.sh create mode 100644 workloads/linux/spec2026/prepare-spec-workspace.sh create mode 100644 workloads/linux/spec2026/riscv_gcc15_base.cfg create mode 100644 workloads/linux/spec2026/rules.mk create mode 100644 workloads/linux/spec2026/spec2026-package.py diff --git a/dts/xiangshan-fpga-noAIA-mem64g.dts.in b/dts/xiangshan-fpga-noAIA-mem64g.dts.in new file mode 100644 index 0000000..5236aec --- /dev/null +++ b/dts/xiangshan-fpga-noAIA-mem64g.dts.in @@ -0,0 +1,174 @@ +/* + * XiangShan FPGA DTS used by the fpga board flow. + * + * 1. AIA is disabled, so Linux should only see the legacy CLINT + PLIC path. + * 2. UART16550 is exposed as the serial console. + */ + +/dts-v1/; + +/ { + compatible = "freechips,rocketchip-unknown-dev"; + model = "xiangshan,Kunminghu-dev"; + #address-cells = <2>; + #size-cells = <2>; + cpus { + #address-cells = <1>; + #size-cells = <0>; + timebase-frequency = <1000000>; + + cpu0: cpu@0 { + compatible = "ICT,xiangshan", "riscv"; + device_type = "cpu"; + reg = <0x0>; + status = "okay"; + + d-cache-block-size = <64>; + d-cache-sets = <256>; + d-cache-size = <65536>; + d-tlb-sets = <1>; + d-tlb-size = <48>; + i-cache-block-size = <64>; + i-cache-sets = <256>; + i-cache-size = <65536>; + i-tlb-sets = <1>; + i-tlb-size = <48>; + mmu-type = "riscv,sv48"; + clock-frequency = <0>; + timebase-frequency = <1000000>; + tlb-split; + + /* + * Keep the Linux-friendly CPU capability description from the + * workload template while using the FPGA board interrupt/peripheral + * map below. + */ + riscv,isa = "rv64imafdcvh_smstateen_sscofpmf_sstc_zicntr_zihpm_svpbmt_sdtrig_smcsrind_sscsrind_svade"; + riscv,isa-base = "rv64i"; + riscv,isa-extensions = + "i", "m", "a", "f", "d", "c", "v", "h", + "sdtrig", "sha", "shcounterenw", "shgatpa", + "shlcofideleg", "shtvala", "shvsatpa", "shvstvala", + "shvstvecd", "smcsrind", "smdbltrp", + "smmpm", "smnpm", "smrnmi", "smstateen", + "ss1p13", "ssccptr", "sscofpmf", + "sscounterenw", "sscsrind", "ssdbltrp", "ssnpm", + "sspm", "ssstateen", "ssstrict", "sstc", + "sstvala", "sstvecd", "ssu64xl", "supm", + "sv39", "sv48", "svade", "svbare", "svinval", + "svnapot", "svpbmt", "za64rs", "zacas", "zawrs", + "zba", "zbb", "zbc", "zbkb", "zbkc", "zbkx", + "zbs", "zcb", "zcmop", "zfa", "zfh", "zfhmin", + "zic64b", + "ziccamoa", "ziccif", "zicclsm", "ziccrse", + "zicntr", "zicond", "zicsr", "zifencei", + "zihintntl", "zihintpause", "zihpm", "zimop", + "zkn", "zknd", "zkne", "zknh", "zksed", + "zksh", "zkt", "zvbb", "zvfh", "zvfhmin", + "zvkt", "zvl128b", "zvl32b", "zvl64b"; + riscv,cbom-block-size = <0x40>; + riscv,cboz-block-size = <0x40>; + + next-level-cache = <&l2_cache>; + + intc_cpu0: interrupt-controller { + #interrupt-cells = <1>; + compatible = "riscv,cpu-intc"; + interrupt-controller; + }; + }; + }; + + l2_cache: l2-cache { + compatible = "cache"; + cache-level = <2>; + cache-block-size = <64>; + cache-size = <1048576>; + }; + + memory: memory@80000000 { + device_type = "memory"; + /* + * Static 64 GiB memory profile for SPEC CPU2026 speed workloads. + */ + reg = <0x0 0x80000000 0x10 0x00000000>; + }; + + soc { + #address-cells = <2>; + #size-cells = <2>; + compatible = "freechips,rocketchip-unknown-soc", "simple-bus"; + ranges; + + /* + * This FPGA image boots without AIA. Describe only the CLINT + PLIC + * interrupt topology that the software stack should probe. + */ + clint: clint@38000000 { + compatible = "riscv,clint0"; + reg = <0x0 0x38000000 0x0 0x10000>; + reg-names = "control"; + interrupts-extended = <&intc_cpu0 3 &intc_cpu0 7>; + }; + + debug-controller@38020000 { + compatible = "sifive,debug-013", "riscv,debug-013"; + debug-attach = "jtag"; + reg = <0x0 0x38020000 0x0 0x1000>; + reg-names = "control"; + interrupts-extended = <&intc_cpu0 65535>; + }; + + PLIC: interrupt-controller@3c000000 { + compatible = "riscv,plic0"; + #interrupt-cells = <1>; + interrupt-controller; + reg = <0x0 0x3c000000 0x0 0x4000000>; + reg-names = "control"; + interrupts-extended = <&intc_cpu0 11 &intc_cpu0 9>; + riscv,max-priority = <7>; + riscv,ndev = <66>; + }; + + /* + * Board-specific FPGA peripheral: the external AXI UART16550 used as + * the Linux console on fpga. Keep the 32-bit MMIO spacing and the + * 50 MHz input clock to match the FPGA wrapper. + */ + uart0: serial@310b0000 { + compatible = "ns16550a"; + reg = <0x0 0x310b0000 0x0 0x10000>; + reg-shift = <0x2>; + reg-io-width = <0x4>; + clock-frequency = <50000000>; + current-speed = <115200>; + //interrupt-parent = <&PLIC>; + //interrupts = <40>; + status = "okay"; + }; + }; + + aliases { + serial0 = &uart0; + }; + + chosen { + /* + * On this FPGA DTS the UART16550 interrupt is not wired into Linux + * (`interrupt-parent` / `interrupts` are intentionally absent above). + * `ttyS0` can still print early boot logs via polling/earlycon, but the + * normal console path becomes unreliable once Linux switches away from + * earlycon. When the UART interrupt is absent, use the SBI console + * (`hvc0`) instead. + */ + bootargs = "console=hvc0 earlycon=sbi loglevel=8"; + stdout-path = "serial0:115200n8"; + linux,initrd-start = <0x0 INITRAMFS_BEGIN>; + linux,initrd-end = <0x0 INITRAMFS_END>; + + opensbi-config { + compatible = "opensbi,config"; + cold-boot-harts = <&cpu0>; + }; + }; +}; diff --git a/dts/xiangshan-fpga-noAIA-mem8g.dts.in b/dts/xiangshan-fpga-noAIA-mem8g.dts.in index 2ffddac..1b172ea 100644 --- a/dts/xiangshan-fpga-noAIA-mem8g.dts.in +++ b/dts/xiangshan-fpga-noAIA-mem8g.dts.in @@ -89,7 +89,8 @@ memory: memory@80000000 { device_type = "memory"; /* - * Static 8 GiB memory profile for SPEC2017 and other large initramfs workloads. + * Static 8 GiB memory profile for SPEC rate workloads and other + * large initramfs images that need more than the default footprint. */ reg = <0x0 0x80000000 0x2 0x00000000>; }; diff --git a/scripts/build-firmware-linux.sh b/scripts/build-firmware-linux.sh index ebef4de..1451904 100644 --- a/scripts/build-firmware-linux.sh +++ b/scripts/build-firmware-linux.sh @@ -93,8 +93,7 @@ for dts_template in "$DTS_TEMPLATE_DIR"/*.dts.in ; do build-dtb "$dts_template" done -# Assemble the image -# Using `xiangshan.dtb` as the default device tree unless DEFAULT_DTB is set. +# Assemble the image using the selected DTB basename and optional memory profile. if [ -n "$DTB_MEMORY_PROFILE" ]; then DEFAULT_DTB_BASE="$DEFAULT_DTB-mem$DTB_MEMORY_PROFILE" else diff --git a/workloads/linux/spec2026/README.md b/workloads/linux/spec2026/README.md new file mode 100644 index 0000000..71e8cb0 --- /dev/null +++ b/workloads/linux/spec2026/README.md @@ -0,0 +1,51 @@ +# SPEC CPU2026 Linux Workload + +Build one case: + +```sh +make linux/spec2026 BENCH=706.stockfish_r SPEC2026_ISO=/path/to/cpu2026-1.0.1.iso -jN +``` + +Export images: + +```sh +make spec2026-images SPEC2026_ISO=/path/to/cpu2026-1.0.1.iso -jN +``` + +Default image export mode is `rate`. Use `MODE=speed` or `MODE=all` to +change the selected cases. + +Output goes to `build/images/spec2026`. +Rate cases are written to `build/images/spec2026rate`; speed cases are written +to `build/images/spec2026speed`. + +The export tree now includes: + +```text +build/images/spec2026rate/ + bin/.fw_payload.bin + kernel/.Image + rootfs/.rootfs.cpio + elf/.elf + cmd/.run.sh + gcpt/gcpt.elf + gcpt/gcpt.bin + cfg/.cfg + logs/build_elf/.log + stamps/.images.stamp + +build/images/spec2026speed/ + bin/.fw_payload.bin + kernel/.Image + rootfs/.rootfs.cpio + elf/.elf + cmd/.run.sh + gcpt/gcpt.elf + gcpt/gcpt.bin + cfg/.cfg + logs/build_elf/.log + stamps/.images.stamp +``` + +Re-running `make spec2026-images` skips completed cases and resumes an +interrupted case build through `build/linux-workloads/spec2026/_bench-builds`. diff --git a/workloads/linux/spec2026/build.sh b/workloads/linux/spec2026/build.sh new file mode 100644 index 0000000..36ade0f --- /dev/null +++ b/workloads/linux/spec2026/build.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${SPEC2026_CASE:?SPEC2026_CASE is required}" +: "${SPEC2026:?SPEC2026 is required}" +: "${SPEC2026_CFG:?SPEC2026_CFG is required}" +: "${CROSS_COMPILE:=riscv64-unknown-linux-gnu-}" +: "${SPEC2026_TUNE:=base}" +: "${SPEC2026_JOBS:=$(nproc)}" +: "${SPEC2026_ELF_ONLY:=false}" +: "${SPEC2026_LOG_DIR:=$WORKLOAD_BUILD_DIR/logs}" +: "${PKG_DIR:=$WORKLOAD_BUILD_DIR/package}" + +mkdir -p "$SPEC2026_LOG_DIR" + +spec2026_progress_prefix() { + local k="${SPEC2026_PROGRESS_K:-1}" + local n="${SPEC2026_PROGRESS_N:-1}" + printf '[spec2026 %s/%s]' "$k" "$n" +} + +status() { + printf '%s %s\n' "$(spec2026_progress_prefix)" "$*" +} + +is_true() { + case "$1" in + 1|true|yes|on) return 0 ;; + *) return 1 ;; + esac +} + +python_args=() +if is_true "$SPEC2026_ELF_ONLY"; then + python_args+=(--elf-only) +fi + +python3 "$WORKLOAD_DIR/spec2026-package.py" \ + --case "$SPEC2026_CASE" \ + --spec "$SPEC2026" \ + --spec-config "$SPEC2026_CFG" \ + --pkg-dir "$PKG_DIR" \ + --out-dir "$WORKLOAD_BUILD_DIR" \ + --cross-compile "$CROSS_COMPILE" \ + --log-dir "$SPEC2026_LOG_DIR" \ + --tune "$SPEC2026_TUNE" \ + --jobs "$SPEC2026_JOBS" \ + --input-set "${SPEC2026_INPUT:-ref}" \ + "${python_args[@]}" diff --git a/workloads/linux/spec2026/prepare-spec-workspace.sh b/workloads/linux/spec2026/prepare-spec-workspace.sh new file mode 100644 index 0000000..ddc161e --- /dev/null +++ b/workloads/linux/spec2026/prepare-spec-workspace.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +source_spec_iso="$(realpath "$1")" +prepared_spec_root="$(realpath -m "$2")" + +prepare_dir="$(dirname "$prepared_spec_root")" +mkdir -p "$prepare_dir" +chmod -R u+rwX "$prepared_spec_root" 2>/dev/null || true +rm -rf "$prepared_spec_root" + +temp_root="" +cleanup() { + if [ -n "$temp_root" ]; then + chmod -R u+rwX "$temp_root" 2>/dev/null || true + rm -rf "$temp_root" 2>/dev/null || true + fi +} +trap cleanup EXIT + +if ! command -v xorriso >/dev/null 2>&1; then + echo "xorriso is required to extract $source_spec_iso" >&2 + exit 1 +fi + +tmp_parent="${SPEC2026_PREPARE_TMPDIR:-${TMPDIR:-/tmp}}" +mkdir -p "$tmp_parent" +temp_root="$(mktemp -d "$tmp_parent/spec2026-prepare.XXXXXX")" + +media_root="$temp_root/media" +staged_spec_root="$temp_root/spec-src" +mkdir -p "$media_root" + +xorriso -osirrox on -indev "$source_spec_iso" -extract / "$media_root" >/dev/null + +if ! [ -f "$media_root/install.sh" ]; then + echo "install.sh not found in $source_spec_iso" >&2 + exit 1 +fi + +( + cd "$media_root" + env -u SPEC sh ./install.sh -f -d "$staged_spec_root" +) + +mkdir -p "$prepared_spec_root" +cp -R "$staged_spec_root"/. "$prepared_spec_root"/ +chmod -R u+rwX "$prepared_spec_root" + +if [ -x "$prepared_spec_root/bin/relocate" ]; then + ( + cd "$prepared_spec_root" + . ./shrc >/dev/null + bin/relocate >/dev/null + ) +fi diff --git a/workloads/linux/spec2026/riscv_gcc15_base.cfg b/workloads/linux/spec2026/riscv_gcc15_base.cfg new file mode 100644 index 0000000..c2f3431 --- /dev/null +++ b/workloads/linux/spec2026/riscv_gcc15_base.cfg @@ -0,0 +1,107 @@ +#------------------------------------------------------------------------------ +# SPEC CPU(R) 2026 config for gcc/g++/gfortran on Linux RISCV-64 +#------------------------------------------------------------------------------ +# +# This file is a build-oriented copy of the official Example-gcc-linux-riscv64 +# config shipped with the CPU2026 kit. The workload helper passes `gcc_dir` +# and build parallelism on the command line so this file can stay in-repo. +# `gcc_dir` is the toolchain root, and the compiler binaries are the prefixed +# `riscv64-unknown-linux-gnu-*` tools under `bin/`. +# + +%ifndef %{label} + label = spec2026 +%else + label = %{label} +%endif + +%ifndef %{build_ncpus} +% define build_ncpus 8 +%endif + +default: + bench_post_setup = sync + command_add_redirect = 1 + flagsurl = $[top]/config/flags/gcc.xml + ignore_errors = 1 + iterations = 1 + line_width = 1020 + log_line_width = 1020 + makeflags = --jobs=%{build_ncpus} + mean_anyway = 1 + output_format = txt,html,cfg,pdf,csv + preenv = 1 + reportable = 0 + tune = base + +default: + copies = 1 + threads = 4 + +%ifndef %{gcc_dir} +% define gcc_dir "/opt/gcc-15" +%endif + +%define GCCge10 + +default: + preENV_LD_LIBRARY_PATH = %{gcc_dir}/lib64/:%{gcc_dir}/lib/:/lib64 + SPECLANG = %{gcc_dir}/bin/riscv64-unknown-linux-gnu- + CC = $(SPECLANG)gcc -std=c18 + CXX = $(SPECLANG)g++ -std=c++17 + FC = $(SPECLANG)gfortran -std=f2018 + CC_VERSION_OPTION = --version + CXX_VERSION_OPTION = --version + FC_VERSION_OPTION = --version + +800.pot3d_s: + PORTABILITY = -DSPEC_SUPPRESS_LOCAL_AND_REDUCE + +710.omnetpp_r: + PORTABILITY = -fno-finite-math-only + +734.vpr_r,834.vpr_s: + PORTABILITY = -fno-finite-math-only + +735.gem5_r,835.gem5_s: + PORTABILITY = -fno-finite-math-only + +736.ocio_r: + PORTABILITY = -fno-finite-math-only + +737.gmsh_r: + PORTABILITY = -fno-fast-math + +748.flightdm_r: + PORTABILITY = -fno-fast-math + +753.ns3_r,853.ns3_s: + PORTABILITY = -fno-finite-math-only + +767.nest_r,867.nest_s: + PORTABILITY = -fno-finite-math-only + PORTABILITY_LIBS = -lstdc++fs + +%ifdef %{GCCge10} + default: + EXTRA_FFLAGS = -fallow-argument-mismatch +%endif + +intspeed,fpspeed: + preENV_OMP_STACKSIZE = 120M + +doconcurrent: + EXTRA_FOPTIMIZE = -ftree-parallelize-loops=8 + +openmp: + EXTRA_OPTIMIZE = -fopenmp -DSPEC_OPENMP + +cxxthreads: + EXTRA_OPTIMIZE = -pthread + +default=base: + OPTIMIZE = -g -O3 -march=rv64gc + +default: + basepeak = yes +default=peak: diff --git a/workloads/linux/spec2026/rules.mk b/workloads/linux/spec2026/rules.mk new file mode 100644 index 0000000..5629ea0 --- /dev/null +++ b/workloads/linux/spec2026/rules.mk @@ -0,0 +1,220 @@ +SPEC2026_WORKLOAD_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +SPEC2026_REPO_ROOT := $(abspath $(SPEC2026_WORKLOAD_DIR)/../../..) +SPEC2026_SELF_MAKEFILE := $(SPEC2026_WORKLOAD_DIR)/rules.mk +SPEC2026_ROOT_MAKEFILE := $(SPEC2026_REPO_ROOT)/Makefile +SPEC2026_RECURSE_MAKEFILE := $(if $(filter $(SPEC2026_ROOT_MAKEFILE),$(abspath $(firstword $(MAKEFILE_LIST)))),$(SPEC2026_ROOT_MAKEFILE),$(SPEC2026_SELF_MAKEFILE)) +SPEC2026_SCRIPTS_DIR := $(SPEC2026_REPO_ROOT)/scripts +SPEC2026_DTS_DIR := $(SPEC2026_REPO_ROOT)/dts +SPEC2026_BUILD_DIR ?= $(SPEC2026_REPO_ROOT)/build/linux-workloads/spec2026 +SPEC2026_CFG ?= $(SPEC2026_WORKLOAD_DIR)/riscv_gcc15_base.cfg +SPEC2026_HELPER := $(SPEC2026_WORKLOAD_DIR)/spec2026-package.py +SPEC2026_IMAGE_RATE_DIR ?= $(SPEC2026_REPO_ROOT)/build/images/spec2026rate +SPEC2026_IMAGE_SPEED_DIR ?= $(SPEC2026_REPO_ROOT)/build/images/spec2026speed +SPEC2026_SOURCE_SPEC_ISO := $(SPEC2026_ISO) +SPEC2026_PREPARED_SPEC_ROOT := $(SPEC2026_BUILD_DIR)/spec-src +SPEC2026_SOURCE_SPEC_HASH := $(shell printf '%s\n' "$(SPEC2026_SOURCE_SPEC_ISO)" | sha256sum | cut -d ' ' -f 1) +SPEC2026_PREPARE_STAMP := $(SPEC2026_BUILD_DIR)/spec-src.$(SPEC2026_SOURCE_SPEC_HASH).prepared +SPEC2026_PREPARE_SCRIPT := $(SPEC2026_WORKLOAD_DIR)/prepare-spec-workspace.sh +SPEC2026_CROSS_COMPILE ?= riscv64-unknown-linux-gnu- +SPEC2026_TUNE ?= base +SPEC2026_JOBS ?= $(shell nproc) +SPEC2026_INPUT ?= $(if $(INPUT),$(INPUT),ref) +SPEC2026_MODE ?= $(if $(MODE),$(MODE),rate) +SPEC2026_IMAGE_INPUT ?= $(if $(IMAGE_INPUT),$(IMAGE_INPUT),$(SPEC2026_INPUT)) +SPEC2026_IMAGE_MODE ?= $(if $(IMAGE_MODE),$(IMAGE_MODE),$(SPEC2026_MODE)) +SPEC2026_PROGRESS_K ?= 1 +SPEC2026_PROGRESS_N ?= 1 +SPEC2026_PROGRESS_PREFIX := [spec2026 $(SPEC2026_PROGRESS_K)/$(SPEC2026_PROGRESS_N)] +SPEC2026_BUILDROOT_DIR ?= $(if $(BUILDROOT_DIR),$(BUILDROOT_DIR),$(SPEC2026_REPO_ROOT)/build/buildroot) +SPEC2026_LINUX_IMAGE ?= $(if $(LINUX_IMAGE),$(LINUX_IMAGE),$(SPEC2026_BUILDROOT_DIR)/output/images/Image) +SPEC2026_GCPT_ELF ?= $(if $(GCPT_ELF),$(GCPT_ELF),$(SPEC2026_REPO_ROOT)/build/LibCheckpointAlpha/build/gcpt) +SPEC2026_GCPT_BIN ?= $(if $(GCPT_BIN),$(GCPT_BIN),$(SPEC2026_REPO_ROOT)/build/LibCheckpointAlpha/build/gcpt.bin) +SPEC2026_SBI_BUILD_DIR ?= $(if $(SBI_BUILD_DIR),$(SBI_BUILD_DIR),$(SPEC2026_REPO_ROOT)/build/opensbi) +SPEC2026_SBI_BIN ?= $(if $(SBI_BIN),$(SBI_BIN),$(SPEC2026_SBI_BUILD_DIR)/build/platform/generic/firmware/fw_jump.bin) +SPEC2026_BUILDROOT_CROSS_COMPILE ?= $(SPEC2026_BUILDROOT_DIR)/output/host/bin/riscv64-linux- +SPEC2026_DTC ?= $(SPEC2026_BUILDROOT_DIR)/output/host/bin/dtc +SPEC2026_EXPLICIT_DEFAULT_DTB := $(if $(filter undefined,$(origin DEFAULT_DTB)),,1) +SPEC2026_DEFAULT_DTB ?= $(if $(DEFAULT_DTB),$(DEFAULT_DTB),xiangshan-fpga-noAIA) +SPEC2026_RATE_DTB_MEMORY ?= 8g +SPEC2026_SPEED_DTB_MEMORY ?= 64g +SPEC2026_DTB_MEMORY ?= +SPEC2026_RATE_DTB_MIN_MEMORY_BYTES ?= 8589934592 +SPEC2026_SPEED_DTB_MIN_MEMORY_BYTES ?= 68719476736 +SPEC2026_ALL_CASES := $(shell python3 $(SPEC2026_HELPER) --list-cases --mode all 2>/dev/null) +SPEC2026_SELECTED_CASES := $(shell python3 $(SPEC2026_HELPER) --list-cases --input-set $(SPEC2026_INPUT) --mode $(SPEC2026_MODE) 2>/dev/null) +SPEC2026_IMAGE_CASES := $(shell python3 $(SPEC2026_HELPER) --list-cases --input-set $(SPEC2026_IMAGE_INPUT) --mode $(SPEC2026_IMAGE_MODE) 2>/dev/null) +SPEC2026_DTS_SOURCES := $(shell find $(SPEC2026_DTS_DIR) -type f 2>/dev/null) + +spec2026_case_dtb_memory = $(if $(SPEC2026_DTB_MEMORY),$(SPEC2026_DTB_MEMORY),$(if $(filter %_s,$(1)),$(SPEC2026_SPEED_DTB_MEMORY),$(SPEC2026_RATE_DTB_MEMORY))) +spec2026_case_dtb_profile = $(if $(SPEC2026_EXPLICIT_DEFAULT_DTB),,$(call spec2026_case_dtb_memory,$(1))) +spec2026_case_dtb_name = $(SPEC2026_DEFAULT_DTB)$(if $(call spec2026_case_dtb_profile,$(1)),-mem$(call spec2026_case_dtb_profile,$(1))) +spec2026_case_dtb_tag = $(subst /,_,$(call spec2026_case_dtb_name,$(1))) +spec2026_case_dtb_min_memory_bytes = $(if $(filter %_s,$(1)),$(SPEC2026_SPEED_DTB_MIN_MEMORY_BYTES),$(SPEC2026_RATE_DTB_MIN_MEMORY_BYTES)) +spec2026_case_image_dir = $(if $(filter %_s,$(1)),$(SPEC2026_IMAGE_SPEED_DIR),$(SPEC2026_IMAGE_RATE_DIR)) +spec2026_case_image_stamp = $(call spec2026_case_image_dir,$(1))/stamps/$(1).images.stamp + +WORKLOAD_DIRS += $(SPEC2026_BUILD_DIR) + +spec2026-check-spec-iso: + @if [ -z "$(SPEC2026_SOURCE_SPEC_ISO)" ]; then \ + echo "SPEC2026_ISO is required, for example:"; \ + echo " make linux/spec2026 BENCH=706.stockfish_r SPEC2026_ISO=/path/to/cpu2026-1.0.1.iso -jN"; \ + exit 1; \ + fi; \ + if ! [ -f "$(SPEC2026_SOURCE_SPEC_ISO)" ]; then \ + echo "SPEC ISO path does not exist: $(SPEC2026_SOURCE_SPEC_ISO)"; \ + exit 1; \ + fi; \ + if ! [ -f "$(SPEC2026_CFG)" ]; then \ + echo "SPEC2026 cfg does not exist: $(SPEC2026_CFG)"; \ + exit 1; \ + fi; \ + case "$(SPEC2026_INPUT)" in \ + ref|train|test|all) ;; \ + *) echo "SPEC2026_INPUT must be one of: ref, train, test, all"; exit 1 ;; \ + esac; \ + case "$(SPEC2026_IMAGE_INPUT)" in \ + ref|train|test|all) ;; \ + *) echo "SPEC2026_IMAGE_INPUT must be one of: ref, train, test, all"; exit 1 ;; \ + esac; \ + case "$(SPEC2026_MODE)" in \ + rate|speed|all) ;; \ + *) echo "SPEC2026_MODE/MODE must be one of: rate, speed, all"; exit 1 ;; \ + esac; \ + case "$(SPEC2026_IMAGE_MODE)" in \ + rate|speed|all) ;; \ + *) echo "SPEC2026_IMAGE_MODE must be one of: rate, speed, all"; exit 1 ;; \ + esac + +spec2026-force: + +spec2026-prepare: $(SPEC2026_PREPARE_STAMP) + +$(SPEC2026_PREPARE_STAMP): $(SPEC2026_PREPARE_SCRIPT) $(SPEC2026_SOURCE_SPEC_ISO) | spec2026-check-spec-iso + @printf '$(SPEC2026_PROGRESS_PREFIX) Preparing SPEC workspace at $(SPEC2026_PREPARED_SPEC_ROOT)\n' + @bash "$(SPEC2026_PREPARE_SCRIPT)" "$(SPEC2026_SOURCE_SPEC_ISO)" "$(SPEC2026_PREPARED_SPEC_ROOT)" + @touch "$@" + +define add_spec2026_case +$(SPEC2026_BUILD_DIR)/$(1)/download/sentinel: + @mkdir -p "$$(@D)" + @touch "$$@" + +$(SPEC2026_BUILD_DIR)/$(1)/firmware/dtb-$(call spec2026_case_dtb_tag,$(1)).stamp: spec2026-force + @mkdir -p "$$(@D)" + @printf '%s\n' \ + "case=$(1)" \ + "default_dtb=$$(SPEC2026_DEFAULT_DTB)" \ + "profile=$(call spec2026_case_dtb_profile,$(1))" \ + "min_memory_bytes=$(call spec2026_case_dtb_min_memory_bytes,$(1))" > "$$@.tmp" + @if [ -f "$$@" ] && cmp -s "$$@.tmp" "$$@"; then rm "$$@.tmp"; else mv "$$@.tmp" "$$@"; fi + +$(SPEC2026_BUILD_DIR)/$(1)/elf/$(1).elf: $(SPEC2026_PREPARE_STAMP) $$(SPEC2026_HELPER) $$(SPEC2026_WORKLOAD_DIR)/build.sh $$(SPEC2026_CFG) + @mkdir -p "$$(dir $$@)" + @WORKLOAD_DIR="$$(abspath $$(SPEC2026_WORKLOAD_DIR))" \ + WORKLOAD_BUILD_DIR="$$(abspath $(SPEC2026_BUILD_DIR)/$(1))" \ + PKG_DIR="$$(abspath $(SPEC2026_BUILD_DIR)/$(1)/package)" \ + CROSS_COMPILE="$$(SPEC2026_CROSS_COMPILE)" \ + SPEC2026_PROGRESS_K="$$(SPEC2026_PROGRESS_K)" \ + SPEC2026_PROGRESS_N="$$(SPEC2026_PROGRESS_N)" \ + SPEC2026_CASE="$(1)" \ + SPEC2026="$$(SPEC2026_PREPARED_SPEC_ROOT)" \ + SPEC2026_CFG="$$(abspath $$(SPEC2026_CFG))" \ + SPEC2026_TUNE="$$(SPEC2026_TUNE)" \ + SPEC2026_JOBS="$$(SPEC2026_JOBS)" \ + SPEC2026_ELF_ONLY=1 \ + bash "$$(abspath $$(SPEC2026_WORKLOAD_DIR))/build.sh" + +$(SPEC2026_BUILD_DIR)/$(1)/rootfs.cpio: $(SPEC2026_PREPARE_STAMP) $$(SPEC2026_HELPER) $$(SPEC2026_WORKLOAD_DIR)/build.sh $$(SPEC2026_CFG) $(SPEC2026_BUILD_DIR)/$(1)/download/sentinel $$(SPEC2026_SCRIPTS_DIR)/build-workload-linux.sh + @CROSS_COMPILE="$$(SPEC2026_CROSS_COMPILE)" \ + SPEC2026_PROGRESS_K="$$(SPEC2026_PROGRESS_K)" \ + SPEC2026_PROGRESS_N="$$(SPEC2026_PROGRESS_N)" \ + SPEC2026_CASE="$(1)" \ + SPEC2026="$$(SPEC2026_PREPARED_SPEC_ROOT)" \ + SPEC2026_CFG="$$(abspath $$(SPEC2026_CFG))" \ + SPEC2026_INPUT="$$(SPEC2026_INPUT)" \ + SPEC2026_TUNE="$$(SPEC2026_TUNE)" \ + SPEC2026_JOBS="$$(SPEC2026_JOBS)" \ + bash "$$(SPEC2026_SCRIPTS_DIR)/build-workload-linux.sh" "$$(SPEC2026_WORKLOAD_DIR)" "$(SPEC2026_BUILD_DIR)/$(1)" + +$(SPEC2026_BUILD_DIR)/$(1)/fw_payload.bin: $$(SPEC2026_DTS_SOURCES) $(SPEC2026_BUILD_DIR)/$(1)/firmware/dtb-$(call spec2026_case_dtb_tag,$(1)).stamp $$(SPEC2026_GCPT_BIN) $$(SPEC2026_SCRIPTS_DIR)/build-firmware-linux.sh $(SPEC2026_BUILD_DIR)/$(1)/rootfs.cpio $$(SPEC2026_LINUX_IMAGE) $$(SPEC2026_SBI_BIN) + @printf '$(SPEC2026_PROGRESS_PREFIX) Assembling firmware for $(1)\n' + @CROSS_COMPILE="$$(SPEC2026_BUILDROOT_CROSS_COMPILE)" \ + DTC="$$(SPEC2026_DTC)" \ + DEFAULT_DTB="$$(SPEC2026_DEFAULT_DTB)" \ + DTB_MEMORY_PROFILE="$(call spec2026_case_dtb_profile,$(1))" \ + DTB_MIN_MEMORY_BYTES="$(call spec2026_case_dtb_min_memory_bytes,$(1))" \ + SPEC2026_PROGRESS_K="$$(SPEC2026_PROGRESS_K)" \ + SPEC2026_PROGRESS_N="$$(SPEC2026_PROGRESS_N)" \ + bash "$$(SPEC2026_SCRIPTS_DIR)/build-firmware-linux.sh" "$$(SPEC2026_GCPT_BIN)" "$$(SPEC2026_SBI_BUILD_DIR)" "$$(SPEC2026_DTS_DIR)" "$$(SPEC2026_LINUX_IMAGE)" "$(SPEC2026_BUILD_DIR)/$(1)" + +linux/$(1): $(SPEC2026_BUILD_DIR)/$(1)/fw_payload.bin + +WORKLOAD_PHONY_TARGETS += linux/$(1) + +$(call spec2026_case_image_stamp,$(1)): $(SPEC2026_BUILD_DIR)/$(1)/fw_payload.bin $(SPEC2026_GCPT_ELF) $(SPEC2026_GCPT_BIN) $(SPEC2026_LINUX_IMAGE) | spec2026-check-spec-iso + @printf '$(SPEC2026_PROGRESS_PREFIX) Exporting $(1) artifacts to $(call spec2026_case_image_dir,$(1))\n' + @mkdir -p "$(call spec2026_case_image_dir,$(1))/bin" "$(call spec2026_case_image_dir,$(1))/kernel" "$(call spec2026_case_image_dir,$(1))/rootfs" "$(call spec2026_case_image_dir,$(1))/elf" "$(call spec2026_case_image_dir,$(1))/cmd" "$(call spec2026_case_image_dir,$(1))/cfg" "$(call spec2026_case_image_dir,$(1))/gcpt" "$(call spec2026_case_image_dir,$(1))/logs/build_elf" "$(call spec2026_case_image_dir,$(1))/stamps" + @cp "$(SPEC2026_CFG)" "$(call spec2026_case_image_dir,$(1))/cfg/$(notdir $(SPEC2026_CFG))" + @cp "$(SPEC2026_GCPT_ELF)" "$(call spec2026_case_image_dir,$(1))/gcpt/gcpt.elf" + @cp "$(SPEC2026_GCPT_BIN)" "$(call spec2026_case_image_dir,$(1))/gcpt/gcpt.bin" + @cp "$(SPEC2026_BUILD_DIR)/$(1)/elf/$(1).elf" "$(call spec2026_case_image_dir,$(1))/elf/$(1).elf" + @cp "$(SPEC2026_BUILD_DIR)/$(1)/logs/build_elf/build.log" "$(call spec2026_case_image_dir,$(1))/logs/build_elf/$(1).log" + @cp "$(SPEC2026_LINUX_IMAGE)" "$(call spec2026_case_image_dir,$(1))/kernel/$(1).Image" + @cp "$(SPEC2026_BUILD_DIR)/$(1)/rootfs.cpio" "$(call spec2026_case_image_dir,$(1))/rootfs/$(1).rootfs.cpio" + @cp "$(SPEC2026_BUILD_DIR)/$(1)/fw_payload.bin" "$(call spec2026_case_image_dir,$(1))/bin/$(1).fw_payload.bin" + @cp "$(SPEC2026_BUILD_DIR)/$(1)/package/spec/run.sh" "$(call spec2026_case_image_dir,$(1))/cmd/$(1).run.sh" + @touch "$$@" +endef + +$(foreach case,$(SPEC2026_ALL_CASES),$(eval $(call add_spec2026_case,$(case)))) + +linux/spec2026: spec2026-check-spec-iso + @if [ -z "$(BENCH)" ]; then \ + echo "Usage: make linux/spec2026 BENCH=706.stockfish_r SPEC2026_ISO=/path/to/cpu2026-1.0.1.iso -jN"; \ + exit 1; \ + fi + @$(MAKE) --no-print-directory -f "$(SPEC2026_RECURSE_MAKEFILE)" GCPT_DEFAULT_DTB="$(SPEC2026_DEFAULT_DTB)" $(SPEC2026_BUILD_DIR)/$(BENCH)/fw_payload.bin + +spec2026-elf: spec2026-check-spec-iso + @if [ -z "$(BENCH)" ]; then \ + echo "Usage: make spec2026-elf BENCH=706.stockfish_r SPEC2026_ISO=/path/to/cpu2026-1.0.1.iso -jN"; \ + exit 1; \ + fi + @$(MAKE) --no-print-directory -f "$(SPEC2026_RECURSE_MAKEFILE)" $(SPEC2026_BUILD_DIR)/$(BENCH)/elf/$(BENCH).elf + +spec2026-elfs: spec2026-check-spec-iso + @if [ -z "$(SPEC2026_SELECTED_CASES)" ]; then \ + echo "No SPEC2026 cases selected by SPEC2026_INPUT=$(SPEC2026_INPUT)"; \ + exit 1; \ + fi; \ + for case in $(SPEC2026_SELECTED_CASES); do \ + $(MAKE) --no-print-directory -f "$(SPEC2026_RECURSE_MAKEFILE)" "$(SPEC2026_BUILD_DIR)/$$case/elf/$$case.elf" || exit $$?; \ + done + +spec2026-images: spec2026-check-spec-iso + @if [ -z "$(SPEC2026_IMAGE_CASES)" ]; then \ + echo "No SPEC2026 cases selected by SPEC2026_IMAGE_INPUT=$(SPEC2026_IMAGE_INPUT), SPEC2026_IMAGE_MODE=$(SPEC2026_IMAGE_MODE)"; \ + exit 1; \ + fi; \ + total="$(words $(SPEC2026_IMAGE_CASES))"; \ + i=0; \ + for case in $(SPEC2026_IMAGE_CASES); do \ + i=$$((i + 1)); \ + case "$$case" in \ + *_s) image_dir="$(SPEC2026_IMAGE_SPEED_DIR)" ;; \ + *) image_dir="$(SPEC2026_IMAGE_RATE_DIR)" ;; \ + esac; \ + stamp="$$image_dir/stamps/$$case.images.stamp"; \ + if [ -f "$$stamp" ] && [ -f "$$image_dir/bin/$$case.fw_payload.bin" ] && [ -f "$$image_dir/kernel/$$case.Image" ] && [ -f "$$image_dir/rootfs/$$case.rootfs.cpio" ] && [ -f "$$image_dir/elf/$$case.elf" ] && [ -f "$$image_dir/cmd/$$case.run.sh" ] && [ -f "$$image_dir/cfg/$(notdir $(SPEC2026_CFG))" ] && [ -f "$$image_dir/gcpt/gcpt.elf" ] && [ -f "$$image_dir/gcpt/gcpt.bin" ] && [ -f "$$image_dir/logs/build_elf/$$case.log" ]; then \ + printf '[spec2026 %s/%s] Reusing exported images for %s\n' "$$i" "$$total" "$$case"; \ + continue; \ + fi; \ + if [ -f "$$stamp" ]; then \ + rm -f "$$stamp"; \ + fi; \ + SPEC2026_PROGRESS_K="$$i" SPEC2026_PROGRESS_N="$$total" $(MAKE) --no-print-directory -f "$(SPEC2026_RECURSE_MAKEFILE)" GCPT_DEFAULT_DTB="$(SPEC2026_DEFAULT_DTB)" "$$stamp" || exit $$?; \ + done + @printf '[spec2026 %s/%s] Output written to %s and %s\n' "$(words $(SPEC2026_IMAGE_CASES))" "$(words $(SPEC2026_IMAGE_CASES))" "$(abspath $(SPEC2026_IMAGE_RATE_DIR))" "$(abspath $(SPEC2026_IMAGE_SPEED_DIR))" + +.PHONY: linux/spec2026 spec2026-check-spec-iso spec2026-prepare spec2026-elf spec2026-elfs spec2026-images spec2026-force diff --git a/workloads/linux/spec2026/spec2026-package.py b/workloads/linux/spec2026/spec2026-package.py new file mode 100644 index 0000000..6ca6667 --- /dev/null +++ b/workloads/linux/spec2026/spec2026-package.py @@ -0,0 +1,657 @@ +#!/usr/bin/env python3 +import argparse +import hashlib +import json +import os +import shlex +import shutil +import subprocess +import sys +from collections import deque +from pathlib import Path + +INPUT_SETS = ("ref", "train", "test") +DEFAULT_LABEL = "spec2026" +ALL_CASES = ( + "706.stockfish_r", + "707.ntest_r", + "708.sqlite_r", + "709.cactus_r", + "710.omnetpp_r", + "714.cpython_r", + "721.gcc_r", + "722.palm_r", + "723.llvm_r", + "727.cppcheck_r", + "729.abc_r", + "731.astcenc_r", + "734.vpr_r", + "735.gem5_r", + "736.ocio_r", + "737.gmsh_r", + "748.flightdm_r", + "749.fotonik3d_r", + "750.sealcrypto_r", + "753.ns3_r", + "765.roms_r", + "766.femflow_r", + "767.nest_r", + "772.marian_r", + "777.zstd_r", + "782.lbm_r", + "800.pot3d_s", + "801.xz_s", + "803.sph_exa_s", + "807.ntest_s", + "809.cactus_s", + "811.tealeaf_s", + "816.nab_s", + "817.flac_s", + "820.cloverleaf_s", + "821.gcc_s", + "822.palm_s", + "823.llvm_s", + "827.cppcheck_s", + "829.abc_s", + "834.vpr_s", + "835.gem5_s", + "838.diamond_s", + "846.minizinc_s", + "849.fotonik3d_s", + "853.ns3_s", + "854.graph500_s", + "857.namd_s", + "865.roms_s", + "867.nest_s", + "872.marian_s", + "881.neutron_s", + "998.specrand_s", + "999.specrand_r", +) + + +def format_cmd(cmd): + return " ".join(shlex.quote(str(part)) for part in cmd) + + +def progress_prefix(): + current = os.environ.get("SPEC2026_PROGRESS_K", "1") + total = os.environ.get("SPEC2026_PROGRESS_N", "1") + return f"[spec2026 {current}/{total}]" + + +def status(message): + print(f"{progress_prefix()} {message}", flush=True) + + +def read_log_tail(log_path, max_lines=40): + with open(log_path, "r", encoding="utf-8", errors="replace") as f: + lines = deque(f, maxlen=max_lines) + return "".join(lines).rstrip() + + +def run(cmd, *, env=None, cwd=None, log_path=None, summary=None): + command_text = format_cmd(cmd) + if summary: + status(summary) + + if log_path is None: + raise ValueError("log_path is required") + + log_path.parent.mkdir(parents=True, exist_ok=True) + with open(log_path, "a", encoding="utf-8") as log: + log.write(f"$ {command_text}\n") + if cwd is not None: + log.write(f"# cwd: {cwd}\n") + log.flush() + result = subprocess.run( + cmd, + cwd=cwd, + env=env, + check=False, + stdout=log, + stderr=subprocess.STDOUT, + text=True, + ) + log.write("\n") + + if result.returncode != 0: + tail = read_log_tail(log_path) + detail = f"\nLast log lines from {log_path}:\n{tail}" if tail else "" + raise RuntimeError(f"command failed: {command_text}\nSee {log_path}{detail}") + + +def capture(cmd, *, env=None, cwd=None, log_path=None, summary=None): + command_text = format_cmd(cmd) + if summary: + status(summary) + + result = subprocess.run( + cmd, + cwd=cwd, + env=env, + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + + if log_path is not None: + log_path.parent.mkdir(parents=True, exist_ok=True) + with open(log_path, "w", encoding="utf-8") as log: + log.write(f"$ {command_text}\n") + if cwd is not None: + log.write(f"# cwd: {cwd}\n") + log.write(result.stdout) + if result.stdout and not result.stdout.endswith("\n"): + log.write("\n") + + if result.returncode != 0: + detail = result.stdout[-4000:] if result.stdout else "" + raise RuntimeError(f"command failed: {command_text}\n{detail}") + + return result.stdout + + +def case_mode(case_name): + if case_name.endswith("_s"): + return "speed" + if case_name.endswith("_r"): + return "rate" + raise RuntimeError(f"unexpected SPEC2026 case name: {case_name}") + + +def filter_cases(cases, input_set, mode): + if input_set not in (None, "", "all", *INPUT_SETS): + choices = ", ".join((*INPUT_SETS, "all")) + raise RuntimeError(f"unknown SPEC2026 input set {input_set!r}; available: {choices}") + if mode in (None, "", "all"): + return cases + if mode not in ("rate", "speed"): + raise RuntimeError("unknown SPEC2026 mode {!r}; available: rate, speed, all".format(mode)) + return tuple(case for case in cases if case_mode(case) == mode) + + +def normalize_runtime_input_set(input_set): + if input_set == "all": + return "ref" + return input_set + + +def file_sha256(path): + digest = hashlib.sha256() + with open(path, "rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def load_json(path): + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def maybe_load_json(path): + if not path.is_file(): + return None + try: + return load_json(path) + except Exception: + return None + + +def write_json(path, value): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def build_local_config(template_cfg, generated_cfg): + filtered_lines = [] + for line in template_cfg.read_text(encoding="utf-8").splitlines(): + if line.strip() == "__HASH__": + break + filtered_lines.append(line) + generated_cfg.parent.mkdir(parents=True, exist_ok=True) + generated_cfg.write_text("\n".join(filtered_lines) + "\n", encoding="utf-8") + return generated_cfg + + +def spec_env(spec_dir): + script = f"cd {shlex.quote(str(spec_dir))} && . ./shrc >/dev/null && env -0" + raw = subprocess.check_output(["bash", "-lc", script]) + env = {} + for item in raw.split(b"\0"): + if not item: + continue + key, value = item.split(b"=", 1) + env[key.decode()] = value.decode() + return env + + +def resolve_cross_compile(prefix): + gcc_name = prefix if prefix.endswith("gcc") else prefix + "gcc" + gcc_path = Path(gcc_name) + if not gcc_path.is_absolute(): + found = shutil.which(gcc_name) + if found is None: + raise RuntimeError(f"cannot locate compiler for CROSS_COMPILE={prefix!r}") + gcc_path = Path(found) + if not gcc_path.is_file(): + raise RuntimeError(f"compiler does not exist: {gcc_path}") + gcc_path = gcc_path.resolve() + if not gcc_path.name.endswith("gcc"): + raise RuntimeError(f"unexpected compiler name for CROSS_COMPILE={prefix!r}: {gcc_path.name}") + cross_compile = str(gcc_path)[: -len("gcc")] + toolchain_root = gcc_path.parent.parent + return cross_compile, toolchain_root + + +def case_base_name(case_name): + if "." not in case_name or "_" not in case_name: + raise RuntimeError(f"unexpected SPEC2026 case name: {case_name}") + return case_name.split(".", 1)[1].rsplit("_", 1)[0] + + +def shared_build_dir_for(out_dir, case_name, tune): + return out_dir.parent / "_bench-builds" / case_name / tune + + +def shared_build_metadata(spec_cfg, spec_dir, cross_compile, tune, compiler_root, jobs): + return { + "spec_cfg_sha256": file_sha256(spec_cfg), + "spec_dir": str(spec_dir), + "cross_compile": cross_compile, + "tune": tune, + "compiler_root": str(compiler_root), + "jobs": jobs, + "label": DEFAULT_LABEL, + } + + +def select_built_elf(exe_dir, tune): + if not exe_dir.is_dir(): + raise RuntimeError(f"runcpu did not create exe directory: {exe_dir}") + candidates = [path for path in exe_dir.iterdir() if path.is_file() and not path.name.endswith(".md5")] + if not candidates: + raise RuntimeError(f"no executable produced by runcpu under {exe_dir}") + preferred = [path for path in candidates if f"_{tune}." in path.name] + if preferred: + candidates = preferred + return max(candidates, key=lambda path: path.stat().st_mtime) + + +def explain_missing_elf(output_root, case_name): + build_root = output_root / "benchspec" / "CPU" / case_name / "build" + if not build_root.is_dir(): + return "" + + build_dirs = sorted(path for path in build_root.iterdir() if path.is_dir()) + if not build_dirs: + return "" + + latest = max(build_dirs, key=lambda path: path.stat().st_mtime) + details = [f"Latest build directory: {latest}"] + for name in ("make.err", "make.out"): + file_path = latest / name + if file_path.is_file(): + tail = read_log_tail(file_path) + if tail: + details.append(f"Last log lines from {file_path}:\n{tail}") + break + return "\n" + "\n".join(details) + + +def export_elf_artifact(elf, case_name, out_dir): + elf_dir = out_dir / "elf" + elf_dir.mkdir(parents=True, exist_ok=True) + exported_elf = elf_dir / f"{case_name}.elf" + shutil.copy2(elf, exported_elf) + return exported_elf + + +def export_build_log_artifact(build_log, out_dir): + if not build_log.is_file(): + return None + log_dir = out_dir / "logs" / "build_elf" + log_dir.mkdir(parents=True, exist_ok=True) + exported_log = log_dir / build_log.name + shutil.copy2(build_log, exported_log) + return exported_log + + +def build_elf(spec_dir, case_name, spec_cfg, out_dir, log_dir, cross_compile, tune, jobs, compiler_root): + shared_dir = shared_build_dir_for(out_dir, case_name, tune) + output_root = shared_dir / "runspec-output" + generated_cfg = shared_dir / "runcpu-config" / spec_cfg.name + shared_log_dir = shared_dir / "logs" + build_log = shared_log_dir / "build.log" + exe_dir = output_root / "benchspec" / "CPU" / case_name / "exe" + metadata_path = shared_dir / "build-meta.json" + build_state_path = shared_dir / "build-state.json" + metadata = shared_build_metadata(spec_cfg, spec_dir, cross_compile, tune, compiler_root, jobs) + + cached_metadata = maybe_load_json(metadata_path) + if cached_metadata == metadata: + try: + elf = select_built_elf(exe_dir, tune) + except Exception: + elf = None + if elf is not None: + if not generated_cfg.is_file(): + build_local_config(spec_cfg, generated_cfg) + status(f"Reusing {case_name} build from {shared_dir}") + export_build_log_artifact(build_log, out_dir) + return elf, output_root, generated_cfg + + build_state = maybe_load_json(build_state_path) + if cached_metadata is None and build_state == metadata: + try: + elf = select_built_elf(exe_dir, tune) + except Exception: + elf = None + if elf is not None: + if not generated_cfg.is_file(): + build_local_config(spec_cfg, generated_cfg) + status(f"Recovering completed {case_name} build from {shared_dir}") + write_json(metadata_path, metadata) + if build_state_path.is_file(): + build_state_path.unlink() + export_build_log_artifact(build_log, out_dir) + return elf, output_root, generated_cfg + + resumable = False + if cached_metadata is None: + if build_state == metadata: + resumable = True + elif output_root.exists() or generated_cfg.parent.exists() or build_log.is_file(): + resumable = True + + if resumable: + status(f"Resuming {case_name} build from {shared_dir}") + else: + if output_root.exists(): + shutil.rmtree(output_root) + if generated_cfg.parent.exists(): + shutil.rmtree(generated_cfg.parent) + if shared_log_dir.exists(): + shutil.rmtree(shared_log_dir) + shared_log_dir.mkdir(parents=True, exist_ok=True) + if not generated_cfg.is_file(): + build_local_config(spec_cfg, generated_cfg) + write_json(build_state_path, metadata) + + env = os.environ.copy() + env.update(spec_env(spec_dir)) + env["SPEC"] = str(spec_dir) + + runcpu = spec_dir / "bin" / "runcpu" + cmd = [ + str(runcpu), + "--action", + "build", + "--config", + str(generated_cfg), + "--output_root", + str(output_root), + "--label", + DEFAULT_LABEL, + "--define", + f"gcc_dir={compiler_root}", + "--define", + f"build_ncpus={jobs}", + "--tune", + tune, + case_name, + ] + run( + cmd, + cwd=spec_dir, + env=env, + log_path=build_log, + summary=f"Building {case_name} with runcpu (log: {build_log})", + ) + + try: + elf = select_built_elf(exe_dir, tune) + except RuntimeError as exc: + detail = explain_missing_elf(output_root, case_name) + raise RuntimeError(str(exc) + detail) from exc + + write_json(metadata_path, metadata) + if build_state_path.is_file(): + build_state_path.unlink() + export_build_log_artifact(build_log, out_dir) + return elf, output_root, generated_cfg + + +def select_primary_run_dir(run_root): + if not run_root.is_dir(): + raise RuntimeError(f"runcpu did not create run directory: {run_root}") + + candidates = [path for path in run_root.iterdir() if path.is_dir() and path.name.endswith(".0000")] + if not candidates: + candidates = [path for path in run_root.iterdir() if path.is_dir()] + if not candidates: + raise RuntimeError(f"no run directories produced by runcpu under {run_root}") + return max(candidates, key=lambda path: path.stat().st_mtime) + + +def matching_run_dirs(run_root, primary_run_dir): + prefix = primary_run_dir.name.rsplit(".", 1)[0] + "." + matches = [path for path in run_root.iterdir() if path.is_dir() and path.name.startswith(prefix)] + if not matches: + matches = [primary_run_dir] + return sorted(matches) + + +def generate_runtime_script(spec_dir, primary_run_dir, run_root, target_run_root, script_path, log_path): + env = os.environ.copy() + env.update(spec_env(spec_dir)) + env["SPEC"] = str(spec_dir) + + output = capture( + [str(spec_dir / "bin" / "specinvoke"), "-nn"], + cwd=primary_run_dir, + env=env, + log_path=log_path, + summary=f"Generating replay script for {primary_run_dir.name} (log: {log_path})", + ) + + lines = output.splitlines() + if lines and lines[-1].startswith("specinvoke exit:"): + lines.pop() + + rewritten = "\n".join(line.replace(str(run_root), str(target_run_root)) for line in lines) + if rewritten: + rewritten += "\n" + script_path.write_text(rewritten, encoding="utf-8") + script_path.chmod(0o755) + + +def write_runtime_files(pkg_dir, case_name, primary_run_dir_name): + spec_root = pkg_dir / "spec" + target_run_dir = Path("/spec") / "benchspec" / "CPU" / case_name / "run" / primary_run_dir_name + + run_sh = spec_root / "run.sh" + run_sh.write_text( + "\n".join( + [ + "#!/bin/sh", + "set -e", + f"echo '======== BEGIN {case_name} ========'", + "date -R || true", + f"cd {target_run_dir}", + "set +e", + "sh ./doit.sh", + "status=$?", + "set -e", + "date -R || true", + f"echo '======== END {case_name} ========'", + "exit $status", + "", + ] + ), + encoding="utf-8", + ) + run_sh.chmod(0o755) + + etc = pkg_dir / "etc" + etc.mkdir(parents=True, exist_ok=True) + (etc / "inittab").write_text("::sysinit:nemu-exec /bin/sh /spec/run.sh\n", encoding="utf-8") + + +def package_run_tree(spec_dir, generated_cfg, case_name, pkg_dir, output_root, input_set, tune, jobs, compiler_root, log_dir): + runtime_input_set = normalize_runtime_input_set(input_set) + env = os.environ.copy() + env.update(spec_env(spec_dir)) + env["SPEC"] = str(spec_dir) + + runsetup_log = log_dir / "runsetup.log" + runcpu = spec_dir / "bin" / "runcpu" + cmd = [ + str(runcpu), + "--action", + "runsetup", + "--config", + str(generated_cfg), + "--output_root", + str(output_root), + "--label", + DEFAULT_LABEL, + "--define", + f"gcc_dir={compiler_root}", + "--define", + f"build_ncpus={jobs}", + "--tune", + tune, + "--size", + runtime_input_set, + case_name, + ] + run( + cmd, + cwd=spec_dir, + env=env, + log_path=runsetup_log, + summary=f"Setting up run directory for {case_name} [{runtime_input_set}] (log: {runsetup_log})", + ) + + run_root = output_root / "benchspec" / "CPU" / case_name / "run" + primary_run_dir = select_primary_run_dir(run_root) + + spec_root = pkg_dir / "spec" + target_run_root = spec_root / "benchspec" / "CPU" / case_name / "run" + target_run_root.mkdir(parents=True, exist_ok=True) + + specinvoke_log = log_dir / "specinvoke.log" + generate_runtime_script( + spec_dir, + primary_run_dir, + run_root, + Path("/spec") / "benchspec" / "CPU" / case_name / "run", + primary_run_dir / "doit.sh", + specinvoke_log, + ) + + for src_dir in matching_run_dirs(run_root, primary_run_dir): + dst_dir = target_run_root / src_dir.name + if dst_dir.exists(): + shutil.rmtree(dst_dir) + shutil.copytree(src_dir, dst_dir, symlinks=True) + + write_runtime_files(pkg_dir, case_name, primary_run_dir.name) + + +def package_case(args): + if args.case not in ALL_CASES: + choices = " ".join(sorted(ALL_CASES)) + raise RuntimeError(f"unknown SPEC2026 case {args.case!r}; available cases: {choices}") + + spec_dir = Path(args.spec).resolve() + spec_cfg = Path(args.spec_config).resolve() + pkg_dir = Path(args.pkg_dir).resolve() if args.pkg_dir else None + out_dir = Path(args.out_dir).resolve() + log_dir = Path(args.log_dir).resolve() if args.log_dir else out_dir / "logs" + + cross_compile, detected_toolchain_root = resolve_cross_compile(args.cross_compile) + compiler_root = Path(args.compiler_root).resolve() if args.compiler_root else detected_toolchain_root + + elf, output_root, generated_cfg = build_elf( + spec_dir, + args.case, + spec_cfg, + out_dir, + log_dir, + cross_compile, + args.tune, + args.jobs, + compiler_root, + ) + exported_elf = export_elf_artifact(elf, args.case, out_dir) + status(f"Exported ELF: {exported_elf}") + + if args.elf_only: + return + + if pkg_dir is None: + raise RuntimeError("--pkg-dir is required unless --elf-only is set") + if pkg_dir.exists(): + shutil.rmtree(pkg_dir) + (pkg_dir / "spec").mkdir(parents=True) + + package_run_tree( + spec_dir, + generated_cfg, + args.case, + pkg_dir, + output_root, + args.input_set, + args.tune, + args.jobs, + compiler_root, + log_dir, + ) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--case") + parser.add_argument("--list-cases", action="store_true") + parser.add_argument("--spec") + parser.add_argument("--spec-config") + parser.add_argument("--pkg-dir") + parser.add_argument("--out-dir") + parser.add_argument("--cross-compile", default=os.environ.get("CROSS_COMPILE", "riscv64-unknown-linux-gnu-")) + parser.add_argument("--compiler-root") + parser.add_argument("--log-dir") + parser.add_argument("--tune", default=os.environ.get("SPEC2026_TUNE", "base")) + parser.add_argument("--jobs", type=int, default=int(os.environ.get("SPEC2026_JOBS", "1"))) + parser.add_argument("--input-set", default=os.environ.get("SPEC2026_INPUT", "ref")) + parser.add_argument("--mode", default=os.environ.get("SPEC2026_MODE", "all")) + parser.add_argument("--elf-only", action="store_true") + args = parser.parse_args() + + try: + cases = filter_cases(ALL_CASES, args.input_set, args.mode) + if args.list_cases: + print(" ".join(cases)) + return + + required = { + "--case": args.case, + "--spec": args.spec, + "--spec-config": args.spec_config, + "--out-dir": args.out_dir, + } + missing = [name for name, value in required.items() if not value] + if missing: + raise RuntimeError(f"missing required arguments: {' '.join(missing)}") + + package_case(args) + except Exception as exc: + print(f"{progress_prefix()} ERROR: {exc}", file=sys.stderr) + raise + + +if __name__ == "__main__": + main()