diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25f9401..e1fa586 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,61 +1,49 @@ -name: Test +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true on: push: - branches: - - master - paths-ignore: - - '*.mkd' - - 'LICENSE' + branches: [master] + paths-ignore: ['*.mkd', 'LICENSE'] pull_request: types: [opened, reopened, synchronize] jobs: - native-test: - name: Test ${{ matrix.manifest }} on ${{ matrix.os }} with ${{ matrix.rust_toolchain }} and ${{ matrix.mode }} + test: + name: "Test: ${{ toJSON(matrix) }}" runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - rust_toolchain: [nightly, stable, 1.38.0] + rust_toolchain: [nightly, stable, 1.54.0] os: [ubuntu-latest, windows-latest, macOS-latest] mode: ['--release', '-Zminimal-versions', ''] - manifest: ['psm/Cargo.toml', 'Cargo.toml'] exclude: - rust_toolchain: stable mode: -Zminimal-versions - - rust_toolchain: 1.38.0 + - rust_toolchain: 1.54.0 mode: -Zminimal-versions timeout-minutes: 10 steps: - - uses: actions/checkout@v2 - - name: Install Rust ${{ matrix.rust_toolchain }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust_toolchain }} - profile: minimal - default: true - - name: Test ${{ matrix.manifest}} with ${{ matrix.mode }} - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=${{ matrix.manifest }} ${{ matrix.mode }} -- --nocapture - - name: Test ${{ matrix.manifest}} examples with ${{ matrix.mode }} - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path=${{ matrix.manifest }} ${{ matrix.mode }} --examples -- --nocapture + - uses: actions/checkout@v3 + - run: rustup install ${{ matrix.rust_toolchain }} --profile=minimal + - run: rustup default ${{ matrix.rust_toolchain }} + - run: cargo test --all ${{ matrix.mode }} -- --nocapture + - run: cargo test --all ${{ matrix.mode }} --examples -- --nocapture - clang-cl-test: - name: Test ${{ matrix.manifest }} on ${{ matrix.rust_target }} with ${{ matrix.clang_cl }} + test-windows: + name: "Test: ${{ toJSON(matrix) }}" runs-on: windows-latest strategy: fail-fast: false matrix: - manifest: ['psm/Cargo.toml', 'Cargo.toml'] + rust_toolchain: [stable] rust_target: - x86_64-pc-windows-msvc - i686-pc-windows-msvc + - x86_64-pc-windows-gnu + - i686-pc-windows-gnu include: - rust_target: x86_64-pc-windows-msvc clang_cl: C:/msys64/mingw64/bin/clang-cl.exe @@ -63,37 +51,6 @@ jobs: - rust_target: i686-pc-windows-msvc clang_cl: C:/msys64/mingw32/bin/clang-cl.exe package: mingw-w64-i686-clang - steps: - - uses: actions/checkout@v2 - - uses: msys2/setup-msys2@v2 - with: - release: false - install: ${{ matrix.package }} - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - default: true - target: ${{ matrix.rust_target }} - - uses: actions-rs/cargo@v1 - with: - command: test - args: --target=${{ matrix.rust_target }} --manifest-path=${{ matrix.manifest }} -- --nocapture - env: - CC: ${{ matrix.clang_cl }} - - windows-gnu-test: - name: Test ${{ matrix.manifest }} on ${{ matrix.rust_target }} with ${{ matrix.rust_toolchain }} - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - rust_toolchain: [nightly, stable] - rust_target: - - x86_64-pc-windows-gnu - - i686-pc-windows-gnu - manifest: ['psm/Cargo.toml', 'Cargo.toml'] - include: - rust_target: x86_64-pc-windows-gnu mingw_path: C:/msys64/mingw64/bin package: mingw-w64-x86_64-gcc @@ -101,137 +58,81 @@ jobs: mingw_path: C:/msys64/mingw32/bin package: mingw-w64-i686-gcc steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: msys2/setup-msys2@v2 with: release: false install: ${{ matrix.package }} - run: echo "c:/msys64/bin" | Out-File -FilePath $env:GITHUB_PATH -Append - run: echo "${{ matrix.mingw_path }}" | Out-File -FilePath $env:GITHUB_PATH -Append - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust_toolchain }} - profile: minimal - target: ${{ matrix.rust_target }} - default: true - - uses: actions-rs/cargo@v1 - with: - command: test - args: --target ${{ matrix.rust_target }} --manifest-path=${{ matrix.manifest }} + if: ${{ matrix.mingw_path }}" + - run: rustup install ${{ matrix.rust_toolchain }} --profile=minimal + - run: rustup default ${{ matrix.rust_toolchain }} + - run: rustup target add ${{ matrix.rust_target }} + - run: cargo test --target=${{ matrix.rust_target }} --all -- --nocapture + env: + CC: ${{ matrix.clang_cl }} - cross-linux-test: - name: Test ${{ matrix.manifest }} on ${{ matrix.rust_target }} with nightly ${{ matrix.mode }} + cross: + name: "Cross: ${{ toJSON(matrix) }}" runs-on: ubuntu-latest strategy: fail-fast: false matrix: + rust_toolchain: [stable, nightly] rust_target: - aarch64-linux-android - - arm-linux-androideabi - - armv7-linux-androideabi - - x86_64-linux-android - aarch64-unknown-linux-gnu + - arm-linux-androideabi - arm-unknown-linux-gnueabi + - armv7-linux-androideabi - armv7-unknown-linux-gnueabihf + - i686-unknown-freebsd - i686-unknown-linux-gnu - i686-unknown-linux-musl - - mips-unknown-linux-gnu - - mips64-unknown-linux-gnuabi64 - mips64el-unknown-linux-gnuabi64 + - mips64-unknown-linux-gnuabi64 - mipsel-unknown-linux-gnu + - mips-unknown-linux-gnu + - powerpc64le-unknown-linux-gnu + - powerpc64-unknown-linux-gnu - powerpc-unknown-linux-gnu - # https://github.com/rust-embedded/cross/pull/440 - # - powerpc64-unknown-linux-gnu + - s390x-unknown-linux-gnu + - sparc64-unknown-linux-gnu + - x86_64-linux-android + - x86_64-unknown-freebsd - x86_64-unknown-linux-musl - manifest: ['psm/Cargo.toml', 'Cargo.toml'] + - x86_64-unknown-netbsd mode: ['--release', '-Zminimal-versions', ''] - timeout-minutes: 10 - steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - target: ${{ matrix.rust_target }} - default: true - - name: Test - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: test - args: --target ${{ matrix.rust_target }} --manifest-path=${{ matrix.manifest }} ${{ matrix.mode }} -- --test-threads=1 --nocapture - native-build: - name: Build ${{ matrix.manifest }} to ${{ matrix.rust_target }} on nightly - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - rust_target: - # BSDs: could be tested with full system emulation - - i686-unknown-freebsd - - x86_64-unknown-freebsd - manifest: ['psm/Cargo.toml', 'Cargo.toml'] - timeout-minutes: 10 - steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - target: ${{ matrix.rust_target }} - default: true - - name: Build ${{ matrix.rust_target }} - uses: actions-rs/cargo@v1 - with: - command: build - args: --target ${{ matrix.rust_target }} --manifest-path=${{ matrix.manifest }} - - cross-build: - name: Cross-compile ${{ matrix.manifest }} to ${{ matrix.rust_target }} with cargo-cross - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - rust_target: - # https://github.com/rust-embedded/cross/issues/333 - - powerpc64le-unknown-linux-gnu - # FIXME: Testing hangs, should be verified once-in-a-while manually. - # could be made work by using full system emulation - # https://github.com/rust-embedded/cross/issues/242 - # - # Currently tested manually with full-system emulation. - - s390x-unknown-linux-gnu - # FIXME: tests could be made work by using full system emulation, maybe? - # - # Currently tested manually on real hardware. - # FIXME: https://github.com/rust-embedded/cross/pull/440 - # - sparc64-unknown-linux-gnu + exclude: + - mode: '-Zminimal-versions' + rust_toolchain: stable + include: # BSDs: could be tested with full system emulation - - x86_64-unknown-netbsd - manifest: ['psm/Cargo.toml', 'Cargo.toml'] + - rust_target: x86_64-unknown-netbsd + build_only: true + - rust_target: i686-unknown-freebsd + build_only: true + - rust_target: x86_64-unknown-freebsd + build_only: true timeout-minutes: 10 steps: - - uses: actions/checkout@v2 - - name: Install Rust nightly - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - target: ${{ matrix.rust_target }} - default: true - - name: Build ${{ matrix.rust_target }} - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --target ${{ matrix.rust_target }} --manifest-path=${{ matrix.manifest }} + - uses: actions/checkout@v3 + - run: rustup install ${{ matrix.rust_toolchain }} --profile=minimal + - run: rustup default ${{ matrix.rust_toolchain }} + - run: rustup target add ${{ matrix.rust_target }} + - run: | + mkdir -p "${{ runner.tool_cache }}/cross" + curl --fail -L 'https://github.com/cross-rs/cross/releases/download/v0.2.4/cross-x86_64-unknown-linux-gnu.tar.gz' | tar xzf - -C "${{ runner.tool_cache }}/cross" + echo "${{ runner.tool_cache }}/cross" >> $GITHUB_PATH + - run: cross build --target ${{ matrix.rust_target }} --all ${{ matrix.mode }} + if: matrix.build_only == true + - run: cross test --target ${{ matrix.rust_target }} --all ${{ matrix.mode }} -- --test-threads=1 --nocapture + if: matrix.build_only != true cross-ios-build: - name: Cross-compile ${{ matrix.manifest }} to ${{ matrix.rust_target }} on ${{ matrix.rust_toolchain }} + name: "Cross: ${{ toJSON(matrix) }}" runs-on: macos-latest strategy: fail-fast: false @@ -240,51 +141,37 @@ jobs: rust_target: - aarch64-apple-ios - x86_64-apple-ios - manifest: ['psm/Cargo.toml', 'Cargo.toml'] timeout-minutes: 10 steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust_toolchain }} - profile: minimal - target: ${{ matrix.rust_target }} - default: true - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --target=${{ matrix.rust_target }} --manifest-path=${{ matrix.manifest }} + - uses: actions/checkout@v3 + - run: rustup install ${{ matrix.rust_toolchain }} --profile=minimal + - run: rustup default ${{ matrix.rust_toolchain }} + - run: rustup target add ${{ matrix.rust_target }} + - run: cargo build --target=${{ matrix.rust_target }} --all cross-windows-build: - name: Cross-compile ${{ matrix.manifest }} for ${{ matrix.rust_target }} from x86_64-unknown-linux-gnu + name: "Cross: ${{ toJSON(matrix) }}" runs-on: ubuntu-20.04 strategy: fail-fast: true matrix: + rust_toolchain: [stable] rust_target: - x86_64-pc-windows-msvc - i686-pc-windows-msvc - manifest: ['psm/Cargo.toml', 'Cargo.toml'] xwin_version: ["0.1.6"] timeout-minutes: 10 steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - target: ${{ matrix.rust_target }} + - uses: actions/checkout@v3 + - run: rustup install ${{ matrix.rust_toolchain }} --profile=minimal + - run: rustup default ${{ matrix.rust_toolchain }} + - run: rustup target add ${{ matrix.rust_target }} - name: Add toolchain shims run: | - set -eux sudo ln -s clang-12 /usr/bin/clang-cl sudo ln -s llvm-ar-12 /usr/bin/llvm-lib sudo ln -s lld-link-12 /usr/bin/lld-link - - name: Install Windows SDK - run: | + - run: | set -eux xwin_version=${{ matrix.xwin_version }} xwin_prefix="xwin-$xwin_version-x86_64-unknown-linux-musl" @@ -294,7 +181,7 @@ jobs: # Splat the CRT and SDK files to /tmp/xwin/crt and /tmp/xwin/sdk respectively xwin --accept-license 1 splat --output /tmp/xwin - - name: Test + - run: cargo build --target ${{ matrix.rust_target }} --all env: CC: "clang-cl" CXX: "clang-cl" @@ -307,24 +194,22 @@ jobs: CFLAGS: "-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc/tmp/xwin/crt/include /imsvc/tmp/xwin/sdk/include/ucrt /imsvc/tmp/xwin/sdk/include/um /imsvc/tmp/xwin/sdk/include/shared" # Inform the linker where to search for libraries RUSTFLAGS: "-Lnative=/tmp/xwin/crt/lib/x86_64 -Lnative=/tmp/xwin/sdk/lib/um/x86_64 -Lnative=/tmp/xwin/sdk/lib/ucrt/x86_64" - run: | - set -eux - cargo build --target ${{ matrix.rust_target }} --manifest-path ${{ matrix.manifest }} - wasm-test: - name: Test stacker on WASM + test-wasm: + name: "Test: ${{ toJSON(matrix) }}" runs-on: ubuntu-latest + strategy: + matrix: + rust_toolchain: [stable] + rust_target: [wasm32-wasi] timeout-minutes: 10 steps: - - uses: actions/checkout@v1 - - name: Install Rust nightly - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - target: wasm32-wasi + - uses: actions/checkout@v3 + - run: rustup install ${{ matrix.rust_toolchain }} --profile=minimal + - run: rustup default ${{ matrix.rust_toolchain }} + - run: rustup target add ${{ matrix.rust_target }} - run: | curl -Lf https://github.com/bytecodealliance/wasmtime/releases/download/v0.19.0/wasmtime-v0.19.0-x86_64-linux.tar.xz | tar xJf - -C ${{ runner.tool_cache }} echo "${{ runner.tool_cache }}/wasmtime-v0.19.0-x86_64-linux" >> $GITHUB_PATH echo "CARGO_TARGET_WASM32_WASI_RUNNER=wasmtime run --" >> $GITHUB_ENV - - run: cargo test --target wasm32-wasi --all -- --nocapture + - run: cargo test --target ${{ matrix.rust_target }} --all -- --nocapture diff --git a/Cargo.toml b/Cargo.toml index 306d990..e065ea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,36 +1,2 @@ -[package] -name = "stacker" -version = "0.1.15" -authors = ["Alex Crichton ", "Simonas Kazlauskas "] -build = "build.rs" -license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/rust-lang/stacker" -homepage = "https://github.com/rust-lang/stacker" -documentation = "https://docs.rs/stacker/0.1.15" -description = """ -A stack growth library useful when implementing deeply recursive algorithms that -may accidentally blow the stack. -""" - -[lib] -name = "stacker" -doctest = false -test = false - -[dependencies] -cfg-if = "1.0.0" -libc = "0.2.45" -psm = { path = "psm", version = "0.1.7" } - -[target.'cfg(windows)'.dependencies.windows-sys] -version = ">=0.34.0, <0.42.0" -features = [ - "Win32_System_Memory", - "Win32_System_Threading", - "Win32_Foundation", -] - - -[build-dependencies] -cc = "1.0.2" +[workspace] +members = ["stacker", "psm"] diff --git a/Cross.toml b/Cross.toml index 7e330ab..5df355a 100644 --- a/Cross.toml +++ b/Cross.toml @@ -1,2 +1,2 @@ -[target.x86_64-linux-android] -image = "rustembedded/cross:x86_64-linux-android" +[target.x86_64-unknown-freebsd.env] +passthrough = ["AR_x86_64_unknown_freebsd=x86_64-unknown-freebsd12-ar"] diff --git a/README.md b/README.md deleted file mode 100644 index 94858e4..0000000 --- a/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# stacker - -[![Build Status](https://github.com/rust-lang/stacker/workflows/Test%20stacker/badge.svg)](https://github.com/rust-lang/stacker/actions) - -[Documentation](https://docs.rs/stacker) - -A stack-growth library for Rust. Enables annotating fixed points in programs -where the stack may want to grow larger. Spills over to the heap if the stack -has hit its limit. - -This library is intended on helping implement recursive algorithms. - -```toml -# Cargo.toml -[dependencies] -stacker = "0.1" -``` - -## Platform Support - -This library currently uses psm for its cross platform capabilities, with a notable exception of -Windows, which uses an implementation based on Fibers. See the README for psm for the support -table. - -On all unsupported platforms this library is a noop. It should compile and run, but it -won't actually grow the stack and code will continue to hit the guard pages -typically in place. - -# License - -This project is licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - https://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or - https://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in this project by you, as defined in the Apache-2.0 license, -shall be dual licensed as above, without any additional terms or conditions. diff --git a/README.mkd b/README.mkd new file mode 100644 index 0000000..db60909 --- /dev/null +++ b/README.mkd @@ -0,0 +1,20 @@ +
+

stacker & psm

+
+ +Check out the README for corresponding crates for further information on each crate. + +# License + +This project is licensed under either of + + * [Apache License, Version 2.0]() + * [MIT license]() + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this project by you, as defined in the Apache-2.0 license, +shall be dual licensed as above, without any additional terms or conditions. diff --git a/psm/Cargo.toml b/psm/Cargo.toml index 9a3c56a..c5a1a30 100644 --- a/psm/Cargo.toml +++ b/psm/Cargo.toml @@ -10,7 +10,14 @@ repository = "https://github.com/rust-lang/stacker/" documentation = "https://docs.rs/psm/0.1.20" readme = "README.mkd" +[lib] +harness = false + [dependencies] +cfg-if = "1.0.0" [build-dependencies] cc = "1.0.2" + +[dev-dependencies] +backtrace = "0.3" diff --git a/psm/README.mkd b/psm/README.mkd index 5bc8957..f797aba 100644 --- a/psm/README.mkd +++ b/psm/README.mkd @@ -1,31 +1,39 @@ -# Portable Stack Manipulation - -This crate provides very portable functions to control the stack pointer and inspect the properties -of the stack. This crate does not attempt to provide safe abstractions to any operations, the -only goals are correctness, portability and efficiency (in that exact order). As a consequence most -functions you’ll see in this crate are unsafe. - -Unless you’re writing a safe abstraction over stack manipulation, this is not the crate you -want. Instead consider one of the safe abstractions over this crate. A good place to look at is -the crates.io’s reverse dependency list. +
+

psm

+

+ A portable library of stack introspection and manipulation operations +

+
+ +This crate provides functions to control the stack pointer and inspect the properties of the stack. +This crate does not attempt to provide safe or Rust-idiomatic abstractions to any of the +operations. The only goals are correctness, portability and efficiency (in this exact order). As a +consequence most functions you’ll see in this crate are unsafe and require the caller to maintain a +variety of invariants. + +Unless you are writing a safe abstraction over stack manipulation or need extremely precse control +over your stack, this is probably not the crate you want. Instead consider one of the safe +abstractions based on this crate. A good place to look at is the crates.io’s reverse dependency +list. # Platform support -The following table lists supported targets and architectures with notes on the level of current -support and knowledge about the target. The three columns “Available”, “Tested” and “Callstack” -imply an increasingly high level of support. +The following table lists targets and architectures supported by this crate, alongside the notes on +the current level of support and behaviour. The three columns “Available”, “Tested” and “Callstack” +indicate different degrees of support: -* “Available” basically means that the code builds and the assembly files have been written for the - target; -* “Tested” means that the assembly code has been tested or otherwise verified to be correct. For - most targets it also means that continuous integration is set up; -* “Callstack” means that the assembly code has been written with due care to support unwinding the - stack and displaying the call frames (i.e. `gdb backtrace` works as expected). +* On platforms marked “available” this library builds and contains necessary functionality to + introspect or manipulate the stack (see the `psm_stack_information` and `psm_stack_manipulation` + macros); +* “Tested” platforms have had their implementations tested or otherwise verified to be correct. For + many such targets it also means that we have a continuous integration setup; +* “Callstack” indicates that, in addition to the library being tested, it has been verified + functions based on stack unwinding (e.g. `gdb backtrace`) continue to work correctly. + - - + @@ -510,11 +518,9 @@ The assembly code for loongarch64 has been tested locally with a C caller. # License -PSM is licensed under either of +`psm` is licensed under either of - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - https://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or - https://opensource.org/licenses/MIT) + * [Apache License, Version 2.0]() + * [MIT license]() at your option. diff --git a/psm/build.rs b/psm/build.rs index 9d40212..76dc313 100644 --- a/psm/build.rs +++ b/psm/build.rs @@ -12,30 +12,16 @@ fn find_assembly( // is not supported in Windows. For x86_64 the implementation actually works locally, // but failed tests in CI (???). Might want to have a feature for experimental support // here. - ("x86", _, "windows", _) => { - if masm { - Some(("src/arch/x86_msvc.asm", false)) - } else { - Some(("src/arch/x86_windows_gnu.s", false)) - } - } - ("x86_64", _, "windows", _) => { - if masm { - Some(("src/arch/x86_64_msvc.asm", false)) - } else { - Some(("src/arch/x86_64_windows_gnu.s", false)) - } - } - ("arm", _, "windows", "msvc") => Some(("src/arch/arm_armasm.asm", false)), - ("aarch64", _, "windows", _) => { - if masm { - Some(("src/arch/aarch64_armasm.asm", false)) - } else { - Some(("src/arch/aarch_aapcs64.s", false)) - } - } - ("x86", _, _, _) => Some(("src/arch/x86.s", true)), - ("x86_64", _, _, _) => Some(("src/arch/x86_64.s", true)), + + // ("x86", _, "windows", _) if masm => Some(("src/arch/x86_msvc.asm", true)), + // ("x86", _, "windows", _) => Some(("src/arch/x86_windows_gnu.s", true)), + // ("x86_64", _, "windows", _) if masm => Some(("src/arch/x86_64_msvc.asm", true)), + // ("x86_64", _, "windows", _) => Some(("src/arch/x86_64_windows_gnu.s", true)), + // ("arm", _, "windows", "msvc") => Some(("src/arch/arm_armasm.asm", true)), + ("aarch64", _, "windows", _) if masm => Some(("src/arch/aarch64_armasm.asm", true)), + ("aarch64", _, "windows", _) => Some(("src/arch/aarch_aapcs64.s", true)), + // ("x86", _, _, _) => Some(("src/arch/x86.s", false)), + // ("x86_64", _, _, _) => Some(("src/arch/x86_64.s", false)), ("arm", _, _, _) => Some(("src/arch/arm_aapcs.s", true)), ("aarch64", _, _, _) => Some(("src/arch/aarch_aapcs64.s", true)), ("powerpc", _, _, _) => Some(("src/arch/powerpc32.s", true)), diff --git a/psm/examples/panics.rs b/psm/examples/panics.rs deleted file mode 100644 index ada658d..0000000 --- a/psm/examples/panics.rs +++ /dev/null @@ -1,52 +0,0 @@ -extern crate psm; - -use std::panic; - -const CHAIN_DEPTH: usize = 16; - -psm::psm_stack_manipulation! { - yes { - use std::alloc; - const STACK_ALIGN: usize = 4096; - // Generating backraces (because of RUST_BACKTRACE) create a few quite large frames, so it is - // important, that all frames have sufficient amount of available memory to not run over the - // stack... - const FRAME_SIZE: usize = 4096 * 10; - - fn panic_chain(depth: usize) { - if depth == 0 { - panic!("full chain!"); - } else { - unsafe { - let layout = alloc::Layout::from_size_align(FRAME_SIZE, STACK_ALIGN).unwrap(); - let new_stack = alloc::alloc(layout); - assert!(!new_stack.is_null(), "allocations must succeed!"); - let p = psm::on_stack(new_stack, FRAME_SIZE, || { - panic::catch_unwind(|| { - panic_chain(depth - 1); - }) - }); - alloc::dealloc(new_stack, layout); - p.map_err(panic::resume_unwind).unwrap() - } - } - } - - fn main() { - panic_chain(CHAIN_DEPTH); - } - - #[test] - fn run_example() { - assert!(panic::catch_unwind(|| { - panic_chain(CHAIN_DEPTH); - }).is_err(), "Panic did not propagate!"); - } - } - - no { - fn main() { - eprintln!("Stack manipulation not supported by this target"); - } - } -} diff --git a/psm/examples/thread.rs b/psm/examples/thread.rs deleted file mode 100644 index eb335a5..0000000 --- a/psm/examples/thread.rs +++ /dev/null @@ -1,60 +0,0 @@ -extern crate psm; - -psm::psm_stack_manipulation! { - yes { - use std::alloc; - - const STACK_ALIGN: usize = 4096; - const FRAME_SIZE: usize = 4096; - const FIB_COUNTS: [(usize, u64); 3] = [ - (8, 21), - (16, 987), - (24, 46368), - ]; - - #[inline(never)] - fn fib(n: usize) -> u64 { - unsafe { - let layout = alloc::Layout::from_size_align(FRAME_SIZE, STACK_ALIGN).unwrap(); - let new_stack = alloc::alloc(layout); - assert!(!new_stack.is_null(), "allocations must succeed!"); - let r = match n { - 0 => 0, - 1 => 1, - _ => { - psm::on_stack(new_stack, FRAME_SIZE, || { - fib(n - 1) + fib(n - 2) - }) - } - }; - alloc::dealloc(new_stack, layout); - r - } - } - - fn main() { - for (n, expected, handle) in FIB_COUNTS.iter().map(|&(n, expected)| - (n, expected, std::thread::spawn(move || { - fib(n) - })) - ) { - if let Ok(res) = handle.join() { - assert_eq!(res, expected); - println!("fib({}) = {}", n, res); - } else { - panic!("joining a thread returned an Err"); - } - } - } - } - no { - fn main() { - eprintln!("Stack manipulation not supported by this target"); - } - } -} - -#[test] -fn run_example() { - main() -} diff --git a/psm/src/arch/aarch64.rs b/psm/src/arch/aarch64.rs new file mode 100644 index 0000000..65e27dc --- /dev/null +++ b/psm/src/arch/aarch64.rs @@ -0,0 +1,70 @@ +pub(crate) fn stack_direction() -> crate::StackDirection { + crate::StackDirection::Descending +} + +pub(crate) fn stack_pointer() -> *mut u8 { + let mut ret; + unsafe { + core::arch::asm! { + "mov {ret}, sp", + ret = lateout(reg) ret, + options(preserves_flags, nomem), + } + } + ret +} + +pub(crate) unsafe extern "C" fn replace_stack( + data: usize, + callback: unsafe extern "C" fn(usize) -> !, + sp: *mut u8, + _: *mut u8, +) -> ! { + core::arch::asm! { + "mov sp, {new_sp}", + "br {callback}", + "ud2", + new_sp = in(reg) sp, + callback = in(reg) callback, + in("r0") data, + options(noreturn, nostack), + } +} + +core::arch::global_asm! { + ".balign 8", + ".local rust_psm_on_stack", + ".hidden rust_psm_on_stack", + ".type rust_psm_on_stack,@function", + "rust_psm_on_stack:", + ".cfi_startproc", + "stp fp, lr, [sp, #-16]!" + ".cfi_offset lr, -8", + ".cfi_offset fp, -16", + "mov fp, sp", + ".cfi_def_cfa_register fp", + "mov sp, x3", + "blr x2", + "mov sp, fp", + ".cfi_def_cfa_register sp", + "ldp fp, lr, [fp], #16", + "ret", + ".cfi_endproc", +} + +pub(crate) unsafe extern "C" fn on_stack( + data: usize, + return_ptr: usize, + callback: unsafe extern "C" fn(usize, usize), + sp: *mut u8, + _: *mut u8, +) { + core::arch::asm! { + "call rust_psm_on_stack", + in("x0") data, + in("x1") return_ptr, + in("x2") callback, + in("x3") sp, + clobber_abi("C"), + } +} diff --git a/psm/src/arch/arm.rs b/psm/src/arch/arm.rs new file mode 100644 index 0000000..1782f22 --- /dev/null +++ b/psm/src/arch/arm.rs @@ -0,0 +1,71 @@ +pub(crate) fn stack_direction() -> crate::StackDirection { + crate::StackDirection::Descending +} + +pub(crate) fn stack_pointer() -> *mut u8 { + let mut ret; + unsafe { + core::arch::asm! { + "mov {ret}, sp", + ret = lateout(reg) ret, + options(preserves_flags, nomem), + } + } + ret +} + +pub(crate) unsafe extern "aapcs" fn replace_stack( + data: usize, + callback: unsafe extern "aapcs" fn(usize) -> !, + sp: *mut u8, + _: *mut u8, +) -> ! { + core::arch::asm! { + "mov sp, {new_sp}", + "bx {callback}", + "ud2", + new_sp = in(reg) sp, + callback = in(reg) callback, + in("r0") data, + options(noreturn, nostack), + } +} + +core::arch::global_asm! { + ".balign 8", + ".local rust_psm_on_stack", + ".hidden rust_psm_on_stack", + ".type rust_psm_on_stack,@function", + "rust_psm_on_stack:", + ".fnstart", + ".cfi_startproc", + "push {fp, lr}", + ".cfi_def_cfa_offset 8", + ".cfi_offset lr, -4", + ".cfi_offset fp, -8", + "mov fp, sp", + ".cfi_def_cfa_register fp", + "mov sp, r3", + "blx r2", + "mov sp, fp", + "pop {fp, pc}", + ".cfi_endproc", + ".fnend", +} + +pub(crate) unsafe extern "aapcs" fn on_stack( + data: usize, + return_ptr: usize, + callback: unsafe extern "aapcs" fn(usize, usize), + sp: *mut u8, + _: *mut u8, +) { + core::arch::asm! { + "call rust_psm_on_stack", + in("r0") data, + in("r1") return_ptr, + in("r2") callback, + in("r3") sp, + clobber_abi("aapcs"), + } +} diff --git a/psm/src/arch/mod.rs b/psm/src/arch/mod.rs new file mode 100644 index 0000000..6d2580d --- /dev/null +++ b/psm/src/arch/mod.rs @@ -0,0 +1,27 @@ +cfg_if::cfg_if! { + if #[cfg(asm)] { + compile_error!("not implemented yet"); + } else if #[cfg(all(target_arch="x86", target_os="windows"))] { + #[path = "x86_windows.rs"] + mod imp; + } else if #[cfg(target_arch="x86")] { + #[path = "x86.rs"] + mod imp; + } else if #[cfg(all(target_arch="x86_64", target_os="windows"))] { + #[path = "x86_64_windows.rs"] + mod imp; + } else if #[cfg(target_arch="x86_64")] { + #[path = "x86_64.rs"] + mod imp; + } else if #[cfg(target_arch="arm")] { + #[path = "arm.rs"] + mod imp; + } else if #[cfg(target_arch="aarch64")] { + #[path = "aarch64.rs"] + mod imp; + } else { + compile_error!("Target is not supported by the `psm` crate!"); + } +} + +pub(crate) use self::imp::*; diff --git a/psm/src/arch/x86.rs b/psm/src/arch/x86.rs new file mode 100644 index 0000000..4617b57 --- /dev/null +++ b/psm/src/arch/x86.rs @@ -0,0 +1,63 @@ +pub(crate) fn stack_direction() -> crate::StackDirection { + crate::StackDirection::Descending +} + +pub(crate) fn stack_pointer() -> *mut u8 { + let mut ret; + unsafe { + core::arch::asm! { + "mov {ret}, esp", + ret = lateout(reg) ret, + options(preserves_flags, nomem), + } + } + ret +} + +pub(crate) unsafe fn replace_stack( + data: usize, + callback: unsafe extern "fastcall" fn(usize) -> !, + sp: *mut u8, + _: *mut u8, +) -> ! { + core::arch::asm! { + "lea esp, [{sp} - 12]", + "jmp {callback}", + sp = in(reg) sp, + callback = in(reg) callback, + in("ecx") data, + options(noreturn, nostack), + } +} + +core::arch::global_asm! { + ".balign 16", + ".local rust_psm_on_stack", + ".hidden rust_psm_on_stack", + ".type rust_psm_on_stack,@function", + "rust_psm_on_stack:", + ".cfi_startproc", + "xchg esp, edi", + ".cfi_def_cfa_register edi", + "call eax", + "mov esp, edi", + "ret", + ".cfi_endproc", +} + +pub(crate) unsafe fn on_stack( + data: usize, + return_ptr: usize, + callback: unsafe extern "fastcall" fn(usize, usize), + sp: *mut u8, + _: *mut u8, +) { + core::arch::asm! { + "call rust_psm_on_stack", + in("ecx") data, + in("edx") return_ptr, + in("eax") callback, + inout("edi") sp => _, + clobber_abi("fastcall"), + } +} diff --git a/psm/src/arch/x86_64.rs b/psm/src/arch/x86_64.rs new file mode 100644 index 0000000..913baa4 --- /dev/null +++ b/psm/src/arch/x86_64.rs @@ -0,0 +1,63 @@ +pub(crate) fn stack_direction() -> crate::StackDirection { + crate::StackDirection::Descending +} + +pub(crate) fn stack_pointer() -> *mut u8 { + let mut ret; + unsafe { + core::arch::asm! { + "mov {ret}, rsp", + ret = lateout(reg) ret, + options(preserves_flags, nomem), + } + } + ret +} + +pub(crate) unsafe fn replace_stack( + data: usize, + callback: unsafe extern "sysv64" fn(usize) -> !, + sp: *mut u8, + _: *mut u8, +) -> ! { + core::arch::asm! { + "lea rsp, [{sp} - 8]", + "jmp {callback}", + sp = in(reg) sp, + callback = in(reg) callback, + in("rdi") data, + options(noreturn, nostack), + } +} + +core::arch::global_asm! { + ".balign 16", + ".local rust_psm_on_stack", + ".hidden rust_psm_on_stack", + ".type rust_psm_on_stack,@function", + "rust_psm_on_stack:", + ".cfi_startproc", + "xchg rsp, r12", + ".cfi_def_cfa_register r12", + "call rdx", + "mov rsp, r12", + "ret", + ".cfi_endproc", +} + +pub(crate) unsafe fn on_stack( + data: usize, + return_ptr: usize, + callback: unsafe extern "sysv64" fn(usize, usize), + sp: *mut u8, + _: *mut u8, +) { + core::arch::asm! { + "call rust_psm_on_stack", + in("rdi") data, + in("rsi") return_ptr, + in("rdx") callback, + inout("r12") sp => _, + clobber_abi("sysv64"), + } +} diff --git a/psm/src/arch/x86_64_msvc.asm b/psm/src/arch/x86_64_msvc.asm index 67d7283..c7b5fc8 100644 --- a/psm/src/arch/x86_64_msvc.asm +++ b/psm/src/arch/x86_64_msvc.asm @@ -19,10 +19,15 @@ rust_psm_stack_pointer ENDP ; extern "sysv64" fn(%rdi: usize, %rsi: extern "sysv64" fn(usize), %rdx: *mut u8, %rcx: *mut u8) rust_psm_replace_stack PROC - mov gs:[08h], rdx - mov gs:[10h], rcx + mov qword ptr gs:[00h], -1 + mov qword ptr gs:[10h], rcx + mov qword ptr gs:[08h], rdx + mov qword ptr gs:[1478h], rcx + mov qword ptr gs:[1748h], 0 + lea rsp, [rdx - 8] jmp rsi + ud2 rust_psm_replace_stack ENDP ; extern "sysv64" fn(%rdi: usize, %rsi: usize, @@ -34,25 +39,42 @@ rust_psm_replace_stack ENDP ; ; This necessitates an API difference from the usual 4-argument signature used elsewhere. ; -; FIXME: this needs a catch-all exception handler that aborts in case somebody unwinds into here. +; FIXME: this needs a catch-all exception handler that aborts in case anything unwinds into here. rust_psm_on_stack PROC FRAME - push rbp + push qword ptr rbp .pushreg rbp + push qword ptr gs:[1748h] ; GuaranteedStackBytes + .allocstack 8 + push qword ptr gs:[1478h] ; DeallocationStack + .allocstack 8 + push qword ptr gs:[10h] ; StackLimit + .allocstack 8 + push qword ptr gs:[08h] ; StackBase + .allocstack 8 + push qword ptr gs:[00h] ; ExceptionList + .allocstack 8 mov rbp, rsp .setframe rbp, 0 .endprolog - push gs:[08h] - mov gs:[08h], rcx - push gs:[10h] - mov gs:[10h], r8 mov rsp, rcx + + mov qword ptr gs:[00h], -1 + mov qword ptr gs:[08h], rcx + mov qword ptr gs:[10h], r8 + ; TODO: these need a more proper handling + mov qword ptr gs:[1478h], rcx + mov qword ptr gs:[1748h], 0 + call rdx - lea rsp, [rbp - 010h] - pop gs:[10h] - pop gs:[08h] - pop rbp + mov rsp, rbp + pop qword ptr gs:[00h] + pop qword ptr gs:[08h] + pop qword ptr gs:[10h] + pop qword ptr gs:[1478h] + pop qword ptr gs:[1748h] + pop qword ptr rbp ret rust_psm_on_stack ENDP diff --git a/psm/src/arch/x86_64_windows.rs b/psm/src/arch/x86_64_windows.rs new file mode 100644 index 0000000..cae4a8c --- /dev/null +++ b/psm/src/arch/x86_64_windows.rs @@ -0,0 +1,92 @@ +pub(crate) fn stack_direction() -> crate::StackDirection { + crate::StackDirection::Descending +} + +pub(crate) fn stack_pointer() -> *mut u8 { + let mut ret; + unsafe { + core::arch::asm! { + "mov {ret}, rsp", + ret = lateout(reg) ret, + options(preserves_flags, nomem), + } + } + ret +} + +pub(crate) unsafe fn replace_stack( + data: usize, + callback: unsafe extern "sysv64" fn(usize) -> !, + sp: *mut u8, + _: *mut u8, +) -> ! { + core::arch::asm! { + "mov qword ptr gs:[0x0], -1", + "mov qword ptr gs:[0x8], rdx", + "mov qword ptr gs:[0x10], rcx", + "mov qword ptr gs:[0x1478], rcx", + "mov qword ptr gs:[0x1748], 0", + "lea rsp, [{sp} - 8]", + "jmp {callback}", + sp = in(reg) sp, + callback = in(reg) callback, + in("rdi") data, + options(noreturn, nostack), + } +} + +core::arch::global_asm! { + ".balign 16", + "rust_psm_on_stack:", + ".seh_proc rust_psm_on_stack", + + "push qword ptr gs:[0x1748]", // GuaranteedStackBytes + ".seh_stackalloc 8", + "push qword ptr gs:[0x1478]", // DeallocationStack + ".seh_stackalloc 8", + "push qword ptr gs:[0x10]", // StackLimit + ".seh_stackalloc 8", + "push qword ptr gs:[0x08]", // StackBase + ".seh_stackalloc 8", + "push qword ptr gs:[0x00]", // ExceptionList + ".seh_stackalloc 8", + "xchg rsp, r12", + ".seh_setframe r12, 0", + ".seh_endprologue", + + "mov qword ptr gs:[0x00], -1", + "mov qword ptr gs:[0x08], rsp", + "mov qword ptr gs:[0x10], r8", + // TODO: these need a more proper handling + "mov qword ptr gs:[0x1478], rsp", + "mov qword ptr gs:[0x1748], 0", + + "call rdx", + + // Reset the state. + "mov rsp, r12", + "pop qword ptr gs:[0x00]", + "pop qword ptr gs:[0x08]", + "pop qword ptr gs:[0x10]", + "pop qword ptr gs:[0x1478]", + "pop qword ptr gs:[0x1748]", + "ret", + ".seh_endproc", +} + +pub(crate) unsafe fn on_stack( + data: usize, + return_ptr: usize, + callback: unsafe extern "sysv64" fn(usize, usize), + sp: *mut u8, + _: *mut u8, +) { + core::arch::asm! { + "call rust_psm_on_stack", + in("rdi") data, + in("rsi") return_ptr, + in("rdx") callback, + inout("r12") sp => _, + clobber_abi("sysv64"), + } +} diff --git a/psm/src/arch/x86_windows.rs b/psm/src/arch/x86_windows.rs new file mode 100644 index 0000000..4617b57 --- /dev/null +++ b/psm/src/arch/x86_windows.rs @@ -0,0 +1,63 @@ +pub(crate) fn stack_direction() -> crate::StackDirection { + crate::StackDirection::Descending +} + +pub(crate) fn stack_pointer() -> *mut u8 { + let mut ret; + unsafe { + core::arch::asm! { + "mov {ret}, esp", + ret = lateout(reg) ret, + options(preserves_flags, nomem), + } + } + ret +} + +pub(crate) unsafe fn replace_stack( + data: usize, + callback: unsafe extern "fastcall" fn(usize) -> !, + sp: *mut u8, + _: *mut u8, +) -> ! { + core::arch::asm! { + "lea esp, [{sp} - 12]", + "jmp {callback}", + sp = in(reg) sp, + callback = in(reg) callback, + in("ecx") data, + options(noreturn, nostack), + } +} + +core::arch::global_asm! { + ".balign 16", + ".local rust_psm_on_stack", + ".hidden rust_psm_on_stack", + ".type rust_psm_on_stack,@function", + "rust_psm_on_stack:", + ".cfi_startproc", + "xchg esp, edi", + ".cfi_def_cfa_register edi", + "call eax", + "mov esp, edi", + "ret", + ".cfi_endproc", +} + +pub(crate) unsafe fn on_stack( + data: usize, + return_ptr: usize, + callback: unsafe extern "fastcall" fn(usize, usize), + sp: *mut u8, + _: *mut u8, +) { + core::arch::asm! { + "call rust_psm_on_stack", + in("ecx") data, + in("edx") return_ptr, + in("eax") callback, + inout("edi") sp => _, + clobber_abi("fastcall"), + } +} diff --git a/psm/src/lib.rs b/psm/src/lib.rs index b9050c8..27b91fb 100644 --- a/psm/src/lib.rs +++ b/psm/src/lib.rs @@ -1,16 +1,19 @@ -//! # **P**ortable **S**tack **M**anipulation -//! This crate provides portable functions to control the stack pointer and inspect the properties -//! of the stack. This crate does not attempt to provide safe abstractions to any operations, the -//! only goals are correctness, portability and efficiency (in that exact order). As a consequence -//! most functions you will find in this crate are unsafe. -//! -//! Note, that the stack allocation is left up to the user. Unless you’re writing a safe -//! abstraction over stack manipulation, this is unlikely to be the crate you want. Instead -//! consider one of the safe abstractions over this crate such as `stacker`. Another good place to -//! look at is the crates.io’s reverse dependency list. - +#![doc=include_str!("../README.mkd")] #![allow(unused_macros)] -#![no_std] +#![cfg_attr(not(test), no_std)] + +#[cfg(test)] +extern crate core; + +#[path = "tests.rs"] +mod tests; + +#[cfg(test)] +fn main() { + tests::run(); +} + +mod arch; macro_rules! extern_item { (unsafe $($toks: tt)+) => { @@ -177,7 +180,6 @@ unsafe fn rust_psm_on_stack( /// println!("4 + 4 = {} has been calculated on stack {:p}", result, stack); /// } /// ``` -#[cfg(switchable_stack)] pub unsafe fn on_stack R>(base: *mut u8, size: usize, callback: F) -> R { use core::mem::MaybeUninit; @@ -196,13 +198,20 @@ pub unsafe fn on_stack R>(base: *mut u8, size: usize, callback }; let mut callback: MaybeUninit = MaybeUninit::new(callback); let mut return_value: MaybeUninit = MaybeUninit::uninit(); - rust_psm_on_stack( + arch::on_stack( &mut callback as *mut MaybeUninit as usize, &mut return_value as *mut MaybeUninit as usize, with_on_stack::, sp, base, ); + // rust_psm_on_stack( + // &mut callback as *mut MaybeUninit as usize, + // &mut return_value as *mut MaybeUninit as usize, + // with_on_stack::, + // sp, + // base, + // ); return return_value.assume_init(); } @@ -249,7 +258,6 @@ pub unsafe fn on_stack R>(base: *mut u8, size: usize, callback /// /// `callback` must not return (not enforced by typesystem currently because `!` is unstable), /// unwind or otherwise return control flow to any of the previous frames. -#[cfg(switchable_stack)] pub unsafe fn replace_stack(base: *mut u8, size: usize, callback: F) -> ! { extern_item! { unsafe fn with_replaced_stack(d: usize) -> ! { // Safe to move out, because the closure is essentially forgotten by @@ -261,12 +269,12 @@ pub unsafe fn replace_stack(base: *mut u8, size: usize, callback: F StackDirection::Ascending => base, StackDirection::Descending => base.offset(size as isize), }; - rust_psm_replace_stack( + arch::replace_stack( &callback as *const F as usize, with_replaced_stack::, sp, base, - ); + ) } /// The direction into which stack grows as stack frames are made. @@ -281,17 +289,17 @@ pub enum StackDirection { impl StackDirection { /// Obtain the stack growth direction. - #[cfg(asm)] pub fn new() -> StackDirection { - const ASC: u8 = StackDirection::Ascending as u8; - const DSC: u8 = StackDirection::Descending as u8; - unsafe { - match rust_psm_stack_direction() { - ASC => StackDirection::Ascending, - DSC => StackDirection::Descending, - _ => ::core::hint::unreachable_unchecked(), - } - } + arch::stack_direction() + // const ASC: u8 = StackDirection::Ascending as u8; + // const DSC: u8 = StackDirection::Descending as u8; + // unsafe { + // match rust_psm_stack_direction() { + // ASC => StackDirection::Ascending, + // DSC => StackDirection::Descending, + // _ => ::core::hint::unreachable_unchecked(), + // } + // } } } @@ -314,9 +322,8 @@ impl StackDirection { /// padding applied; /// 2. Callee allocates more stack than was accounted for with padding, and accesses pages outside /// the stack, invalidating the execution (by e.g. crashing). -#[cfg(asm)] pub fn stack_pointer() -> *mut u8 { - unsafe { rust_psm_stack_pointer() } + arch::stack_pointer() } /// Macro that outputs its tokens only if `psm::on_stack` and `psm::replace_stack` are available. diff --git a/psm/src/tests.rs b/psm/src/tests.rs new file mode 100644 index 0000000..3085c92 --- /dev/null +++ b/psm/src/tests.rs @@ -0,0 +1,237 @@ +#![cfg(test)] + +macro_rules! tests { + ($($(#[$meta:meta])* fn $name:ident() { $($body:tt)* })*) => { + $( + $(#[$meta])* + fn $name() { + $($body)* + } + + )* + + static TESTS: &[(&'static str, fn())] = &[$((stringify!($name), $name)),*]; + } +} + +fn ptr_distance(a: *const (), b: *const ()) -> usize { + (a as isize).wrapping_sub(b as isize).abs() as usize +} + +fn alloc_stack(size: usize) -> *mut u8 { + const STACK_ALIGN: usize = 4096; + unsafe { + let layout = std::alloc::Layout::from_size_align(size, STACK_ALIGN).unwrap(); + let new_stack = std::alloc::alloc(layout); + assert!(!new_stack.is_null(), "allocations must succeed!"); + new_stack + } +} + +#[no_mangle] +#[inline(never)] +fn rust_psm_test_get_bt() -> backtrace::Backtrace { + backtrace::Backtrace::new() +} + +fn find_frame_name(bt: &backtrace::Backtrace, name: &[u8]) -> Option<(usize, usize)> { + for (frame_idx, frame) in bt.frames().into_iter().enumerate() { + for (symbol_idx, symbol) in frame.symbols().into_iter().enumerate() { + if symbol.name().map(|n| n.as_bytes()) == Some(name) { + return Some((frame_idx, symbol_idx)); + } + } + } + None +} + +tests! { + fn stack_direction_always_equal() { + assert_eq!(crate::StackDirection::new(), crate::StackDirection::new()); + } + + fn stack_direction_is_correct() { + #[inline(never)] + fn test_direction(previous_sp: *mut u8) { + let current_sp = crate::stack_pointer(); + match crate::StackDirection::new() { + crate::StackDirection::Ascending => { + assert!( + current_sp > previous_sp, + "the stack pointer is not ascending! current = {:p}, previous = {:p}", + current_sp, + previous_sp + ); + } + crate::StackDirection::Descending => { + assert!( + current_sp < previous_sp, + "the stack pointer is not descending! current = {:p}, previous = {:p}", + current_sp, + previous_sp + ); + } + } + } + test_direction(crate::stack_pointer()); + } + + fn basic_on_stack() { + unsafe { + let new_stack = alloc_stack(4096); + let r = crate::on_stack(new_stack, 4096, || (crate::stack_pointer(), 42 + 42_0000)); + assert_eq!(r.1, 42_0042); + assert!(ptr_distance(crate::stack_pointer() as _, r.0 as _) > 0x100_0000); + assert!(ptr_distance(new_stack as _, r.0 as _) < 4096); + } + } + + #[inline(never)] + fn on_stack_basic_backtrace() { + let bt = unsafe { + let new_stack = alloc_stack(128 * 4096); + crate::on_stack(new_stack, 128 * 4096, rust_psm_test_get_bt) + }; + + let test_get_bt_frame = find_frame_name(&bt, b"rust_psm_test_get_bt"); + assert!(test_get_bt_frame.is_some()); + assert!(bt.frames().len() > test_get_bt_frame.unwrap().0 + 4); + } + + + fn on_stack_panic_handling() { + use std::panic; + const CHAIN_DEPTH: usize = 16; + fn panic_chain(depth: usize) { + if depth == 0 { + panic!("full chain!"); + } else { + unsafe { + // Generating backraces (because of RUST_BACKTRACE) create a few quite large + // frames, so it is important, that all frames have sufficient amount of + // available memory to not run over the stack... + let new_stack = alloc_stack(128 * 4096); + let p = crate::on_stack(new_stack, 128 * 4096, || { + panic::catch_unwind(|| { + panic_chain(depth - 1); + }) + }); + p.map_err(panic::resume_unwind).unwrap() + } + } + } + assert!(panic::catch_unwind(|| { + panic_chain(CHAIN_DEPTH); + }).is_err(), "Panic did not propagate!"); + } + + fn on_stack_multithread() { + use std::thread; + const FIB_COUNTS: [(usize, u64); 3] = [ + (8, 21), + (16, 987), + (24, 46368), + ]; + + #[inline(never)] + fn fib(n: usize) -> u64 { + unsafe { + let new_stack = alloc_stack(4096); + let r = match n { + 0 => 0, + 1 => 1, + _ => { + crate::on_stack(new_stack, 4096, || { + fib(n - 1) + fib(n - 2) + }) + } + }; + r + } + } + + for (expected, handle) in FIB_COUNTS.iter().map(|&(n, expected)| + (expected, thread::spawn(move || { + fib(n) + })) + ) { + if let Ok(res) = handle.join() { + assert_eq!(res, expected); + } else { + panic!("joining a thread returned an Err"); + } + } + } + + fn replace_stack_basic() { + unsafe { + let init_stack_ptr = crate::stack_pointer(); + let new_stack = alloc_stack(4096 * 64); + crate::replace_stack(new_stack, 4096 * 64, || { + assert!(ptr_distance(crate::stack_pointer() as _, init_stack_ptr as _) > 0x100_0000); + assert!(ptr_distance(crate::stack_pointer() as _, new_stack as _) < 4096 * 64); + std::process::exit(0) + }); + } + } + + fn replace_stack_panic() { + unsafe { + let new_stack = alloc_stack(4096 * 64); + crate::replace_stack(new_stack, 4096 * 64, || { + let unwind = std::panic::catch_unwind(|| panic!("test") ); + assert!(unwind.is_err()); + std::process::exit(0); + }); + } + } + + fn replace_stack_backtrace() { + unsafe { + let new_stack = alloc_stack(128 * 4096); + crate::replace_stack(new_stack, 128 * 4096, || { + let bt = rust_psm_test_get_bt(); + let test_get_bt_frame = find_frame_name(&bt, b"rust_psm_test_get_bt"); + assert!(test_get_bt_frame.is_some()); + assert!(bt.frames().len() < test_get_bt_frame.unwrap().0 + 8); + std::process::exit(0); + }) + } + } +} + +#[inline(never)] +pub(crate) fn run() { + let this = std::env::args_os().next().unwrap(); + if let Some(test_to_run) = std::env::args_os().nth(1) { + for (name, test) in TESTS { + if test_to_run == std::ffi::OsStr::new(name) { + test(); + std::process::exit(0); + } + } + } else { + let mut failures = 0; + for (name, _) in TESTS { + let mut cmd = std::process::Command::new(&this); + cmd.arg(name); + eprintln!("{:?}...", cmd); + failures = failures + + match cmd.status() { + Ok(s) if s.success() => continue, + Ok(s) => { + eprintln!("test did not complete successfully: {:?}", s); + 1 + } + Err(e) => { + eprintln!("could not spawn self for single test: {}", e); + 1 + } + } + } + if failures != 0 { + eprintln!("{} tests failed", failures); + std::process::exit(failures); + } + } +} diff --git a/psm/tests/stack_direction.rs b/psm/tests/stack_direction.rs deleted file mode 100644 index 609decb..0000000 --- a/psm/tests/stack_direction.rs +++ /dev/null @@ -1,6 +0,0 @@ -extern crate psm; - -#[test] -fn always_equal() { - assert_eq!(psm::StackDirection::new(), psm::StackDirection::new()); -} diff --git a/psm/tests/stack_direction_2.rs b/psm/tests/stack_direction_2.rs deleted file mode 100644 index dd06790..0000000 --- a/psm/tests/stack_direction_2.rs +++ /dev/null @@ -1,29 +0,0 @@ -extern crate psm; - -#[inline(never)] -fn test_direction(previous_sp: *mut u8) { - let current_sp = psm::stack_pointer(); - match psm::StackDirection::new() { - psm::StackDirection::Ascending => { - assert!( - current_sp > previous_sp, - "the stack pointer is not ascending! current = {:p}, previous = {:p}", - current_sp, - previous_sp - ); - } - psm::StackDirection::Descending => { - assert!( - current_sp < previous_sp, - "the stack pointer is not descending! current = {:p}, previous = {:p}", - current_sp, - previous_sp - ); - } - } -} - -#[test] -fn direction_right() { - test_direction(psm::stack_pointer()); -} diff --git a/stacker/Cargo.toml b/stacker/Cargo.toml new file mode 100644 index 0000000..01acf63 --- /dev/null +++ b/stacker/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "stacker" +version = "0.1.15" +authors = ["Alex Crichton ", "Simonas Kazlauskas "] +build = "build.rs" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/rust-lang/stacker" +homepage = "https://github.com/rust-lang/stacker" +documentation = "https://docs.rs/stacker/0.1.15" +description = """ +A stack growth library useful when implementing deeply recursive algorithms that +may accidentally blow the stack. +""" + +[lib] +name = "stacker" +doctest = false +test = false + +[dependencies] +cfg-if = "1.0.0" +libc = "0.2.45" +psm = { path = "../psm", version = "0.1.7" } + +[target.'cfg(windows)'.dependencies.windows-sys] +version = ">=0.34.0, <0.42.0" +features = [ + "Win32_System_Memory", + "Win32_System_Threading", + "Win32_Foundation", +] + + +[build-dependencies] +cc = "1.0.2" diff --git a/LICENSE-APACHE b/stacker/LICENSE-APACHE similarity index 100% rename from LICENSE-APACHE rename to stacker/LICENSE-APACHE diff --git a/LICENSE-MIT b/stacker/LICENSE-MIT similarity index 100% rename from LICENSE-MIT rename to stacker/LICENSE-MIT diff --git a/stacker/README.mkd b/stacker/README.mkd new file mode 100644 index 0000000..1e650ed --- /dev/null +++ b/stacker/README.mkd @@ -0,0 +1,49 @@ +
+

stacker

+

+ A stack-growth library for Rust +

+
+ + +`stacker` enables users to annotate fixed points in their programs where the stack may want to grow +larger to accomodate for recursion or other kinds of heavy stack space usage. At each such +annotation, the caller indicates how far away from the end of the stack it's allowed to be, plus +the amount of stack to allocate if the remaining capacity is insufficient. + +Once a program has reached the end of its stack, a temporary stack on the heap is allocated and +is switched to for the duration of a closure. + +A philosophy behind this crate is to expose a straightforward interface. In case the functionality +provided by this crate aren’t flexible enough for your use-case, check out the `psm` crate upon +which `stacker` is built. + +# Examples + +``` +// Grow the stack if we are within the "red zone" of 32K, and if we allocate +// a new stack allocate an additional 1MB of stack space. +// +// If we're already within bounds, the provided closure will run on the current stack. +stacker::maybe_grow(32 * 1024, 1024 * 1024, || { + // guaranteed to have at least 32K of stack available to use. +}); +``` + +## Platform Support + +This library currently uses `psm` for its cross platform capabilities, with a notable exception of +Windows, which uses an implementation based on Fibers. See the documentation of psm for the table +of supported platforms. + +On all unsupported platforms this library is a noop. It should compile and run, but it won't +actually grow the stack and code will continue to hit the guard pages typically in place. + +# License + +`stacker` is licensed under either of + + * [Apache License, Version 2.0]() + * [MIT license]() + +at your option. diff --git a/build.rs b/stacker/build.rs similarity index 100% rename from build.rs rename to stacker/build.rs diff --git a/src/arch/asm.h b/stacker/src/arch/asm.h similarity index 100% rename from src/arch/asm.h rename to stacker/src/arch/asm.h diff --git a/src/arch/windows.c b/stacker/src/arch/windows.c similarity index 100% rename from src/arch/windows.c rename to stacker/src/arch/windows.c diff --git a/src/lib.rs b/stacker/src/lib.rs similarity index 93% rename from src/lib.rs rename to stacker/src/lib.rs index eaef9f1..a7a6536 100644 --- a/src/lib.rs +++ b/stacker/src/lib.rs @@ -1,27 +1,4 @@ -//! A library to help grow the stack when it runs out of space. -//! -//! This is an implementation of manually instrumented segmented stacks where points in a program's -//! control flow are annotated with "maybe grow the stack here". Each point of annotation indicates -//! how far away from the end of the stack it's allowed to be, plus the amount of stack to allocate -//! if it does reach the end. -//! -//! Once a program has reached the end of its stack, a temporary stack on the heap is allocated and -//! is switched to for the duration of a closure. -//! -//! For a set of lower-level primitives, consider the `psm` crate. -//! -//! # Examples -//! -//! ``` -//! // Grow the stack if we are within the "red zone" of 32K, and if we allocate -//! // a new stack allocate 1MB of stack space. -//! // -//! // If we're already in bounds, just run the provided closure on current stack. -//! stacker::maybe_grow(32 * 1024, 1024 * 1024, || { -//! // guaranteed to have at least 32K of stack -//! }); -//! ``` - +#![doc=include_str!("../README.mkd")] #![allow(improper_ctypes)] #[macro_use] @@ -58,9 +35,10 @@ pub fn maybe_grow R>(red_zone: usize, stack_size: usize, callb } } -/// Always creates a new stack for the passed closure to run on. -/// The closure will still be on the same thread as the caller of `grow`. -/// This will allocate a new stack with at least `stack_size` bytes. +/// Always runs the passed closure on a new stack. +/// +/// The closure will still execute on the same thread as the caller of `grow`. +/// This will allocate a new stack of at least `stack_size` bytes. pub fn grow R>(stack_size: usize, callback: F) -> R { // To avoid monomorphizing `_grow()` and everything it calls, // we convert the generic callback to a dynamic one. diff --git a/tests/simple.rs b/stacker/tests/simple.rs similarity index 100% rename from tests/simple.rs rename to stacker/tests/simple.rs diff --git a/tests/smoke.rs b/stacker/tests/smoke.rs similarity index 100% rename from tests/smoke.rs rename to stacker/tests/smoke.rs
TargetSupportTarget Support
Architecture