diff --git a/.github/scripts/install-yq.sh b/.github/scripts/install-yq.sh new file mode 100755 index 00000000..94d8b6a4 --- /dev/null +++ b/.github/scripts/install-yq.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# +# Installs yq (mikefarah/yq) into ~/.local/bin with SHA256 verification. +# Intended for CI runners (Linux amd64). + +set -euo pipefail + +YQ_VERSION="${YQ_VERSION:-v4.52.4}" +INSTALL_DIR="$HOME/.local/bin" +BASE_URL="https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}" + +mkdir -p "$INSTALL_DIR" + +wget -qO "$INSTALL_DIR/yq_linux_amd64" "${BASE_URL}/yq_linux_amd64" +wget -qO "$INSTALL_DIR/checksums-bsd" "${BASE_URL}/checksums-bsd" + +( + cd "$INSTALL_DIR" + # checksums-bsd uses BSD-style: SHA256 (yq_linux_amd64) = 0c4d965e... + # sha256sum -c expects GNU-style: 0c4d965e... yq_linux_amd64 + # sed captures the filename (\1) and hash (\2), discarding "SHA256 (", ") = ", + # then emits them in reversed order to match GNU format. + grep 'SHA256 (yq_linux_amd64)' checksums-bsd \ + | sed 's/SHA256 (\(.*\)) = \(.*\)/\2 \1/' \ + | sha256sum -c - + mv yq_linux_amd64 yq + rm -f checksums-bsd + chmod +x yq +) + +echo "$INSTALL_DIR" >> "${GITHUB_PATH:-/dev/null}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 08f81b8c..cf425808 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,10 +22,24 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} submodules: recursive + - name: Install yq + run: .github/scripts/install-yq.sh + + - name: 'Read nightly channel from rust-toolchain.toml' + id: toolchain-meta + run: | + set -euo pipefail + CHANNEL=$(yq -r '.toolchain.channel' rust-toolchain.toml) + if [ -z "$CHANNEL" ] || [ "$CHANNEL" = "null" ]; then + echo "::error::Could not read toolchain.channel from rust-toolchain.toml" + exit 1 + fi + echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + - name: "Set up nightly Rust" # https://github.com/rust-lang/rustup/issues/3409 uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2024-11-29 # Hardcoded version, same as is in the build.rs + toolchain: ${{ steps.toolchain-meta.outputs.channel }} - name: 'Build stable-mir-json' # rustfmt documentation claims it is unstable on code that doesn't build run: | @@ -66,10 +80,24 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} submodules: recursive + - name: Install yq + run: .github/scripts/install-yq.sh + + - name: 'Read nightly channel from rust-toolchain.toml' + id: toolchain-meta + run: | + set -euo pipefail + CHANNEL=$(yq -r '.toolchain.channel' rust-toolchain.toml) + if [ -z "$CHANNEL" ] || [ "$CHANNEL" = "null" ]; then + echo "::error::Could not read toolchain.channel from rust-toolchain.toml" + exit 1 + fi + echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + - name: "Set up nightly Rust" # https://github.com/rust-lang/rustup/issues/3409 uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2024-11-29 # Hardcoded version, same as is in the build.rs + toolchain: ${{ steps.toolchain-meta.outputs.channel }} - name: 'Build stable-mir-json' run: | # Warning check should be redundant since code-quality runs first @@ -100,24 +128,31 @@ jobs: submodules: recursive - name: Install yq + run: .github/scripts/install-yq.sh + + - name: 'Read nightly channel from rust-toolchain.toml' + id: toolchain-meta run: | set -euo pipefail - YQ_VERSION="v4.52.4" - mkdir -p "$HOME/.local/bin" - wget -qO "$HOME/.local/bin/yq" \ - "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" - chmod +x "$HOME/.local/bin/yq" - echo "$HOME/.local/bin" >> $GITHUB_PATH - yq --version + CHANNEL=$(yq -r '.toolchain.channel' rust-toolchain.toml) + if [ -z "$CHANNEL" ] || [ "$CHANNEL" = "null" ]; then + echo "::error::Could not read toolchain.channel from rust-toolchain.toml" + exit 1 + fi + echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + - name: "Set up nightly Rust" # https://github.com/rust-lang/rustup/issues/3409 + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ steps.toolchain-meta.outputs.channel }} - - name: 'Read rustc commit from rust-toolchain.toml' + - name: 'Derive rustc commit and check out Rust repo' id: rustc-meta run: | set -euo pipefail - COMMIT=$(yq '.metadata.rustc-commit' rust-toolchain.toml) - if [ -z "$COMMIT" ] || [ "$COMMIT" = "null" ]; then - echo "::error::metadata.rustc-commit not found in rust-toolchain.toml" + COMMIT=$(rustc -vV | grep 'commit-hash' | cut -d' ' -f2) + if [ -z "$COMMIT" ]; then + echo "::error::Could not determine rustc commit-hash from 'rustc -vV'" exit 1 fi echo "rustc-commit=$COMMIT" >> "$GITHUB_OUTPUT" @@ -130,11 +165,6 @@ jobs: path: rust fetch-depth: 1 - - name: "Set up nightly Rust" # https://github.com/rust-lang/rustup/issues/3409 - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2024-11-29 # Hardcoded version, same as is in the build.rs - - name: 'Build stable-mir-json' run: | # Warning check should be redundant since code-quality runs first RUSTFLAGS='--deny warnings' cargo build -vv diff --git a/Makefile b/Makefile index eace0fc9..453f65ad 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,31 @@ RELEASE_FLAG= -TOOLCHAIN_NAME='' +TOOLCHAIN_NAME= -default: build +.DEFAULT_GOAL := build +.PHONY: help +## Show this help message +help: + @echo "Available targets:" + @awk 'BEGIN {FS = ":.*"; printf "\nUsage:\n make \033[36m\033[0m\n"} \ + /^###/ {printf "\n\033[1m%s\033[0m\n", substr($$0, 5); next} \ + /^##/ {description=substr($$0, 4)} \ + /^[a-zA-Z0-9_-]+:/ { \ + if (description) { \ + printf " \033[36m%-18s\033[0m %s\n", $$1, description; \ + description = ""; \ + } \ + }' $(MAKEFILE_LIST) + +### Build + +.PHONY: build +## Build the project (use RELEASE_FLAG=--release for release) build: - cargo build ${RELEASE_FLAG} + cargo build $(RELEASE_FLAG) +.PHONY: clean +## Clean build artifacts, toolchain overrides, and graphs clean: rustup-clear-toolchain clean-graphs cargo clean @@ -13,10 +33,26 @@ clean: rustup-clear-toolchain clean-graphs rustup-clear-toolchain: rustup override unset rustup override unset --nonexistent - rustup toolchain uninstall "${TOOLCHAIN_NAME}" + if [ -n "$(TOOLCHAIN_NAME)" ]; then rustup toolchain uninstall "$(TOOLCHAIN_NAME)"; fi + +### Test TESTDIR=tests/integration/programs +# Detect the active nightly for golden-file lookup. +# The nightly name (e.g. nightly-2025-03-01) is derived from rustc's +# commit-date, which is one day before the nightly date. We add one day +# to align with the toolchain channel name that users actually see. +NIGHTLY_COMMIT_DATE := $(shell rustc -vV 2>/dev/null | awk '/^commit-date:/{print $$2}') +# Portable +1 day: macOS date(1) uses -v+1d, GNU date uses -d "+1 day". +NIGHTLY_DATE := $(shell \ + if [ -n "$(NIGHTLY_COMMIT_DATE)" ]; then \ + date -j -v+1d -f "%Y-%m-%d" "$(NIGHTLY_COMMIT_DATE)" "+%Y-%m-%d" 2>/dev/null \ + || date -d "$(NIGHTLY_COMMIT_DATE) +1 day" "+%Y-%m-%d" 2>/dev/null \ + || echo "$(NIGHTLY_COMMIT_DATE)"; \ + fi) +ACTIVE_NIGHTLY := nightly-$(NIGHTLY_DATE) + .PHONY: integration-test integration-test: TESTS ?= $(shell find $(TESTDIR) -type f -name "*.rs") integration-test: SMIR ?= cargo run -- "-Zno-codegen" @@ -24,6 +60,7 @@ integration-test: SMIR ?= cargo run -- "-Zno-codegen" integration-test: NORMALIZE ?= jq -S -e -f $(TESTDIR)/../normalise-filter.jq # override this to re-make golden files integration-test: DIFF ?= | diff - +## Run integration tests against expected outputs integration-test: errors=""; \ report() { echo "$$1: $$2"; errors="$$errors\n$$1: $$2"; }; \ @@ -39,19 +76,12 @@ integration-test: done; \ [ -z "$$errors" ] || (echo "===============\nFAILING TESTS:$$errors"; exit 1) - +.PHONY: golden golden: make integration-test DIFF=">" -format: - cargo fmt - bash -O globstar -c 'nixfmt **/*.nix' - -style-check: format - cargo clippy -- -Dwarnings - -.PHONY: remake-ui-tests test-ui - +.PHONY: remake-ui-tests +## Regenerate UI test fixtures (requires RUST_DIR_ROOT) remake-ui-tests: # Check if RUST_DIR_ROOT is set if [ -z "$$RUST_DIR_ROOT" ]; then \ @@ -61,20 +91,77 @@ remake-ui-tests: # This will run without saving source files. Run the script manually to do this. bash tests/ui/remake_ui_tests.sh "$$RUST_DIR_ROOT" +.PHONY: test-ui +## Run UI tests (requires RUST_DIR_ROOT, VERBOSE=1 for details) test-ui: VERBOSE?=0 test-ui: bash tests/ui/run_ui_tests.sh $(if $(filter 1,$(VERBOSE)),--verbose) "$$RUST_DIR_ROOT" -.PHONY: dot svg png d2 clean-graphs check-graphviz +.PHONY: test-directives +## Run unit tests for the directive parser (parse_test_directives.awk) +test-directives: + bash tests/ui/test_directives_test.sh + +.PHONY: test-ui-emit +## Generate effective UI test lists for a nightly (requires RUST_DIR_ROOT, NIGHTLY=nightly-YYYY-MM-DD) +test-ui-emit: + bash tests/ui/diff_test_lists.sh --emit "$$RUST_DIR_ROOT" $(NIGHTLY) + +### Nightly management + +.PHONY: nightly-add +## Add support for a new nightly (requires NIGHTLY, RUST_DIR_ROOT) +nightly-add: + @test -n "$$NIGHTLY" || { echo "Error: NIGHTLY not set (e.g., NIGHTLY=nightly-2025-08-01)"; exit 1; } + @test -n "$$RUST_DIR_ROOT" || { echo "Error: RUST_DIR_ROOT not set"; exit 1; } + python3 scripts/nightly_admin.py add "$$NIGHTLY" --rust-dir "$$RUST_DIR_ROOT" + +.PHONY: nightly-check +## Run all tests for a nightly (requires NIGHTLY, RUST_DIR_ROOT) +nightly-check: + @test -n "$$NIGHTLY" || { echo "Error: NIGHTLY not set"; exit 1; } + @test -n "$$RUST_DIR_ROOT" || { echo "Error: RUST_DIR_ROOT not set"; exit 1; } + python3 scripts/nightly_admin.py check "$$NIGHTLY" --rust-dir "$$RUST_DIR_ROOT" + +.PHONY: nightly-bump +## Bump the pinned nightly (requires NIGHTLY) +nightly-bump: + @test -n "$$NIGHTLY" || { echo "Error: NIGHTLY not set"; exit 1; } + python3 scripts/nightly_admin.py bump "$$NIGHTLY" + +### Diagnostics + +.PHONY: build-info +## Show build.rs cfg detection output (rustc commit-date, enabled flags) +build-info: + @touch build.rs + @cargo build -vv 2>&1 | grep '\] build\.rs:' + +### Code quality + +.PHONY: fmt format +## Format Rust and Nix source files +fmt format: + cargo fmt + bash -O globstar -c 'nixfmt **/*.nix' + +.PHONY: clippy +## Run clippy lint checks (deny warnings) +clippy: + cargo clippy -- -Dwarnings + +.PHONY: style-check +## Run format + clippy lint checks +style-check: format clippy + +### Graph generation OUTDIR_DOT=output-dot OUTDIR_SVG=output-svg OUTDIR_PNG=output-png OUTDIR_D2=output-d2 -clean-graphs: - @rm -rf $(OUTDIR_DOT) $(OUTDIR_SVG) $(OUTDIR_PNG) $(OUTDIR_D2) - +.PHONY: check-graphviz check-graphviz: @command -v dot >/dev/null 2>&1 || { \ echo "Error: Graphviz is not installed or 'dot' is not in PATH."; \ @@ -83,6 +170,8 @@ check-graphviz: exit 1; \ } +.PHONY: dot +## Generate DOT files from test programs dot: @mkdir -p $(OUTDIR_DOT) @for rs in $(TESTDIR)/*.rs; do \ @@ -92,6 +181,8 @@ dot: mv $$name.smir.dot $(OUTDIR_DOT)/ 2>/dev/null || true; \ done +.PHONY: svg +## Generate SVG files from DOT (requires graphviz) svg: check-graphviz dot @mkdir -p $(OUTDIR_SVG) @for dotfile in $(OUTDIR_DOT)/*.dot; do \ @@ -100,6 +191,8 @@ svg: check-graphviz dot dot -Tsvg $$dotfile -o $(OUTDIR_SVG)/$$name.svg; \ done +.PHONY: png +## Generate PNG files from DOT (requires graphviz) png: check-graphviz dot @mkdir -p $(OUTDIR_PNG) @for dotfile in $(OUTDIR_DOT)/*.dot; do \ @@ -108,6 +201,8 @@ png: check-graphviz dot dot -Tpng $$dotfile -o $(OUTDIR_PNG)/$$name.png; \ done +.PHONY: d2 +## Generate D2 diagram files from test programs d2: @mkdir -p $(OUTDIR_D2) @for rs in $(TESTDIR)/*.rs; do \ @@ -116,3 +211,52 @@ d2: cargo run --release -- --d2 -Zno-codegen $$rs 2>/dev/null; \ mv $$name.smir.d2 $(OUTDIR_D2)/ 2>/dev/null || true; \ done + +.PHONY: clean-graphs +## Remove generated graph output directories +clean-graphs: + @rm -rf $(OUTDIR_DOT) $(OUTDIR_SVG) $(OUTDIR_PNG) $(OUTDIR_D2) + +### stdlib smir.json + +STDLIB_OUTDIR=tests/stdlib-artifacts +STDLIB_TARGET=$(shell rustc --print target-triple 2>/dev/null || rustc -vV | grep host | awk '{print $$2}') +SYSROOT=$(shell rustc --print sysroot) +SMIR_BIN=$(CURDIR)/target/debug/stable_mir_json + +.PHONY: stdlib-smir +## Generate smir.json for stdlib via -Zbuild-std +stdlib-smir: build + @# Create a throwaway crate to drive -Zbuild-std + $(eval STDLIB_TMPDIR := $(shell mktemp -d)) + @echo '[package]' > $(STDLIB_TMPDIR)/Cargo.toml + @echo 'name = "stdlib-smir"' >> $(STDLIB_TMPDIR)/Cargo.toml + @echo 'version = "0.0.0"' >> $(STDLIB_TMPDIR)/Cargo.toml + @echo 'edition = "2021"' >> $(STDLIB_TMPDIR)/Cargo.toml + @echo '[[bin]]' >> $(STDLIB_TMPDIR)/Cargo.toml + @echo 'name = "stdlib-smir"' >> $(STDLIB_TMPDIR)/Cargo.toml + @echo 'path = "main.rs"' >> $(STDLIB_TMPDIR)/Cargo.toml + @echo 'fn main() {}' > $(STDLIB_TMPDIR)/main.rs + @# Build stdlib through our driver; set library path the same way + @# cargo does when it runs our binary via `cargo run` + cd $(STDLIB_TMPDIR) && \ + DYLD_LIBRARY_PATH=$(SYSROOT)/lib \ + LD_LIBRARY_PATH=$(SYSROOT)/lib \ + RUSTC=$(SMIR_BIN) \ + cargo build -Zbuild-std --target $(STDLIB_TARGET) + @# Collect artifacts, stripping hash suffixes from filenames + @rm -rf $(STDLIB_OUTDIR) + @mkdir -p $(STDLIB_OUTDIR) + @for f in $(STDLIB_TMPDIR)/target/$(STDLIB_TARGET)/debug/deps/*.smir.json; do \ + name=$$(basename "$$f" | sed 's/-[0-9a-f]*\.smir\.json/.smir.json/'); \ + case "$$name" in stdlib_smir*) continue ;; esac; \ + cp "$$f" $(STDLIB_OUTDIR)/$$name; \ + done + @rm -rf $(STDLIB_TMPDIR) + @echo "stdlib smir.json artifacts written to $(STDLIB_OUTDIR)/" + @ls -lhS $(STDLIB_OUTDIR)/ + +.PHONY: clean-stdlib-smir +## Remove stdlib smir.json artifacts +clean-stdlib-smir: + @rm -rf $(STDLIB_OUTDIR) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9249d51a..06f099e1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,9 +1,3 @@ [toolchain] channel = "nightly-2024-11-29" components = ["llvm-tools", "rustc-dev", "rust-src", "rust-analyzer"] - -# Ignored by rustup; used by our test scripts. -# This is the rustc commit that backs the nightly above. -# UI test scripts automatically checkout this commit in RUST_DIR_ROOT. -[metadata] -rustc-commit = "a2545fd6fc66b4323f555223a860c451885d1d2b" diff --git a/src/bin/cargo_stable_mir_json.rs b/src/bin/cargo_stable_mir_json.rs index 2ab3c9cd..ce06000c 100644 --- a/src/bin/cargo_stable_mir_json.rs +++ b/src/bin/cargo_stable_mir_json.rs @@ -188,51 +188,40 @@ fn add_run_script(smir_json_dir: &Path, ld_library_path: &Path, profile: Profile Ok(()) } -fn record_ld_library_path(smir_json_dir: &Path) -> Result { - #[cfg(target_os = "macos")] - { - // macOS: Check DYLD_LIBRARY_PATH or use default path - if let Some(paths) = env::var_os("DYLD_LIBRARY_PATH") { - let mut ld_library_file = std::fs::File::create(smir_json_dir.join("ld_library_path"))?; - match paths.to_str() { - Some(ld_library_path) => { - writeln!(ld_library_file, "{}", ld_library_path)?; - Ok(ld_library_path.into()) - } - None => bail!("Couldn't cast DYLD_LIBRARY_PATH to str"), - } - } else { - // Use default macOS library path including Rust toolchain - let rustup_home = env::var("HOME").unwrap_or_else(|_| "/Users".to_string()); - let rust_toolchain_path = format!( - "{}/.rustup/toolchains/nightly-2024-11-29-aarch64-apple-darwin/lib", - rustup_home - ); - let default_path = format!("{}:/usr/local/lib:/usr/lib", rust_toolchain_path); - let mut ld_library_file = std::fs::File::create(smir_json_dir.join("ld_library_path"))?; - writeln!(ld_library_file, "{}", default_path)?; - Ok(default_path.into()) - } +/// Resolve the rustc toolchain lib path dynamically via `rustc --print sysroot`. +fn rustc_lib_path() -> Result { + let output = std::process::Command::new("rustc") + .args(["--print", "sysroot"]) + .output()?; + if !output.status.success() { + bail!("rustc --print sysroot failed"); } + let sysroot = String::from_utf8(output.stdout)?.trim().to_string(); + Ok(PathBuf::from(sysroot).join("lib")) +} - #[cfg(not(target_os = "macos"))] - { - // Linux and other systems: Check LD_LIBRARY_PATH - if let Some(paths) = env::var_os("LD_LIBRARY_PATH") { - let mut ld_library_file = std::fs::File::create(smir_json_dir.join("ld_library_path"))?; - match paths.to_str() { - Some(ld_library_path) => { - writeln!(ld_library_file, "{}", ld_library_path)?; - Ok(ld_library_path.into()) - } - None => bail!("Couldn't cast LD_LIBRARY_PATH to str"), - } +fn record_ld_library_path(smir_json_dir: &Path) -> Result { + // Prefer the environment variable if set; otherwise derive the path + // from the active rustc toolchain so the wrapper script stays in sync + // with whatever nightly rust-toolchain.toml selects. + let lib_path = { + #[cfg(target_os = "macos")] + let env_var = "DYLD_LIBRARY_PATH"; + #[cfg(not(target_os = "macos"))] + let env_var = "LD_LIBRARY_PATH"; + + if let Some(paths) = env::var_os(env_var) { + paths + .to_str() + .ok_or_else(|| anyhow::anyhow!("Couldn't cast {} to str", env_var))? + .to_string() } else { - // Use default Linux library path - let default_path = "/usr/local/lib:/usr/lib"; - let mut ld_library_file = std::fs::File::create(smir_json_dir.join("ld_library_path"))?; - writeln!(ld_library_file, "{}", default_path)?; - Ok(default_path.into()) + let toolchain_lib = rustc_lib_path()?; + format!("{}:/usr/local/lib:/usr/lib", toolchain_lib.display()) } - } + }; + + let mut ld_library_file = std::fs::File::create(smir_json_dir.join("ld_library_path"))?; + writeln!(ld_library_file, "{lib_path}")?; + Ok(lib_path.into()) } diff --git a/src/printer/collect.rs b/src/printer/collect.rs index c8089e6e..f44a8532 100644 --- a/src/printer/collect.rs +++ b/src/printer/collect.rs @@ -142,6 +142,16 @@ fn collect_and_analyze_items( all_items.push(item); } + if !ty_visitor.layout_panics.is_empty() { + eprintln!( + "warning: {} type layout(s) could not be computed (rustc panicked):", + ty_visitor.layout_panics.len() + ); + for panic in &ty_visitor.layout_panics { + eprintln!(" type {:?}: {}", panic.ty, panic.message); + } + } + ( CollectedCrate { items: all_items, diff --git a/src/printer/items.rs b/src/printer/items.rs index a253ad2c..884ab49d 100644 --- a/src/printer/items.rs +++ b/src/printer/items.rs @@ -101,8 +101,7 @@ pub(super) fn mk_item(tcx: TyCtxt<'_>, item: MonoItem, sym_name: String) -> (Mon Ok(alloc) => Some(alloc), err => { eprintln!( - "StaticDef({:#?}).eval_initializer() failed with: {:#?}", - static_def, err + "StaticDef({static_def:#?}).eval_initializer() failed with: {err:#?}" ); None } @@ -125,7 +124,7 @@ pub(super) fn mk_item(tcx: TyCtxt<'_>, item: MonoItem, sym_name: String) -> (Mon ) } MonoItem::GlobalAsm(ref asm) => { - let asm_str = format!("{:#?}", asm); + let asm_str = format!("{asm:#?}"); ( item, Item::new( diff --git a/src/printer/schema.rs b/src/printer/schema.rs index d75da9ba..18ccad06 100644 --- a/src/printer/schema.rs +++ b/src/printer/schema.rs @@ -118,11 +118,10 @@ impl AllocMap { assert!( missing_from_map.is_empty(), - "Alloc-id coherence violation: AllocIds {:?} are referenced in \ + "Alloc-id coherence violation: AllocIds {missing_from_map:?} are referenced in \ stored Item bodies but missing from the alloc map. This means \ the analysis phase collected allocations from a different body \ - than what is stored in the Items.", - missing_from_map + than what is stored in the Items." ); assert!( diff --git a/src/printer/ty_visitor.rs b/src/printer/ty_visitor.rs index bb375871..d2735c49 100644 --- a/src/printer/ty_visitor.rs +++ b/src/printer/ty_visitor.rs @@ -13,6 +13,7 @@ use crate::compat::stable_mir; use std::collections::{HashMap, HashSet}; use std::ops::ControlFlow; +use std::panic::{catch_unwind, AssertUnwindSafe}; use stable_mir::mir::mono::Instance; use stable_mir::ty::{RigidTy, TyKind}; @@ -20,23 +21,78 @@ use stable_mir::visitor::{Visitable, Visitor}; use super::schema::TyMap; +/// A layout computation that panicked inside rustc. +pub(super) struct LayoutPanic { + pub ty: stable_mir::ty::Ty, + pub message: String, +} + +/// Attempt to get a type's layout, catching any rustc-internal panics. +/// +/// Some types (e.g., those involving `dyn Trait` in certain positions) cause +/// rustc's layout computation to panic rather than returning an error. We +/// catch those panics here so the visitor can continue; the caller gets +/// `Ok(Some(shape))` on success, `Ok(None)` when layout returns `Err`, or +/// `Err(message)` when rustc panicked. +fn try_layout_shape( + ty: &stable_mir::ty::Ty, +) -> Result, String> { + // catch_unwind keeps the process alive when rustc's layout engine panics + // (e.g. on certain `dyn Trait` types). The default panic hook will still + // print a backtrace to stderr for each caught panic; that's noisy but + // harmless, and avoids the thread-safety issues of swapping the global + // hook per-call. We collect the messages and report them in our own + // summary at the end. + let result = catch_unwind(AssertUnwindSafe(|| ty.layout().ok().map(|l| l.shape()))); + + match result { + Ok(shape) => Ok(shape), + Err(payload) => { + let message = if let Some(s) = payload.downcast_ref::<&str>() { + (*s).to_string() + } else if let Some(s) = payload.downcast_ref::() { + s.clone() + } else { + "(non-string panic payload)".to_string() + }; + Err(message) + } + } +} + pub(super) struct TyCollector<'tcx> { tcx: TyCtxt<'tcx>, pub types: TyMap, + pub layout_panics: Vec, resolved: HashSet, } impl TyCollector<'_> { - pub fn new(tcx: TyCtxt<'_>) -> TyCollector { + pub fn new(tcx: TyCtxt<'_>) -> TyCollector<'_> { TyCollector { tcx, types: HashMap::new(), + layout_panics: Vec::new(), resolved: HashSet::new(), } } } impl TyCollector<'_> { + /// Get layout for `ty`, recording a [`LayoutPanic`] if rustc panics. + fn layout_shape_or_record( + &mut self, + ty: &stable_mir::ty::Ty, + ) -> Option { + match try_layout_shape(ty) { + Ok(shape) => shape, + Err(message) => { + self.layout_panics.push(LayoutPanic { ty: *ty, message }); + None + } + } + } + #[inline(always)] fn visit_instance(&mut self, instance: Instance) -> ControlFlow<::Break> { let fn_abi = instance.fn_abi().unwrap(); @@ -63,7 +119,7 @@ impl Visitor for TyCollector<'_> { let control = self.visit_instance(instance); // Mirror other branches: record closure Ty only when traversal succeeds. if matches!(control, ControlFlow::Continue(_)) { - let maybe_layout_shape = ty.layout().ok().map(|layout| layout.shape()); + let maybe_layout_shape = self.layout_shape_or_record(ty); self.types.insert(*ty, (ty.kind(), maybe_layout_shape)); } control @@ -97,7 +153,7 @@ impl Visitor for TyCollector<'_> { let control = ty.super_visit(self); if matches!(control, ControlFlow::Continue(_)) { - let maybe_layout_shape = ty.layout().ok().map(|layout| layout.shape()); + let maybe_layout_shape = self.layout_shape_or_record(ty); self.types.insert(*ty, (ty.kind(), maybe_layout_shape)); fields.super_visit(self) } else { @@ -108,7 +164,7 @@ impl Visitor for TyCollector<'_> { let control = ty.super_visit(self); match control { ControlFlow::Continue(_) => { - let maybe_layout_shape = ty.layout().ok().map(|layout| layout.shape()); + let maybe_layout_shape = self.layout_shape_or_record(ty); self.types.insert(*ty, (ty.kind(), maybe_layout_shape)); control } diff --git a/tests/ui/ensure_rustc_commit.sh b/tests/ui/ensure_rustc_commit.sh index 5054b341..21f8e46f 100644 --- a/tests/ui/ensure_rustc_commit.sh +++ b/tests/ui/ensure_rustc_commit.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # # Ensures a rust checkout (regular or bare+worktree) is at the commit -# specified in rust-toolchain.toml's [metadata] rustc-commit field. +# that backs the active nightly toolchain (derived from `rustc -vV`). # # Usage: source this script after setting RUST_DIR to the repo root. # It sets RUST_SRC_DIR to the directory containing the source files @@ -11,21 +11,15 @@ set -u : "${RUST_DIR:?RUST_DIR must be set before sourcing ensure_rustc_commit.sh}" -# Require yq (mikefarah/yq) for TOML parsing -if ! command -v yq &> /dev/null; then - echo "Error: yq is required but not installed." - echo "Install via: brew install yq | apt install yq | nix shell nixpkgs#yq-go" - echo "See: https://github.com/mikefarah/yq#install" - exit 1 -fi - -_SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -_REPO_ROOT=$( cd -- "$_SCRIPT_DIR/../.." &> /dev/null && pwd ) - -# Read the expected rustc commit from rust-toolchain.toml -RUSTC_COMMIT=$(yq -r '.metadata.rustc-commit' "$_REPO_ROOT/rust-toolchain.toml") -if [ -z "$RUSTC_COMMIT" ] || [ "$RUSTC_COMMIT" = "null" ]; then - echo "Error: Could not read metadata.rustc-commit from $_REPO_ROOT/rust-toolchain.toml" +# Derive the rustc commit from the active toolchain; rust-toolchain.toml +# selects the nightly, so this stays in sync automatically. +# Run from the repo root so rustup picks up rust-toolchain.toml even when +# the caller's CWD is outside the repository. +_SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +_REPO_ROOT=$(cd -- "$_SCRIPT_DIR/../.." &>/dev/null && pwd) +RUSTC_COMMIT=$(cd "$_REPO_ROOT" && rustc -vV | grep 'commit-hash' | cut -d' ' -f2) +if [ -z "$RUSTC_COMMIT" ]; then + echo "Error: Could not determine rustc commit-hash from 'rustc -vV'" exit 1 fi @@ -48,6 +42,16 @@ if [ "$IS_BARE" = "true" ]; then exit 1 fi else + # Ensure the commit is available locally; fetch if needed. + if ! git -C "$RUST_DIR" cat-file -e "$RUSTC_COMMIT" 2>/dev/null; then + echo "Commit ${SHORT_COMMIT} not found locally; fetching..." + git -C "$RUST_DIR" fetch origin "$RUSTC_COMMIT" --quiet 2>/dev/null || \ + git -C "$RUST_DIR" fetch origin --quiet || { + echo "Error: Could not fetch commit ${RUSTC_COMMIT}." + echo "Ensure ${RUST_DIR} is a clone of https://github.com/rust-lang/rust" + exit 1 + } + fi echo "Creating worktree at ${WORKTREE_DIR} for commit ${SHORT_COMMIT}..." git -C "$RUST_DIR" worktree add "$WORKTREE_DIR" "$RUSTC_COMMIT" --detach --quiet || { echo "Error: Failed to create worktree for commit ${RUSTC_COMMIT} in ${RUST_DIR}" @@ -59,6 +63,16 @@ else # Regular repo: checkout the commit directly CURRENT_COMMIT=$(git -C "$RUST_DIR" rev-parse HEAD 2>/dev/null) if [ "${CURRENT_COMMIT}" != "${RUSTC_COMMIT}" ]; then + # Ensure the commit is available locally; fetch if needed. + if ! git -C "$RUST_DIR" cat-file -e "$RUSTC_COMMIT" 2>/dev/null; then + echo "Commit ${SHORT_COMMIT} not found locally; fetching..." + git -C "$RUST_DIR" fetch origin "$RUSTC_COMMIT" --quiet 2>/dev/null || \ + git -C "$RUST_DIR" fetch origin --quiet || { + echo "Error: Could not fetch commit ${RUSTC_COMMIT}." + echo "Ensure ${RUST_DIR} is a clone of https://github.com/rust-lang/rust" + exit 1 + } + fi echo "Checking out rustc commit ${SHORT_COMMIT} in ${RUST_DIR}..." git -C "$RUST_DIR" checkout "$RUSTC_COMMIT" --quiet || { echo "Error: Failed to checkout commit ${RUSTC_COMMIT} in ${RUST_DIR}"