From 641f1c53862aec64810c0b93b5b1de49d55fda92 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 10:21:42 +0200 Subject: [PATCH 01/30] lcitool: use newer Rust for Debian and Ubuntu On Debian, the rustc-web package provides a newer Rust compiler (1.78) for all architectures except mips64el. On Ubuntu, Rust versions up to 1.80 (?) are available as of this writing for both Jammy (22.04) and Noble (24.04). However, the path to rustc and rustdoc must be provided by hand to the configure script using either command line arguments or environment variables. Signed-off-by: Paolo Bonzini --- docs/about/build-platforms.rst | 11 ++++++++--- scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml | 2 +- scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml | 2 +- tests/docker/dockerfiles/debian-amd64-cross.docker | 2 +- tests/docker/dockerfiles/debian-arm64-cross.docker | 2 +- tests/docker/dockerfiles/debian-armhf-cross.docker | 2 +- tests/docker/dockerfiles/debian-i686-cross.docker | 2 +- tests/docker/dockerfiles/debian-mips64el-cross.docker | 2 +- tests/docker/dockerfiles/debian-mipsel-cross.docker | 2 +- tests/docker/dockerfiles/debian-ppc64el-cross.docker | 2 +- tests/docker/dockerfiles/debian-s390x-cross.docker | 2 +- tests/docker/dockerfiles/debian.docker | 2 +- tests/docker/dockerfiles/ubuntu2204.docker | 3 ++- tests/lcitool/mappings.yml | 5 +++++ tests/lcitool/refresh | 5 +++-- 15 files changed, 29 insertions(+), 17 deletions(-) diff --git a/docs/about/build-platforms.rst b/docs/about/build-platforms.rst index c3651871d2..8ecbd6b26f 100644 --- a/docs/about/build-platforms.rst +++ b/docs/about/build-platforms.rst @@ -118,9 +118,14 @@ Rust build dependencies include bindgen or have an older version, it is recommended to install a newer version using ``cargo install bindgen-cli``. - Developers may want to use Cargo-based tools in the QEMU source tree; - this requires Cargo 1.74.0. Note that Cargo is not required in order - to build QEMU. + QEMU requires Rust 1.77.0. This is available on all supported platforms + with one exception, namely the ``mips64el`` architecture on Debian bookworm. + For all other architectures, Debian bookworm provides a new-enough Rust + compiler in the ``rustc-web`` package. + + Also, on Ubuntu 22.04 or 24.04 this requires the ``rustc-1.77`` + (or newer) package. The path to ``rustc`` and ``rustdoc`` must be + provided manually to the configure script. Optional build dependencies Build components whose absence does not affect the ability to build QEMU diff --git a/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml b/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml index dbcd2e076d..f11e980826 100644 --- a/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml +++ b/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml @@ -114,7 +114,7 @@ packages: - python3-venv - python3-yaml - rpm2cpio - - rustc + - rustc-1.77 - sed - socat - sparse diff --git a/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml b/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml index 4b8ee3d885..6559cb2934 100644 --- a/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml +++ b/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml @@ -112,7 +112,7 @@ packages: - python3-venv - python3-yaml - rpm2cpio - - rustc + - rustc-1.77 - sed - socat - sparse diff --git a/tests/docker/dockerfiles/debian-amd64-cross.docker b/tests/docker/dockerfiles/debian-amd64-cross.docker index 0535585428..081f3e00f7 100644 --- a/tests/docker/dockerfiles/debian-amd64-cross.docker +++ b/tests/docker/dockerfiles/debian-amd64-cross.docker @@ -50,7 +50,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/debian-arm64-cross.docker b/tests/docker/dockerfiles/debian-arm64-cross.docker index 6b1e4fc827..91c555a36e 100644 --- a/tests/docker/dockerfiles/debian-arm64-cross.docker +++ b/tests/docker/dockerfiles/debian-arm64-cross.docker @@ -50,7 +50,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/debian-armhf-cross.docker b/tests/docker/dockerfiles/debian-armhf-cross.docker index cf0fe63af9..f0e2efcda0 100644 --- a/tests/docker/dockerfiles/debian-armhf-cross.docker +++ b/tests/docker/dockerfiles/debian-armhf-cross.docker @@ -50,7 +50,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/debian-i686-cross.docker b/tests/docker/dockerfiles/debian-i686-cross.docker index 1c84dfb945..025beb1ce2 100644 --- a/tests/docker/dockerfiles/debian-i686-cross.docker +++ b/tests/docker/dockerfiles/debian-i686-cross.docker @@ -50,7 +50,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/debian-mips64el-cross.docker b/tests/docker/dockerfiles/debian-mips64el-cross.docker index 257204eae4..4a941dd870 100644 --- a/tests/docker/dockerfiles/debian-mips64el-cross.docker +++ b/tests/docker/dockerfiles/debian-mips64el-cross.docker @@ -50,7 +50,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/debian-mipsel-cross.docker b/tests/docker/dockerfiles/debian-mipsel-cross.docker index 395c84d65b..4d3e5d711b 100644 --- a/tests/docker/dockerfiles/debian-mipsel-cross.docker +++ b/tests/docker/dockerfiles/debian-mipsel-cross.docker @@ -50,7 +50,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/debian-ppc64el-cross.docker b/tests/docker/dockerfiles/debian-ppc64el-cross.docker index 1ae227ccde..22b4457ba9 100644 --- a/tests/docker/dockerfiles/debian-ppc64el-cross.docker +++ b/tests/docker/dockerfiles/debian-ppc64el-cross.docker @@ -50,7 +50,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/debian-s390x-cross.docker b/tests/docker/dockerfiles/debian-s390x-cross.docker index afa81a57ba..13ec52c8ad 100644 --- a/tests/docker/dockerfiles/debian-s390x-cross.docker +++ b/tests/docker/dockerfiles/debian-s390x-cross.docker @@ -50,7 +50,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/debian.docker b/tests/docker/dockerfiles/debian.docker index 5b3bac43cc..0a57c1a1d3 100644 --- a/tests/docker/dockerfiles/debian.docker +++ b/tests/docker/dockerfiles/debian.docker @@ -122,7 +122,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-web \ sed \ socat \ sparse \ diff --git a/tests/docker/dockerfiles/ubuntu2204.docker b/tests/docker/dockerfiles/ubuntu2204.docker index 88ce4ef9a9..4a1cf2bdff 100644 --- a/tests/docker/dockerfiles/ubuntu2204.docker +++ b/tests/docker/dockerfiles/ubuntu2204.docker @@ -121,7 +121,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ python3-venv \ python3-yaml \ rpm2cpio \ - rustc \ + rustc-1.77 \ sed \ socat \ sparse \ @@ -150,6 +150,7 @@ ENV LANG "en_US.UTF-8" ENV MAKE "/usr/bin/make" ENV NINJA "/usr/bin/ninja" ENV PYTHON "/usr/bin/python3" +ENV RUSTC=/usr/bin/rustc-1.77 ENV CARGO_HOME=/usr/local/cargo ENV PATH=$CARGO_HOME/bin:$PATH RUN DEBIAN_FRONTEND=noninteractive eatmydata \ diff --git a/tests/lcitool/mappings.yml b/tests/lcitool/mappings.yml index 74eb13d62b..673baf3936 100644 --- a/tests/lcitool/mappings.yml +++ b/tests/lcitool/mappings.yml @@ -64,6 +64,11 @@ mappings: python3-wheel: OpenSUSELeap15: python311-pip + rust: + Debian12: rustc-web + Ubuntu2204: rustc-1.77 + Ubuntu2404: rustc-1.77 + pypi_mappings: # Request more recent version meson: diff --git a/tests/lcitool/refresh b/tests/lcitool/refresh index aa551aca9b..8474ea822f 100755 --- a/tests/lcitool/refresh +++ b/tests/lcitool/refresh @@ -141,7 +141,8 @@ fedora_rustup_nightly_extras = [ 'RUN $CARGO --list\n', ] -ubuntu2204_bindgen_extras = [ +ubuntu2204_rust_extras = [ + "ENV RUSTC=/usr/bin/rustc-1.77\n", "ENV CARGO_HOME=/usr/local/cargo\n", 'ENV PATH=$CARGO_HOME/bin:$PATH\n', "RUN DEBIAN_FRONTEND=noninteractive eatmydata \\\n", @@ -170,7 +171,7 @@ try: generate_dockerfile("fedora", "fedora-40") generate_dockerfile("opensuse-leap", "opensuse-leap-15") generate_dockerfile("ubuntu2204", "ubuntu-2204", - trailer="".join(ubuntu2204_bindgen_extras)) + trailer="".join(ubuntu2204_rust_extras)) # # Non-fatal Rust-enabled build From e0b2b74f70a85b16e9d4a9b6111e09956ae4cfd9 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 23:36:11 +0200 Subject: [PATCH 02/30] meson, cargo: require Rust 1.77.0 Reviewed-by: Manos Pitsidianakis Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- meson.build | 6 +++--- rust/Cargo.toml | 2 +- rust/clippy.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 6c61e1dcae..8eb3de5d68 100644 --- a/meson.build +++ b/meson.build @@ -94,12 +94,12 @@ have_rust = have_rust and add_languages('rust', native: true, required: get_option('rust').disable_auto_if(not have_system)) if have_rust rustc = meson.get_compiler('rust') - if rustc.version().version_compare('<1.63.0') + if rustc.version().version_compare('<1.77.0') if get_option('rust').enabled() - error('rustc version ' + rustc.version() + ' is unsupported. Please upgrade to at least 1.63.0') + error('rustc version ' + rustc.version() + ' is unsupported. Please upgrade to at least 1.77.0') else warning('rustc version ' + rustc.version() + ' is unsupported, disabling Rust compilation.') - message('Please upgrade to at least 1.63.0 to use Rust.') + message('Please upgrade to at least 1.77.0 to use Rust.') have_rust = false endif endif diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5ace47c69b..eda7980b31 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -12,7 +12,7 @@ edition = "2021" homepage = "https://www.qemu.org" license = "GPL-2.0-or-later" repository = "https://gitlab.com/qemu-project/qemu/" -rust-version = "1.63.0" +rust-version = "1.77.0" [workspace.lints.rust] unexpected_cfgs = { level = "deny", check-cfg = [ diff --git a/rust/clippy.toml b/rust/clippy.toml index 5d190f91de..933e46a2ff 100644 --- a/rust/clippy.toml +++ b/rust/clippy.toml @@ -1,2 +1,2 @@ doc-valid-idents = ["PrimeCell", ".."] -msrv = "1.63.0" +msrv = "1.77.0" From e4fb0be1d1d6b67df7709d84d16133b64f455ce8 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 10:18:35 +0200 Subject: [PATCH 03/30] rust: use std::ffi instead of std::os::raw This is allowed since Rust 1.64.0. Reviewed-by: Manos Pitsidianakis Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 2 -- rust/hw/char/pl011/src/device_class.rs | 2 +- rust/hw/timer/hpet/src/hpet.rs | 3 +-- rust/qemu-api/src/chardev.rs | 3 +-- rust/qemu-api/src/irq.rs | 6 +++++- rust/qemu-api/src/lib.rs | 2 +- rust/qemu-api/src/memory.rs | 3 +-- rust/qemu-api/src/qdev.rs | 3 +-- rust/qemu-api/src/qom.rs | 3 +-- rust/qemu-api/src/timer.rs | 2 +- rust/qemu-api/src/vmstate.rs | 2 +- rust/qemu-api/tests/vmstate_tests.rs | 7 ++++++- 12 files changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 3cc2841d4d..c7dfba98de 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -74,8 +74,6 @@ Supported tools QEMU supports rustc version 1.63.0 and newer. Notably, the following features are missing: -* ``core::ffi`` (1.64.0). Use ``std::os::raw`` and ``std::ffi`` instead. - * ``cast_mut()``/``cast_const()`` (1.65.0). Use ``as`` instead. * "let ... else" (1.65.0). Use ``if let`` instead. This is currently patched diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs index b4d4a7eb43..cd2dc33c20 100644 --- a/rust/hw/char/pl011/src/device_class.rs +++ b/rust/hw/char/pl011/src/device_class.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later use std::{ - os::raw::{c_int, c_void}, + ffi::{c_int, c_void}, ptr::NonNull, }; diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index 12de2ba59a..d4364f2f2f 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -3,8 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later use std::{ - ffi::CStr, - os::raw::{c_int, c_void}, + ffi::{c_int, c_void, CStr}, pin::Pin, ptr::{addr_of_mut, null_mut, NonNull}, slice::from_ref, diff --git a/rust/qemu-api/src/chardev.rs b/rust/qemu-api/src/chardev.rs index 11e6c45afa..146a4852da 100644 --- a/rust/qemu-api/src/chardev.rs +++ b/rust/qemu-api/src/chardev.rs @@ -10,11 +10,10 @@ //! called. use std::{ - ffi::CStr, + ffi::{c_int, c_void, CStr}, fmt::{self, Debug}, io::{self, ErrorKind, Write}, marker::PhantomPinned, - os::raw::{c_int, c_void}, ptr::addr_of_mut, slice, }; diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index 1222d4fde3..1526e6f63a 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -4,7 +4,11 @@ //! Bindings for interrupt sources -use std::{ffi::CStr, marker::PhantomData, os::raw::c_int, ptr}; +use std::{ + ffi::{c_int, CStr}, + marker::PhantomData, + ptr, +}; use crate::{ bindings::{self, qemu_set_irq}, diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 05f38b51d3..c3641e893b 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -33,7 +33,7 @@ pub mod zeroable; use std::{ alloc::{GlobalAlloc, Layout}, - os::raw::c_void, + ffi::c_void, }; #[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)] diff --git a/rust/qemu-api/src/memory.rs b/rust/qemu-api/src/memory.rs index fdb1ea11fc..9ef2694bd6 100644 --- a/rust/qemu-api/src/memory.rs +++ b/rust/qemu-api/src/memory.rs @@ -5,9 +5,8 @@ //! Bindings for `MemoryRegion`, `MemoryRegionOps` and `MemTxAttrs` use std::{ - ffi::{CStr, CString}, + ffi::{c_uint, c_void, CStr, CString}, marker::PhantomData, - os::raw::{c_uint, c_void}, }; pub use bindings::{hwaddr, MemTxAttrs}; diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 18b4a9ba68..17c7fecabe 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -5,8 +5,7 @@ //! Bindings to create devices and access device functionality from Rust. use std::{ - ffi::{CStr, CString}, - os::raw::{c_int, c_void}, + ffi::{c_int, c_void, CStr, CString}, ptr::NonNull, }; diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index f1b4022157..6929e4d33a 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -93,11 +93,10 @@ //! without incurring into violations of orphan rules for traits. use std::{ - ffi::CStr, + ffi::{c_void, CStr}, fmt, mem::ManuallyDrop, ops::{Deref, DerefMut}, - os::raw::c_void, ptr::NonNull, }; diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs index e769f8bc91..d697fd742b 100644 --- a/rust/qemu-api/src/timer.rs +++ b/rust/qemu-api/src/timer.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later use std::{ - os::raw::{c_int, c_void}, + ffi::{c_int, c_void}, pin::Pin, }; diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 9ae97c389c..c0f29f7418 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -25,7 +25,7 @@ //! functionality that is missing from `vmstate_of!`. use core::{marker::PhantomData, mem, ptr::NonNull}; -use std::os::raw::{c_int, c_void}; +use std::ffi::{c_int, c_void}; pub use crate::bindings::{VMStateDescription, VMStateField}; use crate::{ diff --git a/rust/qemu-api/tests/vmstate_tests.rs b/rust/qemu-api/tests/vmstate_tests.rs index f7a93117e1..9a56ffd238 100644 --- a/rust/qemu-api/tests/vmstate_tests.rs +++ b/rust/qemu-api/tests/vmstate_tests.rs @@ -2,7 +2,12 @@ // Author(s): Zhao Liu // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ffi::CStr, mem::size_of, os::raw::c_void, ptr::NonNull, slice}; +use std::{ + ffi::{c_void, CStr}, + mem::size_of, + ptr::NonNull, + slice, +}; use qemu_api::{ bindings::{ From 51209c2aed343a45f79b522706c807abbdcf01a3 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 10:28:11 +0200 Subject: [PATCH 04/30] rust: let bilge use "let ... else" "let ... else" was stabilized in 1.65.0; bumping the minimum supported Rust version means we don't need to patch it out anymore. Reviewed-by: Manos Pitsidianakis Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 3 -- subprojects/bilge-impl-0.2-rs.wrap | 1 - .../packagefiles/bilge-impl-1.63.0.patch | 45 ------------------- 3 files changed, 49 deletions(-) delete mode 100644 subprojects/packagefiles/bilge-impl-1.63.0.patch diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index c7dfba98de..8167ff49aa 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -76,9 +76,6 @@ are missing: * ``cast_mut()``/``cast_const()`` (1.65.0). Use ``as`` instead. -* "let ... else" (1.65.0). Use ``if let`` instead. This is currently patched - in QEMU's vendored copy of the bilge crate. - * Generic Associated Types (1.65.0) * ``CStr::from_bytes_with_nul()`` as a ``const`` function (1.72.0). diff --git a/subprojects/bilge-impl-0.2-rs.wrap b/subprojects/bilge-impl-0.2-rs.wrap index d14c3dc769..4f84eca1cc 100644 --- a/subprojects/bilge-impl-0.2-rs.wrap +++ b/subprojects/bilge-impl-0.2-rs.wrap @@ -5,7 +5,6 @@ source_filename = bilge-impl-0.2.0.tar.gz source_hash = feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8 #method = cargo patch_directory = bilge-impl-0.2-rs -diff_files = bilge-impl-1.63.0.patch # bump this version number on every change to meson.build or the patches: # v2 diff --git a/subprojects/packagefiles/bilge-impl-1.63.0.patch b/subprojects/packagefiles/bilge-impl-1.63.0.patch deleted file mode 100644 index 987428a6d6..0000000000 --- a/subprojects/packagefiles/bilge-impl-1.63.0.patch +++ /dev/null @@ -1,45 +0,0 @@ ---- a/src/shared/discriminant_assigner.rs -+++ b/src/shared/discriminant_assigner.rs -@@ -26,20 +26,20 @@ - let discriminant_expr = &discriminant.1; - let variant_name = &variant.ident; - -- let Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) = discriminant_expr else { -+ if let Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) = discriminant_expr { -+ let discriminant_value: u128 = int.base10_parse().unwrap_or_else(unreachable); -+ if discriminant_value > self.max_value() { -+ abort!(variant, "Value of variant exceeds the given number of bits") -+ } -+ -+ Some(discriminant_value) -+ } else { - abort!( - discriminant_expr, - "variant `{}` is not a number", variant_name; - help = "only literal integers currently supported" - ) -- }; -- -- let discriminant_value: u128 = int.base10_parse().unwrap_or_else(unreachable); -- if discriminant_value > self.max_value() { -- abort!(variant, "Value of variant exceeds the given number of bits") - } -- -- Some(discriminant_value) - } - - fn assign(&mut self, variant: &Variant) -> u128 { ---- a/src/shared/fallback.rs -+++ b/src/shared/fallback.rs -@@ -22,8 +22,9 @@ - } - Unnamed(fields) => { - let variant_fields = fields.unnamed.iter(); -- let Ok(fallback_value) = variant_fields.exactly_one() else { -- abort!(variant, "fallback variant must have exactly one field"; help = "use only one field or change to a unit variant") -+ let fallback_value = match variant_fields.exactly_one() { -+ Ok(ok) => ok, -+ _ => abort!(variant, "fallback variant must have exactly one field"; help = "use only one field or change to a unit variant") - }; - - if !is_last_variant { From 465a4b80e2afd93f2eb4c9f55233788ebfb47ac0 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 11:42:48 +0200 Subject: [PATCH 05/30] rust: qemu_api_macros: make pattern matching more readable and efficient "let ... else" is useful when visiting syntax trees; it avoids multiple levels of indentation and places the error close to the pattern. While at it, use "ref" to avoid moving the syntax tree objects. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 84 +++++++++++++++------------------ 1 file changed, 37 insertions(+), 47 deletions(-) diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index eda0d46d12..0bffd2691b 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -16,50 +16,41 @@ fn get_fields<'a>( input: &'a DeriveInput, msg: &str, ) -> Result<&'a Punctuated, MacroError> { - if let Data::Struct(s) = &input.data { - if let Fields::Named(fs) = &s.fields { - Ok(&fs.named) - } else { - Err(MacroError::Message( - format!("Named fields required for {}", msg), - input.ident.span(), - )) - } - } else { - Err(MacroError::Message( + let Data::Struct(ref s) = &input.data else { + return Err(MacroError::Message( format!("Struct required for {}", msg), input.ident.span(), - )) - } + )); + }; + let Fields::Named(ref fs) = &s.fields else { + return Err(MacroError::Message( + format!("Named fields required for {}", msg), + input.ident.span(), + )); + }; + Ok(&fs.named) } fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, MacroError> { - if let Data::Struct(s) = &input.data { - let unnamed = match &s.fields { - Fields::Unnamed(FieldsUnnamed { - unnamed: ref fields, - .. - }) => fields, - _ => { - return Err(MacroError::Message( - format!("Tuple struct required for {}", msg), - s.fields.span(), - )) - } - }; - if unnamed.len() != 1 { - return Err(MacroError::Message( - format!("A single field is required for {}", msg), - s.fields.span(), - )); - } - Ok(&unnamed[0]) - } else { - Err(MacroError::Message( + let Data::Struct(ref s) = &input.data else { + return Err(MacroError::Message( format!("Struct required for {}", msg), input.ident.span(), - )) + )); + }; + let Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) = &s.fields else { + return Err(MacroError::Message( + format!("Tuple struct required for {}", msg), + s.fields.span(), + )); + }; + if unnamed.len() != 1 { + return Err(MacroError::Message( + format!("A single field is required for {}", msg), + s.fields.span(), + )); } + Ok(&unnamed[0]) } fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { @@ -210,20 +201,19 @@ fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { } fn get_variants(input: &DeriveInput) -> Result<&Punctuated, MacroError> { - if let Data::Enum(e) = &input.data { - if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) { - return Err(MacroError::Message( - "Cannot derive TryInto for enum with non-unit variants.".to_string(), - v.fields.span(), - )); - } - Ok(&e.variants) - } else { - Err(MacroError::Message( + let Data::Enum(ref e) = &input.data else { + return Err(MacroError::Message( "Cannot derive TryInto for union or struct.".to_string(), input.ident.span(), - )) + )); + }; + if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) { + return Err(MacroError::Message( + "Cannot derive TryInto for enum with non-unit variants.".to_string(), + v.fields.span(), + )); } + Ok(&e.variants) } #[rustfmt::skip::macros(quote)] From 0823837224be428d643dc7000fa534f8aedea5fd Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 10:31:28 +0200 Subject: [PATCH 06/30] rust: use MaybeUninit::zeroed() in const context Reviewed-by: Manos Pitsidianakis Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 4 -- rust/hw/timer/hpet/src/fw_cfg.rs | 6 +- rust/qemu-api/src/zeroable.rs | 106 +++++-------------------------- 3 files changed, 20 insertions(+), 96 deletions(-) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 8167ff49aa..13a002cfe6 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -83,10 +83,6 @@ are missing: * "Return position ``impl Trait`` in Traits" (1.75.0, blocker for including the pinned-init create). -* ``MaybeUninit::zeroed()`` as a ``const`` function (1.75.0). QEMU's - ``Zeroable`` trait can be implemented without ``MaybeUninit::zeroed()``, - so this would be just a cleanup. - * ``c"" literals`` (stable in 1.77.0). QEMU provides a ``c_str!()`` macro to define ``CStr`` constants easily diff --git a/rust/hw/timer/hpet/src/fw_cfg.rs b/rust/hw/timer/hpet/src/fw_cfg.rs index bef03727ea..aa08d28351 100644 --- a/rust/hw/timer/hpet/src/fw_cfg.rs +++ b/rust/hw/timer/hpet/src/fw_cfg.rs @@ -4,7 +4,7 @@ use std::ptr::addr_of_mut; -use qemu_api::{cell::bql_locked, impl_zeroable, zeroable::Zeroable}; +use qemu_api::{cell::bql_locked, zeroable::Zeroable}; /// Each `HPETState` represents a Event Timer Block. The v1 spec supports /// up to 8 blocks. QEMU only uses 1 block (in PC machine). @@ -18,7 +18,7 @@ pub struct HPETFwEntry { pub min_tick: u16, pub page_prot: u8, } -impl_zeroable!(HPETFwEntry); +unsafe impl Zeroable for HPETFwEntry {} #[repr(C, packed)] #[derive(Copy, Clone, Default)] @@ -26,7 +26,7 @@ pub struct HPETFwConfig { pub count: u8, pub hpet: [HPETFwEntry; HPET_MAX_NUM_EVENT_TIMER_BLOCK], } -impl_zeroable!(HPETFwConfig); +unsafe impl Zeroable for HPETFwConfig {} #[allow(non_upper_case_globals)] #[no_mangle] diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index a3415a2ebc..d8239d0856 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -4,89 +4,17 @@ /// Encapsulates the requirement that /// `MaybeUninit::::zeroed().assume_init()` does not cause undefined -/// behavior. This trait in principle could be implemented as just: -/// -/// ``` -/// pub unsafe trait Zeroable: Default { -/// const ZERO: Self = unsafe { ::core::mem::MaybeUninit::::zeroed().assume_init() }; -/// } -/// ``` -/// -/// The need for a manual implementation is only because `zeroed()` cannot -/// be used as a `const fn` prior to Rust 1.75.0. Once we can assume a new -/// enough version of the compiler, we could provide a `#[derive(Zeroable)]` -/// macro to check at compile-time that all struct fields are Zeroable, and -/// use the above blanket implementation of the `ZERO` constant. +/// behavior. /// /// # Safety /// -/// Because the implementation of `ZERO` is manual, it does not make -/// any assumption on the safety of `zeroed()`. However, other users of the -/// trait could use it that way. Do not add this trait to a type unless -/// all-zeroes is a valid value for the type. In particular, remember that -/// raw pointers can be zero, but references and `NonNull` cannot +/// Do not add this trait to a type unless all-zeroes is a valid value for the +/// type. In particular, raw pointers can be zero, but references and +/// `NonNull` cannot. pub unsafe trait Zeroable: Default { - const ZERO: Self; -} - -/// A macro that acts similarly to [`core::mem::zeroed()`], only is const -/// -/// ## Safety -/// -/// Similar to `core::mem::zeroed()`, except this zeroes padding bits. Zeroed -/// padding usually isn't relevant to safety, but might be if a C union is used. -/// -/// Just like for `core::mem::zeroed()`, an all zero byte pattern might not -/// be a valid value for a type, as is the case for references `&T` and `&mut -/// T`. Reference types trigger a (denied by default) lint and cause immediate -/// undefined behavior if the lint is ignored -/// -/// ```rust compile_fail -/// use const_zero::const_zero; -/// // error: any use of this value will cause an error -/// // note: `#[deny(const_err)]` on by default -/// const STR: &str = unsafe{const_zero!(&'static str)}; -/// ``` -/// -/// `const_zero` does not work on unsized types: -/// -/// ```rust compile_fail -/// use const_zero::const_zero; -/// // error[E0277]: the size for values of type `[u8]` cannot be known at compilation time -/// const BYTES: [u8] = unsafe{const_zero!([u8])}; -/// ``` -/// ## Differences with `core::mem::zeroed` -/// -/// `const_zero` zeroes padding bits, while `core::mem::zeroed` doesn't -#[macro_export] -macro_rules! const_zero { - // This macro to produce a type-generic zero constant is taken from the - // const_zero crate (v0.1.1): - // - // https://docs.rs/const-zero/latest/src/const_zero/lib.rs.html - // - // and used under MIT license - ($type_:ty) => {{ - const TYPE_SIZE: ::core::primitive::usize = ::core::mem::size_of::<$type_>(); - union TypeAsBytes { - bytes: [::core::primitive::u8; TYPE_SIZE], - inner: ::core::mem::ManuallyDrop<$type_>, - } - const ZERO_BYTES: TypeAsBytes = TypeAsBytes { - bytes: [0; TYPE_SIZE], - }; - ::core::mem::ManuallyDrop::<$type_>::into_inner(ZERO_BYTES.inner) - }}; -} - -/// A wrapper to implement the `Zeroable` trait through the `const_zero` macro. -#[macro_export] -macro_rules! impl_zeroable { - ($type:ty) => { - unsafe impl $crate::zeroable::Zeroable for $type { - const ZERO: Self = unsafe { $crate::const_zero!($type) }; - } - }; + /// Return a value of Self whose memory representation consists of all + /// zeroes, with the possible exclusion of padding bytes. + const ZERO: Self = unsafe { ::core::mem::MaybeUninit::::zeroed().assume_init() }; } // bindgen does not derive Default here @@ -97,13 +25,13 @@ impl Default for crate::bindings::VMStateFlags { } } -impl_zeroable!(crate::bindings::Property__bindgen_ty_1); -impl_zeroable!(crate::bindings::Property); -impl_zeroable!(crate::bindings::VMStateFlags); -impl_zeroable!(crate::bindings::VMStateField); -impl_zeroable!(crate::bindings::VMStateDescription); -impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_1); -impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_2); -impl_zeroable!(crate::bindings::MemoryRegionOps); -impl_zeroable!(crate::bindings::MemTxAttrs); -impl_zeroable!(crate::bindings::CharBackend); +unsafe impl Zeroable for crate::bindings::Property__bindgen_ty_1 {} +unsafe impl Zeroable for crate::bindings::Property {} +unsafe impl Zeroable for crate::bindings::VMStateFlags {} +unsafe impl Zeroable for crate::bindings::VMStateField {} +unsafe impl Zeroable for crate::bindings::VMStateDescription {} +unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_1 {} +unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_2 {} +unsafe impl Zeroable for crate::bindings::MemoryRegionOps {} +unsafe impl Zeroable for crate::bindings::MemTxAttrs {} +unsafe impl Zeroable for crate::bindings::CharBackend {} From 3a1c694d74deb478d9822e585e90c5903852eb84 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 17:58:54 +0200 Subject: [PATCH 07/30] rust: qom: fix TODO about zeroability of classes The proposed suggestion is not correct. First it is not necessary for *all* classes to be Zeroable, only for Rust-defined ones; classes defined in C never implement ObjectImpl. Second, the parent class field need not be Zeroable. For example, ChardevClass's chr_write and chr_be_event fields cannot be NULL, therefore ChardevClass cannot be Zeroable. However, char_class_init() initializes them, therefore ChardevClass could be subclassed by Rust code. Reviewed-by: Manos Pitsidianakis Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/src/qom.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 6929e4d33a..52e3a1ec98 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -534,9 +534,10 @@ pub trait ObjectImpl: ObjectType + IsA { /// While `klass`'s parent class is initialized on entry, the other fields /// are all zero; it is therefore assumed that all fields in `T` can be /// zeroed, otherwise it would not be possible to provide the class as a - /// `&mut T`. TODO: add a bound of [`Zeroable`](crate::zeroable::Zeroable) - /// to T; this is more easily done once Zeroable does not require a manual - /// implementation (Rust 1.75.0). + /// `&mut T`. TODO: it may be possible to add an unsafe trait that checks + /// that all fields *after the parent class* (but not the parent class + /// itself) are Zeroable. This unsafe trait can be added via a derive + /// macro. const CLASS_INIT: fn(&mut Self::Class); } From 5df3fe062fb0be9a6689dc0336087b214dd30e5c Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 14 Feb 2025 11:34:44 +0100 Subject: [PATCH 08/30] rust: enable clippy::ptr_cast_constness Reviewed-by: Manos Pitsidianakis Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 2 -- rust/Cargo.toml | 2 +- rust/hw/timer/hpet/src/hpet.rs | 2 +- rust/qemu-api/src/cell.rs | 2 +- rust/qemu-api/src/chardev.rs | 2 +- rust/qemu-api/src/qom.rs | 4 ++-- rust/qemu-api/src/timer.rs | 2 +- 7 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 13a002cfe6..a315f66886 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -74,8 +74,6 @@ Supported tools QEMU supports rustc version 1.63.0 and newer. Notably, the following features are missing: -* ``cast_mut()``/``cast_const()`` (1.65.0). Use ``as`` instead. - * Generic Associated Types (1.65.0) * ``CStr::from_bytes_with_nul()`` as a ``const`` function (1.72.0). diff --git a/rust/Cargo.toml b/rust/Cargo.toml index eda7980b31..a328634d94 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -71,6 +71,7 @@ no_effect_underscore_binding = "deny" option_option = "deny" or_fun_call = "deny" ptr_as_ptr = "deny" +ptr_cast_constness = "deny" pub_underscore_fields = "deny" redundant_clone = "deny" redundant_closure_for_method_calls = "deny" @@ -92,7 +93,6 @@ used_underscore_binding = "deny" # nice to have, but cannot be enabled yet #wildcard_imports = "deny" # still have many bindings::* imports -#ptr_cast_constness = "deny" # needs 1.65.0 for cast_mut()/cast_const() # these may have false positives #option_if_let_else = "deny" diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index d4364f2f2f..b7a1be05de 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -219,7 +219,7 @@ impl HPETTimer { // SAFETY: the HPETTimer will only be used after the timer // is initialized below. qemu_timer: unsafe { Timer::new() }, - state: NonNull::new(state as *const _ as *mut _).unwrap(), + state: NonNull::new((state as *const HPETState).cast_mut()).unwrap(), config: 0, cmp: 0, fsb: 0, diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs index ab0785a269..1cb9fdcd53 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/qemu-api/src/cell.rs @@ -1016,7 +1016,7 @@ impl Opaque { /// Returns a raw pointer to the opaque data. pub const fn as_ptr(&self) -> *const T { - self.as_mut_ptr() as *const _ + self.as_mut_ptr().cast_const() } /// Returns a raw pointer to the opaque data that can be passed to a diff --git a/rust/qemu-api/src/chardev.rs b/rust/qemu-api/src/chardev.rs index 146a4852da..6e0590d758 100644 --- a/rust/qemu-api/src/chardev.rs +++ b/rust/qemu-api/src/chardev.rs @@ -160,7 +160,7 @@ impl CharBackend { receive_cb, event_cb, None, - (owner as *const T as *mut T).cast::(), + (owner as *const T).cast_mut().cast::(), core::ptr::null_mut(), true, ); diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 52e3a1ec98..41e5a5e29a 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -388,7 +388,7 @@ where { #[allow(clippy::as_ptr_cast_mut)] { - self.as_ptr::() as *mut _ + self.as_ptr::().cast_mut() } } } @@ -638,7 +638,7 @@ impl Owned { // SAFETY NOTE: while NonNull requires a mutable pointer, only // Deref is implemented so the pointer passed to from_raw // remains const - Owned(NonNull::new(ptr as *mut T).unwrap()) + Owned(NonNull::new(ptr.cast_mut()).unwrap()) } /// Obtain a raw C pointer from a reference. `src` is consumed diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs index d697fd742b..868bd88575 100644 --- a/rust/qemu-api/src/timer.rs +++ b/rust/qemu-api/src/timer.rs @@ -81,7 +81,7 @@ impl Timer { scale as c_int, attributes as c_int, Some(timer_cb), - (opaque as *const T).cast::() as *mut c_void, + (opaque as *const T).cast::().cast_mut(), ) } } From b134a09ffab3b918979007cf40f603e5b54ed597 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 10:35:49 +0200 Subject: [PATCH 09/30] rust: remove offset_of replacement Reviewed-by: Manos Pitsidianakis Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 6 - rust/Cargo.lock | 1 - rust/Cargo.toml | 2 +- rust/hw/char/pl011/src/device.rs | 4 +- rust/hw/timer/hpet/src/hpet.rs | 4 +- rust/qemu-api-macros/src/lib.rs | 29 +---- rust/qemu-api/Cargo.toml | 3 - rust/qemu-api/build.rs | 7 -- rust/qemu-api/meson.build | 4 - rust/qemu-api/src/lib.rs | 4 - rust/qemu-api/src/offset_of.rs | 168 --------------------------- rust/qemu-api/src/qdev.rs | 6 +- rust/qemu-api/src/vmstate.rs | 10 +- rust/qemu-api/tests/tests.rs | 2 - rust/qemu-api/tests/vmstate_tests.rs | 5 +- 15 files changed, 16 insertions(+), 239 deletions(-) delete mode 100644 rust/qemu-api/src/offset_of.rs diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index a315f66886..5cfafc532c 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -84,12 +84,6 @@ are missing: * ``c"" literals`` (stable in 1.77.0). QEMU provides a ``c_str!()`` macro to define ``CStr`` constants easily -* ``offset_of!`` (stable in 1.77.0). QEMU uses ``offset_of!()`` heavily; it - provides a replacement in the ``qemu_api`` crate, but it does not support - lifetime parameters and therefore ``&'a Something`` fields in the struct - may have to be replaced by ``NonNull``. *Nested* ``offset_of!`` - was only stabilized in Rust 1.82.0, but it is not used. - * inline const expression (stable in 1.79.0), currently worked around with associated constants in the ``FnCall`` trait. diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 2ebf0a11ea..13d580c693 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -108,7 +108,6 @@ version = "0.1.0" dependencies = [ "libc", "qemu_api_macros", - "version_check", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a328634d94..4f6fe17b50 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.77.0" [workspace.lints.rust] unexpected_cfgs = { level = "deny", check-cfg = [ 'cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)', - 'cfg(has_offset_of)'] } +] } # Occasionally, we may need to silence warnings and clippy lints that # were only introduced in newer Rust compiler versions. Do not croak diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index bb2a0f207a..7c563ade9c 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -74,7 +74,7 @@ impl std::ops::Index for Fifo { } #[repr(C)] -#[derive(Debug, Default, qemu_api_macros::offsets)] +#[derive(Debug, Default)] pub struct PL011Registers { #[doc(alias = "fr")] pub flags: registers::Flags, @@ -98,7 +98,7 @@ pub struct PL011Registers { } #[repr(C)] -#[derive(qemu_api_macros::Object, qemu_api_macros::offsets)] +#[derive(qemu_api_macros::Object)] /// PL011 Device Model in QEMU pub struct PL011State { pub parent_obj: ParentField, diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index b7a1be05de..719343a20c 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -183,7 +183,7 @@ fn timer_handler(timer_cell: &BqlRefCell) { /// HPET Timer Abstraction #[repr(C)] -#[derive(Debug, qemu_api_macros::offsets)] +#[derive(Debug)] pub struct HPETTimer { /// timer N index within the timer block (`HPETState`) #[doc(alias = "tn")] @@ -523,7 +523,7 @@ impl HPETTimer { /// HPET Event Timer Block Abstraction #[repr(C)] -#[derive(qemu_api_macros::Object, qemu_api_macros::offsets)] +#[derive(qemu_api_macros::Object)] pub struct HPETState { parent_obj: ParentField, iomem: MemoryRegion, diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 0bffd2691b..dc14cac4ff 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -6,7 +6,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, - DeriveInput, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Type, Variant, Visibility, + DeriveInput, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant, }; mod utils; @@ -151,33 +151,6 @@ pub fn derive_opaque(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } -#[rustfmt::skip::macros(quote)] -fn derive_offsets_or_error(input: DeriveInput) -> Result { - is_c_repr(&input, "#[derive(offsets)]")?; - - let name = &input.ident; - let fields = get_fields(&input, "#[derive(offsets)]")?; - let field_names: Vec<&Ident> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect(); - let field_types: Vec<&Type> = fields.iter().map(|f| &f.ty).collect(); - let field_vis: Vec<&Visibility> = fields.iter().map(|f| &f.vis).collect(); - - Ok(quote! { - ::qemu_api::with_offsets! { - struct #name { - #(#field_vis #field_names: #field_types,)* - } - } - }) -} - -#[proc_macro_derive(offsets)] -pub fn derive_offsets(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_offsets_or_error(input).unwrap_or_else(Into::into); - - TokenStream::from(expanded) -} - #[allow(non_snake_case)] fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { let repr = input.attrs.iter().find(|attr| attr.path().is_ident("repr")); diff --git a/rust/qemu-api/Cargo.toml b/rust/qemu-api/Cargo.toml index ca1b04269f..c96cf50e7a 100644 --- a/rust/qemu-api/Cargo.toml +++ b/rust/qemu-api/Cargo.toml @@ -17,9 +17,6 @@ rust-version.workspace = true qemu_api_macros = { path = "../qemu-api-macros" } libc = "0.2.162" -[build-dependencies] -version_check = "~0.9" - [features] default = ["debug_cell"] allocator = [] diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs index 471e6c633d..470be2a4d5 100644 --- a/rust/qemu-api/build.rs +++ b/rust/qemu-api/build.rs @@ -8,8 +8,6 @@ use std::os::unix::fs::symlink as symlink_file; use std::os::windows::fs::symlink_file; use std::{env, fs::remove_file, io::Result, path::Path}; -use version_check as rustc; - fn main() -> Result<()> { // Placing bindings.inc.rs in the source directory is supported // but not documented or encouraged. @@ -38,11 +36,6 @@ fn main() -> Result<()> { } symlink_file(file, dest_path)?; - // Check for available rustc features - if rustc::is_min_version("1.77.0").unwrap_or(false) { - println!("cargo:rustc-cfg=has_offset_of"); - } - println!("cargo:rerun-if-changed=build.rs"); Ok(()) } diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 858685ddd4..95e65643ae 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -5,9 +5,6 @@ _qemu_api_cfg = run_command(rustc_args, libc_dep = dependency('libc-0.2-rs') # _qemu_api_cfg += ['--cfg', 'feature="allocator"'] -if rustc.version().version_compare('>=1.77.0') - _qemu_api_cfg += ['--cfg', 'has_offset_of'] -endif if get_option('debug_mutex') _qemu_api_cfg += ['--cfg', 'feature="debug_cell"'] endif @@ -28,7 +25,6 @@ _qemu_api_rs = static_library( 'src/irq.rs', 'src/memory.rs', 'src/module.rs', - 'src/offset_of.rs', 'src/prelude.rs', 'src/qdev.rs', 'src/qom.rs', diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index c3641e893b..90c1963666 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -23,7 +23,6 @@ pub mod errno; pub mod irq; pub mod memory; pub mod module; -pub mod offset_of; pub mod qdev; pub mod qom; pub mod sysbus; @@ -165,6 +164,3 @@ unsafe impl GlobalAlloc for QemuAllocator { } } } - -#[cfg(has_offset_of)] -pub use core::mem::offset_of; diff --git a/rust/qemu-api/src/offset_of.rs b/rust/qemu-api/src/offset_of.rs deleted file mode 100644 index 373229bbde..0000000000 --- a/rust/qemu-api/src/offset_of.rs +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: MIT - -#![doc(hidden)] -//! This module provides macros that emulate the functionality of -//! `core::mem::offset_of` on older versions of Rust. -//! -//! Documentation is hidden because it only exposes macros, which -//! are exported directly from `qemu_api`. - -/// This macro provides the same functionality as `core::mem::offset_of`, -/// except that only one level of field access is supported. The declaration -/// of the struct must be wrapped with `with_offsets! { }`. -/// -/// It is needed because `offset_of!` was only stabilized in Rust 1.77. -#[cfg(not(has_offset_of))] -#[macro_export] -macro_rules! offset_of { - ($Container:ty, $field:ident) => { - <$Container>::OFFSET_TO__.$field - }; -} - -/// A wrapper for struct declarations, that allows using `offset_of!` in -/// versions of Rust prior to 1.77 -#[macro_export] -macro_rules! with_offsets { - // This method to generate field offset constants comes from: - // - // https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=10a22a9b8393abd7b541d8fc844bc0df - // - // used under MIT license with permission of Yandros aka Daniel Henry-Mantilla - ( - $(#[$struct_meta:meta])* - $struct_vis:vis - struct $StructName:ident { - $( - $(#[$field_meta:meta])* - $field_vis:vis - $field_name:ident : $field_ty:ty - ),* - $(,)? - } - ) => ( - #[cfg(not(has_offset_of))] - const _: () = { - struct StructOffsetsHelper(std::marker::PhantomData); - const END_OF_PREV_FIELD: usize = 0; - - // populate StructOffsetsHelper with associated consts, - // one for each field - $crate::with_offsets! { - @struct $StructName - @names [ $($field_name)* ] - @tys [ $($field_ty ,)*] - } - - // now turn StructOffsetsHelper's consts into a single struct, - // applying field visibility. This provides better error messages - // than if offset_of! used StructOffsetsHelper:: directly. - pub - struct StructOffsets { - $( - $field_vis - $field_name: usize, - )* - } - impl $StructName { - pub - const OFFSET_TO__: StructOffsets = StructOffsets { - $( - $field_name: StructOffsetsHelper::<$StructName>::$field_name, - )* - }; - } - }; - ); - - ( - @struct $StructName:ident - @names [] - @tys [] - ) => (); - - ( - @struct $StructName:ident - @names [$field_name:ident $($other_names:tt)*] - @tys [$field_ty:ty , $($other_tys:tt)*] - ) => ( - #[allow(non_local_definitions)] - #[allow(clippy::modulo_one)] - impl StructOffsetsHelper<$StructName> { - #[allow(nonstandard_style)] - const $field_name: usize = { - const ALIGN: usize = std::mem::align_of::<$field_ty>(); - const TRAIL: usize = END_OF_PREV_FIELD % ALIGN; - END_OF_PREV_FIELD + (if TRAIL == 0 { 0usize } else { ALIGN - TRAIL }) - }; - } - const _: () = { - const END_OF_PREV_FIELD: usize = - StructOffsetsHelper::<$StructName>::$field_name + - std::mem::size_of::<$field_ty>() - ; - $crate::with_offsets! { - @struct $StructName - @names [$($other_names)*] - @tys [$($other_tys)*] - } - }; - ); -} - -#[cfg(test)] -mod tests { - use crate::offset_of; - - #[repr(C)] - struct Foo { - a: u16, - b: u32, - c: u64, - d: u16, - } - - #[repr(C)] - struct Bar { - pub a: u16, - pub b: u64, - c: Foo, - d: u64, - } - - crate::with_offsets! { - #[repr(C)] - struct Bar { - pub a: u16, - pub b: u64, - c: Foo, - d: u64, - } - } - - #[repr(C)] - pub struct Baz { - b: u32, - a: u8, - } - crate::with_offsets! { - #[repr(C)] - pub struct Baz { - b: u32, - a: u8, - } - } - - #[test] - fn test_offset_of() { - const OFFSET_TO_C: usize = offset_of!(Bar, c); - - assert_eq!(offset_of!(Bar, a), 0); - assert_eq!(offset_of!(Bar, b), 8); - assert_eq!(OFFSET_TO_C, 16); - assert_eq!(offset_of!(Bar, d), 40); - - assert_eq!(offset_of!(Baz, b), 0); - assert_eq!(offset_of!(Baz, a), 4); - } -} diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index 17c7fecabe..1279d7a58d 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -190,7 +190,7 @@ macro_rules! define_property { // use associated function syntax for type checking name: ::std::ffi::CStr::as_ptr($name), info: $prop, - offset: $crate::offset_of!($state, $field) as isize, + offset: ::std::mem::offset_of!($state, $field) as isize, bitnr: $bitnr, set_default: true, defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, @@ -202,7 +202,7 @@ macro_rules! define_property { // use associated function syntax for type checking name: ::std::ffi::CStr::as_ptr($name), info: $prop, - offset: $crate::offset_of!($state, $field) as isize, + offset: ::std::mem::offset_of!($state, $field) as isize, set_default: true, defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 }, ..$crate::zeroable::Zeroable::ZERO @@ -213,7 +213,7 @@ macro_rules! define_property { // use associated function syntax for type checking name: ::std::ffi::CStr::as_ptr($name), info: $prop, - offset: $crate::offset_of!($state, $field) as isize, + offset: ::std::mem::offset_of!($state, $field) as isize, set_default: false, ..$crate::zeroable::Zeroable::ZERO } diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index c0f29f7418..205f35a21f 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -205,8 +205,8 @@ macro_rules! vmstate_of { name: ::core::concat!(::core::stringify!($field_name), "\0") .as_bytes() .as_ptr() as *const ::std::os::raw::c_char, - offset: $crate::offset_of!($struct_name, $field_name), - $(num_offset: $crate::offset_of!($struct_name, $num),)? + offset: ::std::mem::offset_of!($struct_name, $field_name), + $(num_offset: ::std::mem::offset_of!($struct_name, $num),)? $(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)? // The calls to `call_func_with_field!` are the magic that // computes most of the VMStateField from the type of the field. @@ -483,10 +483,10 @@ macro_rules! vmstate_struct { name: ::core::concat!(::core::stringify!($field_name), "\0") .as_bytes() .as_ptr() as *const ::std::os::raw::c_char, - $(num_offset: $crate::offset_of!($struct_name, $num),)? + $(num_offset: ::std::mem::offset_of!($struct_name, $num),)? offset: { $crate::assert_field_type!($struct_name, $field_name, $type $(, num = $num)?); - $crate::offset_of!($struct_name, $field_name) + ::std::mem::offset_of!($struct_name, $field_name) }, size: ::core::mem::size_of::<$type>(), flags: $crate::bindings::VMStateFlags::VMS_STRUCT, @@ -518,7 +518,7 @@ macro_rules! vmstate_clock { $field_name, $crate::qom::Owned<$crate::qdev::Clock> $(, num = $num)? ); - $crate::offset_of!($struct_name, $field_name) + ::std::mem::offset_of!($struct_name, $field_name) }, size: ::core::mem::size_of::<*const $crate::qdev::Clock>(), flags: $crate::bindings::VMStateFlags( diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index 99a7aab6fe..d045808c3a 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -26,7 +26,6 @@ pub static VMSTATE: VMStateDescription = VMStateDescription { ..Zeroable::ZERO }; -#[derive(qemu_api_macros::offsets)] #[repr(C)] #[derive(qemu_api_macros::Object)] pub struct DummyState { @@ -79,7 +78,6 @@ impl DeviceImpl for DummyState { } } -#[derive(qemu_api_macros::offsets)] #[repr(C)] #[derive(qemu_api_macros::Object)] pub struct DummyChildState { diff --git a/rust/qemu-api/tests/vmstate_tests.rs b/rust/qemu-api/tests/vmstate_tests.rs index 9a56ffd238..cc3527a9b6 100644 --- a/rust/qemu-api/tests/vmstate_tests.rs +++ b/rust/qemu-api/tests/vmstate_tests.rs @@ -33,7 +33,7 @@ const FOO_ARRAY_MAX: usize = 3; // - VMSTATE_VARRAY_UINT16_UNSAFE // - VMSTATE_VARRAY_MULTIPLY #[repr(C)] -#[derive(Default, qemu_api_macros::offsets)] +#[derive(Default)] struct FooA { arr: [u8; FOO_ARRAY_MAX], num: u16, @@ -154,7 +154,7 @@ fn test_vmstate_varray_multiply() { // - VMSTATE_ARRAY // - VMSTATE_STRUCT_VARRAY_UINT8 with BqlCell wrapper & test_fn #[repr(C)] -#[derive(Default, qemu_api_macros::offsets)] +#[derive(Default)] struct FooB { arr_a: [FooA; FOO_ARRAY_MAX], num_a: u8, @@ -329,7 +329,6 @@ struct FooCWrapper([Opaque<*mut u8>; FOO_ARRAY_MAX]); // Though Opaque<> array i impl_vmstate_forward!(FooCWrapper); #[repr(C)] -#[derive(qemu_api_macros::offsets)] struct FooC { ptr: *const i32, ptr_a: NonNull, From f117857b39fb42c56be23316e2d76db5b13cec8d Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 10:39:08 +0200 Subject: [PATCH 10/30] rust: replace c_str! with c"" literals Reviewed-by: Manos Pitsidianakis Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 6 +-- rust/hw/char/pl011/src/device_class.rs | 11 +++-- rust/hw/char/pl011/src/lib.rs | 6 +-- rust/hw/timer/hpet/src/hpet.rs | 19 ++++---- rust/hw/timer/hpet/src/lib.rs | 4 +- rust/qemu-api/meson.build | 1 - rust/qemu-api/src/c_str.rs | 61 -------------------------- rust/qemu-api/src/cell.rs | 4 +- rust/qemu-api/src/lib.rs | 1 - rust/qemu-api/src/vmstate.rs | 2 +- rust/qemu-api/tests/tests.rs | 9 ++-- rust/qemu-api/tests/vmstate_tests.rs | 15 +++---- 12 files changed, 32 insertions(+), 107 deletions(-) delete mode 100644 rust/qemu-api/src/c_str.rs diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 5cfafc532c..557cee72f3 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -81,9 +81,6 @@ are missing: * "Return position ``impl Trait`` in Traits" (1.75.0, blocker for including the pinned-init create). -* ``c"" literals`` (stable in 1.77.0). QEMU provides a ``c_str!()`` macro - to define ``CStr`` constants easily - * inline const expression (stable in 1.79.0), currently worked around with associated constants in the ``FnCall`` trait. @@ -166,7 +163,6 @@ module status ``bitops`` complete ``callbacks`` complete ``cell`` stable -``c_str`` complete ``errno`` complete ``irq`` complete ``memory`` stable @@ -423,7 +419,7 @@ Adding dependencies Generally, the set of dependent crates is kept small. Think twice before adding a new external crate, especially if it comes with a large set of dependencies itself. Sometimes QEMU only needs a small subset of the -functionality; see for example QEMU's ``assertions`` or ``c_str`` modules. +functionality; see for example QEMU's ``assertions`` module. On top of this recommendation, adding external crates to QEMU is a slightly complicated process, mostly due to the need to teach Meson how diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs index cd2dc33c20..d328d84632 100644 --- a/rust/hw/char/pl011/src/device_class.rs +++ b/rust/hw/char/pl011/src/device_class.rs @@ -9,7 +9,6 @@ use std::{ use qemu_api::{ bindings::{qdev_prop_bool, qdev_prop_chr}, - c_str, prelude::*, vmstate::VMStateDescription, vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_unused, @@ -25,7 +24,7 @@ extern "C" fn pl011_clock_needed(opaque: *mut c_void) -> bool { /// Migration subsection for [`PL011State`] clock. static VMSTATE_PL011_CLOCK: VMStateDescription = VMStateDescription { - name: c_str!("pl011/clock").as_ptr(), + name: c"pl011/clock".as_ptr(), version_id: 1, minimum_version_id: 1, needed: Some(pl011_clock_needed), @@ -46,7 +45,7 @@ extern "C" fn pl011_post_load(opaque: *mut c_void, version_id: c_int) -> c_int { } static VMSTATE_PL011_REGS: VMStateDescription = VMStateDescription { - name: c_str!("pl011/regs").as_ptr(), + name: c"pl011/regs".as_ptr(), version_id: 2, minimum_version_id: 2, fields: vmstate_fields! { @@ -70,7 +69,7 @@ static VMSTATE_PL011_REGS: VMStateDescription = VMStateDescription { }; pub static VMSTATE_PL011: VMStateDescription = VMStateDescription { - name: c_str!("pl011").as_ptr(), + name: c"pl011".as_ptr(), version_id: 2, minimum_version_id: 2, post_load: Some(pl011_post_load), @@ -87,14 +86,14 @@ pub static VMSTATE_PL011: VMStateDescription = VMStateDescription { qemu_api::declare_properties! { PL011_PROPERTIES, qemu_api::define_property!( - c_str!("chardev"), + c"chardev", PL011State, char_backend, unsafe { &qdev_prop_chr }, CharBackend ), qemu_api::define_property!( - c_str!("migrate-clk"), + c"migrate-clk", PL011State, migrate_clock, unsafe { &qdev_prop_bool }, diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index dbae76991c..5c4fbc9d14 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -12,13 +12,11 @@ //! See [`PL011State`](crate::device::PL011State) for the device model type and //! the [`registers`] module for register types. -use qemu_api::c_str; - mod device; mod device_class; mod registers; pub use device::pl011_create; -pub const TYPE_PL011: &::std::ffi::CStr = c_str!("pl011"); -pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c_str!("pl011_luminary"); +pub const TYPE_PL011: &::std::ffi::CStr = c"pl011"; +pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c"pl011_luminary"; diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index 719343a20c..779681d650 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -14,7 +14,6 @@ use qemu_api::{ address_space_memory, address_space_stl_le, qdev_prop_bit, qdev_prop_bool, qdev_prop_uint32, qdev_prop_uint8, }, - c_str, cell::{BqlCell, BqlRefCell}, irq::InterruptSource, memory::{ @@ -908,7 +907,7 @@ impl ObjectImpl for HPETState { qemu_api::declare_properties! { HPET_PROPERTIES, qemu_api::define_property!( - c_str!("timers"), + c"timers", HPETState, num_timers, unsafe { &qdev_prop_uint8 }, @@ -916,7 +915,7 @@ qemu_api::declare_properties! { default = HPET_MIN_TIMERS ), qemu_api::define_property!( - c_str!("msi"), + c"msi", HPETState, flags, unsafe { &qdev_prop_bit }, @@ -925,7 +924,7 @@ qemu_api::declare_properties! { default = false, ), qemu_api::define_property!( - c_str!("hpet-intcap"), + c"hpet-intcap", HPETState, int_route_cap, unsafe { &qdev_prop_uint32 }, @@ -933,7 +932,7 @@ qemu_api::declare_properties! { default = 0 ), qemu_api::define_property!( - c_str!("hpet-offset-saved"), + c"hpet-offset-saved", HPETState, hpet_offset_saved, unsafe { &qdev_prop_bool }, @@ -974,7 +973,7 @@ unsafe extern "C" fn hpet_post_load(opaque: *mut c_void, version_id: c_int) -> c } static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription = VMStateDescription { - name: c_str!("hpet/rtc_irq_level").as_ptr(), + name: c"hpet/rtc_irq_level".as_ptr(), version_id: 1, minimum_version_id: 1, needed: Some(hpet_rtc_irq_level_needed), @@ -985,7 +984,7 @@ static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription = VMStateDescription { }; static VMSTATE_HPET_OFFSET: VMStateDescription = VMStateDescription { - name: c_str!("hpet/offset").as_ptr(), + name: c"hpet/offset".as_ptr(), version_id: 1, minimum_version_id: 1, needed: Some(hpet_offset_needed), @@ -996,7 +995,7 @@ static VMSTATE_HPET_OFFSET: VMStateDescription = VMStateDescription { }; static VMSTATE_HPET_TIMER: VMStateDescription = VMStateDescription { - name: c_str!("hpet_timer").as_ptr(), + name: c"hpet_timer".as_ptr(), version_id: 1, minimum_version_id: 1, fields: vmstate_fields! { @@ -1011,10 +1010,10 @@ static VMSTATE_HPET_TIMER: VMStateDescription = VMStateDescription { ..Zeroable::ZERO }; -const VALIDATE_TIMERS_NAME: &CStr = c_str!("num_timers must match"); +const VALIDATE_TIMERS_NAME: &CStr = c"num_timers must match"; static VMSTATE_HPET: VMStateDescription = VMStateDescription { - name: c_str!("hpet").as_ptr(), + name: c"hpet".as_ptr(), version_id: 2, minimum_version_id: 1, pre_save: Some(hpet_pre_save), diff --git a/rust/hw/timer/hpet/src/lib.rs b/rust/hw/timer/hpet/src/lib.rs index 5e7c961c28..1954584a87 100644 --- a/rust/hw/timer/hpet/src/lib.rs +++ b/rust/hw/timer/hpet/src/lib.rs @@ -7,9 +7,7 @@ //! This library implements a device model for the IA-PC HPET (High //! Precision Event Timers) device in QEMU. -use qemu_api::c_str; - pub mod fw_cfg; pub mod hpet; -pub const TYPE_HPET: &::std::ffi::CStr = c_str!("hpet"); +pub const TYPE_HPET: &::std::ffi::CStr = c"hpet"; diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 95e65643ae..1696df705b 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -20,7 +20,6 @@ _qemu_api_rs = static_library( 'src/callbacks.rs', 'src/cell.rs', 'src/chardev.rs', - 'src/c_str.rs', 'src/errno.rs', 'src/irq.rs', 'src/memory.rs', diff --git a/rust/qemu-api/src/c_str.rs b/rust/qemu-api/src/c_str.rs deleted file mode 100644 index 3fa61b59c7..0000000000 --- a/rust/qemu-api/src/c_str.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2024 Red Hat, Inc. -// Author(s): Paolo Bonzini -// SPDX-License-Identifier: GPL-2.0-or-later - -#![doc(hidden)] -//! This module provides a macro to define a constant of type -//! [`CStr`](std::ffi::CStr), for compatibility with versions of -//! Rust that lack `c""` literals. -//! -//! Documentation is hidden because it only exposes macros, which -//! are exported directly from `qemu_api`. - -#[macro_export] -/// Given a string constant _without_ embedded or trailing NULs, return -/// a `CStr`. -/// -/// Needed for compatibility with Rust <1.77. -macro_rules! c_str { - ($str:expr) => {{ - const STRING: &str = concat!($str, "\0"); - const BYTES: &[u8] = STRING.as_bytes(); - - // "for" is not allowed in const context... oh well, - // everybody loves some lisp. This could be turned into - // a procedural macro if this is a problem; alternatively - // Rust 1.72 makes CStr::from_bytes_with_nul a const function. - const fn f(b: &[u8], i: usize) { - if i == b.len() - 1 { - } else if b[i] == 0 { - panic!("c_str argument contains NUL") - } else { - f(b, i + 1) - } - } - f(BYTES, 0); - - // SAFETY: absence of NULs apart from the final byte was checked above - unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(BYTES) } - }}; -} - -#[cfg(test)] -mod tests { - use std::ffi::CStr; - - use crate::c_str; - - #[test] - fn test_cstr_macro() { - let good = c_str!("🦀"); - let good_bytes = b"\xf0\x9f\xa6\x80\0"; - assert_eq!(good.to_bytes_with_nul(), good_bytes); - } - - #[test] - fn test_cstr_macro_const() { - const GOOD: &CStr = c_str!("🦀"); - const GOOD_BYTES: &[u8] = b"\xf0\x9f\xa6\x80\0"; - assert_eq!(GOOD.to_bytes_with_nul(), GOOD_BYTES); - } -} diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs index 1cb9fdcd53..05ce09f6cb 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/qemu-api/src/cell.rs @@ -77,13 +77,13 @@ //! //! ``` //! # use qemu_api::prelude::*; -//! # use qemu_api::{c_str, cell::BqlRefCell, irq::InterruptSource, irq::IRQState}; +//! # use qemu_api::{cell::BqlRefCell, irq::InterruptSource, irq::IRQState}; //! # use qemu_api::{sysbus::SysBusDevice, qom::Owned, qom::ParentField}; //! # const N_GPIOS: usize = 8; //! # struct PL061Registers { /* ... */ } //! # unsafe impl ObjectType for PL061State { //! # type Class = ::Class; -//! # const TYPE_NAME: &'static std::ffi::CStr = c_str!("pl061"); +//! # const TYPE_NAME: &'static std::ffi::CStr = c"pl061"; //! # } //! struct PL061State { //! parent_obj: ParentField, diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 90c1963666..234a94e3c2 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -15,7 +15,6 @@ pub mod prelude; pub mod assertions; pub mod bitops; -pub mod c_str; pub mod callbacks; pub mod cell; pub mod chardev; diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 205f35a21f..9c8b2398e9 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -427,7 +427,7 @@ unsafe impl VMState for [T; N] { macro_rules! vmstate_unused { ($size:expr) => {{ $crate::bindings::VMStateField { - name: $crate::c_str!("unused").as_ptr(), + name: c"unused".as_ptr(), size: $size, info: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_unused_buffer) }, flags: $crate::bindings::VMStateFlags::VMS_BUFFER, diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index d045808c3a..a658a49fcf 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -6,7 +6,6 @@ use std::{ffi::CStr, ptr::addr_of}; use qemu_api::{ bindings::{module_call_init, module_init_type, qdev_prop_bool}, - c_str, cell::{self, BqlCell}, declare_properties, define_property, prelude::*, @@ -21,7 +20,7 @@ mod vmstate_tests; // Test that macros can compile. pub static VMSTATE: VMStateDescription = VMStateDescription { - name: c_str!("name").as_ptr(), + name: c"name".as_ptr(), unmigratable: true, ..Zeroable::ZERO }; @@ -48,7 +47,7 @@ impl DummyClass { declare_properties! { DUMMY_PROPERTIES, define_property!( - c_str!("migrate-clk"), + c"migrate-clk", DummyState, migrate_clock, unsafe { &qdev_prop_bool }, @@ -58,7 +57,7 @@ declare_properties! { unsafe impl ObjectType for DummyState { type Class = DummyClass; - const TYPE_NAME: &'static CStr = c_str!("dummy"); + const TYPE_NAME: &'static CStr = c"dummy"; } impl ObjectImpl for DummyState { @@ -92,7 +91,7 @@ pub struct DummyChildClass { unsafe impl ObjectType for DummyChildState { type Class = DummyChildClass; - const TYPE_NAME: &'static CStr = c_str!("dummy_child"); + const TYPE_NAME: &'static CStr = c"dummy_child"; } impl ObjectImpl for DummyChildState { diff --git a/rust/qemu-api/tests/vmstate_tests.rs b/rust/qemu-api/tests/vmstate_tests.rs index cc3527a9b6..ad0fc5cd5d 100644 --- a/rust/qemu-api/tests/vmstate_tests.rs +++ b/rust/qemu-api/tests/vmstate_tests.rs @@ -14,7 +14,6 @@ use qemu_api::{ vmstate_info_bool, vmstate_info_int32, vmstate_info_int64, vmstate_info_int8, vmstate_info_uint64, vmstate_info_uint8, vmstate_info_unused_buffer, VMStateFlags, }, - c_str, cell::{BqlCell, Opaque}, impl_vmstate_forward, vmstate::{VMStateDescription, VMStateField}, @@ -43,7 +42,7 @@ struct FooA { } static VMSTATE_FOOA: VMStateDescription = VMStateDescription { - name: c_str!("foo_a").as_ptr(), + name: c"foo_a".as_ptr(), version_id: 1, minimum_version_id: 1, fields: vmstate_fields! { @@ -173,7 +172,7 @@ fn validate_foob(_state: &FooB, _version_id: u8) -> bool { } static VMSTATE_FOOB: VMStateDescription = VMStateDescription { - name: c_str!("foo_b").as_ptr(), + name: c"foo_b".as_ptr(), version_id: 2, minimum_version_id: 1, fields: vmstate_fields! { @@ -337,7 +336,7 @@ struct FooC { } static VMSTATE_FOOC: VMStateDescription = VMStateDescription { - name: c_str!("foo_c").as_ptr(), + name: c"foo_c".as_ptr(), version_id: 3, minimum_version_id: 1, fields: vmstate_fields! { @@ -452,13 +451,13 @@ fn validate_food_2(_state: &FooD, _version_id: u8) -> bool { } static VMSTATE_FOOD: VMStateDescription = VMStateDescription { - name: c_str!("foo_d").as_ptr(), + name: c"foo_d".as_ptr(), version_id: 3, minimum_version_id: 1, fields: vmstate_fields! { - vmstate_validate!(FooD, c_str!("foo_d_0"), FooD::validate_food_0), - vmstate_validate!(FooD, c_str!("foo_d_1"), FooD::validate_food_1), - vmstate_validate!(FooD, c_str!("foo_d_2"), validate_food_2), + vmstate_validate!(FooD, c"foo_d_0", FooD::validate_food_0), + vmstate_validate!(FooD, c"foo_d_1", FooD::validate_food_1), + vmstate_validate!(FooD, c"foo_d_2", validate_food_2), }, ..Zeroable::ZERO }; From 1017041ff4f04e3bdff6404b366d53c836f5d2fe Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 10:40:54 +0200 Subject: [PATCH 11/30] docs: rust: update for newer minimum supported version Remove leftover notes for Rust changes between 1.63.0 and 1.77.0. Reviewed-by: Manos Pitsidianakis Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 557cee72f3..4de8637502 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -71,16 +71,9 @@ Building Rust code with ``--enable-modules`` is not supported yet. Supported tools ''''''''''''''' -QEMU supports rustc version 1.63.0 and newer. Notably, the following features +QEMU supports rustc version 1.77.0 and newer. Notably, the following features are missing: -* Generic Associated Types (1.65.0) - -* ``CStr::from_bytes_with_nul()`` as a ``const`` function (1.72.0). - -* "Return position ``impl Trait`` in Traits" (1.75.0, blocker for including - the pinned-init create). - * inline const expression (stable in 1.79.0), currently worked around with associated constants in the ``FnCall`` trait. @@ -105,12 +98,6 @@ are missing: __ https://github.com/rust-lang/rust/pull/125258 -It is expected that QEMU will advance its minimum supported version of -rustc to 1.77.0 as soon as possible; as of January 2025, blockers -for that right now are Debian bookworm and 32-bit MIPS processors. -This unfortunately means that references to statics in constants will -remain an issue. - QEMU also supports version 0.60.x of bindgen, which is missing option ``--generate-cstr``. This option requires version 0.66.x and will be adopted as soon as supporting these older versions is not necessary From 7abf0d95ac6437b38437ad75711c081363995285 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 May 2025 21:58:09 +0200 Subject: [PATCH 12/30] target/i386/emulate: fix target_ulong format strings Do not assume that TARGET_FMT_lx is %llx. Signed-off-by: Paolo Bonzini --- target/i386/emulate/x86_decode.c | 2 +- target/i386/emulate/x86_emu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/target/i386/emulate/x86_decode.c b/target/i386/emulate/x86_decode.c index 7efa2f570e..88be9479a8 100644 --- a/target/i386/emulate/x86_decode.c +++ b/target/i386/emulate/x86_decode.c @@ -26,7 +26,7 @@ static void decode_invalid(CPUX86State *env, struct x86_decode *decode) { - printf("%llx: failed to decode instruction ", env->eip); + printf(TARGET_FMT_lx ": failed to decode instruction ", env->eip); for (int i = 0; i < decode->opcode_len; i++) { printf("%x ", decode->opcode[i]); } diff --git a/target/i386/emulate/x86_emu.c b/target/i386/emulate/x86_emu.c index 26a4876aac..7773b51b95 100644 --- a/target/i386/emulate/x86_emu.c +++ b/target/i386/emulate/x86_emu.c @@ -1241,7 +1241,7 @@ static void init_cmd_handler(void) bool exec_instruction(CPUX86State *env, struct x86_decode *ins) { if (!_cmd_handler[ins->cmd].handler) { - printf("Unimplemented handler (%llx) for %d (%x %x) \n", env->eip, + printf("Unimplemented handler (" TARGET_FMT_lx ") for %d (%x %x) \n", env->eip, ins->cmd, ins->opcode[0], ins->opcode_len > 1 ? ins->opcode[1] : 0); env->eip += ins->len; From c6b04613bdc9a911c0fbe7c114a421991721f878 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 5 May 2025 12:19:55 +0200 Subject: [PATCH 13/30] rust: clippy: enable uninlined_format_args lint Signed-off-by: Paolo Bonzini --- rust/Cargo.toml | 1 + rust/clippy.toml | 1 + rust/qemu-api-macros/src/lib.rs | 16 ++++++++-------- rust/qemu-api/build.rs | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 4f6fe17b50..d9faeecb10 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -89,6 +89,7 @@ suspicious_operation_groupings = "deny" transmute_ptr_to_ptr = "deny" transmute_undefined_repr = "deny" type_repetition_in_bounds = "deny" +uninlined_format_args = "deny" used_underscore_binding = "deny" # nice to have, but cannot be enabled yet diff --git a/rust/clippy.toml b/rust/clippy.toml index 933e46a2ff..58a62c0e63 100644 --- a/rust/clippy.toml +++ b/rust/clippy.toml @@ -1,2 +1,3 @@ doc-valid-idents = ["PrimeCell", ".."] +allow-mixed-uninlined-format-args = false msrv = "1.77.0" diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index dc14cac4ff..f97449bb30 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -18,13 +18,13 @@ fn get_fields<'a>( ) -> Result<&'a Punctuated, MacroError> { let Data::Struct(ref s) = &input.data else { return Err(MacroError::Message( - format!("Struct required for {}", msg), + format!("Struct required for {msg}"), input.ident.span(), )); }; let Fields::Named(ref fs) = &s.fields else { return Err(MacroError::Message( - format!("Named fields required for {}", msg), + format!("Named fields required for {msg}"), input.ident.span(), )); }; @@ -34,19 +34,19 @@ fn get_fields<'a>( fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, MacroError> { let Data::Struct(ref s) = &input.data else { return Err(MacroError::Message( - format!("Struct required for {}", msg), + format!("Struct required for {msg}"), input.ident.span(), )); }; let Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) = &s.fields else { return Err(MacroError::Message( - format!("Tuple struct required for {}", msg), + format!("Tuple struct required for {msg}"), s.fields.span(), )); }; if unnamed.len() != 1 { return Err(MacroError::Message( - format!("A single field is required for {}", msg), + format!("A single field is required for {msg}"), s.fields.span(), )); } @@ -60,7 +60,7 @@ fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { Ok(()) } else { Err(MacroError::Message( - format!("#[repr(C)] required for {}", msg), + format!("#[repr(C)] required for {msg}"), input.ident.span(), )) } @@ -73,7 +73,7 @@ fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> Ok(()) } else { Err(MacroError::Message( - format!("#[repr(transparent)] required for {}", msg), + format!("#[repr(transparent)] required for {msg}"), input.ident.span(), )) } @@ -168,7 +168,7 @@ fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { } Err(MacroError::Message( - format!("#[repr(u8/u16/u32/u64) required for {}", msg), + format!("#[repr(u8/u16/u32/u64) required for {msg}"), input.ident.span(), )) } diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs index 470be2a4d5..1e720641d2 100644 --- a/rust/qemu-api/build.rs +++ b/rust/qemu-api/build.rs @@ -14,7 +14,7 @@ fn main() -> Result<()> { let path = env::var("MESON_BUILD_ROOT") .unwrap_or_else(|_| format!("{}/src", env!("CARGO_MANIFEST_DIR"))); - let file = format!("{}/bindings.inc.rs", path); + let file = format!("{path}/bindings.inc.rs"); let file = Path::new(&file); if !Path::new(&file).exists() { panic!(concat!( @@ -29,7 +29,7 @@ fn main() -> Result<()> { } let out_dir = env::var("OUT_DIR").unwrap(); - let dest_path = format!("{}/bindings.inc.rs", out_dir); + let dest_path = format!("{out_dir}/bindings.inc.rs"); let dest_path = Path::new(&dest_path); if dest_path.symlink_metadata().is_ok() { remove_file(dest_path)?; From eb8f7292e1315be0e36000a847b77153dcf460ef Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 5 May 2025 16:23:26 +0200 Subject: [PATCH 14/30] ci: run RISC-V cross jobs by default The riscv64-debian-cross container is based on Trixie rather than sid these days, so it is pretty much as stable as the others. Enable it by default. Signed-off-by: Paolo Bonzini --- .gitlab-ci.d/container-cross.yml | 3 --- .gitlab-ci.d/crossbuilds.yml | 5 ----- 2 files changed, 8 deletions(-) diff --git a/.gitlab-ci.d/container-cross.yml b/.gitlab-ci.d/container-cross.yml index 34c0e729ad..e45ea17cf1 100644 --- a/.gitlab-ci.d/container-cross.yml +++ b/.gitlab-ci.d/container-cross.yml @@ -67,11 +67,8 @@ ppc64el-debian-cross-container: riscv64-debian-cross-container: extends: .container_job_template stage: containers - # as we are currently based on 'sid/unstable' we may break so... - allow_failure: true variables: NAME: debian-riscv64-cross - QEMU_JOB_OPTIONAL: 1 s390x-debian-cross-container: extends: .container_job_template diff --git a/.gitlab-ci.d/crossbuilds.yml b/.gitlab-ci.d/crossbuilds.yml index 7ae0f966f1..3f76c901ba 100644 --- a/.gitlab-ci.d/crossbuilds.yml +++ b/.gitlab-ci.d/crossbuilds.yml @@ -118,12 +118,8 @@ cross-ppc64el-kvm-only: IMAGE: debian-ppc64el-cross EXTRA_CONFIGURE_OPTS: --disable-tcg --without-default-devices -# The riscv64 cross-builds currently use a 'sid' container to get -# compilers and libraries. Until something more stable is found we -# allow_failure so as not to block CI. cross-riscv64-system: extends: .cross_system_build_job - allow_failure: true needs: job: riscv64-debian-cross-container variables: @@ -131,7 +127,6 @@ cross-riscv64-system: cross-riscv64-user: extends: .cross_user_build_job - allow_failure: true needs: job: riscv64-debian-cross-container variables: From 821ee1c31427a4e08af030469311c2d8ed96f1d1 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 6 May 2025 15:49:27 +0200 Subject: [PATCH 15/30] docs: build-system: fix typo Reviewed-by: Peter Maydell Signed-off-by: Paolo Bonzini --- docs/devel/build-system.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/devel/build-system.rst b/docs/devel/build-system.rst index 258cfad3fe..2c884197a2 100644 --- a/docs/devel/build-system.rst +++ b/docs/devel/build-system.rst @@ -168,7 +168,7 @@ The required versions of the packages are stored in a configuration file ``pythondeps.toml``. The format is custom to QEMU, but it is documented at the top of the file itself and it should be easy to understand. The requirements should make it possible to use the version that is packaged -that is provided by supported distros. +by QEMU's supported distros. When dependencies are downloaded, instead, ``configure`` uses a "known good" version that is also listed in ``pythondeps.toml``. In this From 44d3ec593b0afd2f8cfaf2ba2149465ee3541695 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:38:53 +0900 Subject: [PATCH 16/30] target/arm/helper.c: Fix type conflict of GLib function pointers On Emscripten, function pointer casts can result in runtime failures due to strict function signature checks. This affects the use of g_list_sort and g_slist_sort, which internally perform function pointer casts that are not supported by Emscripten. To avoid these issues, g_list_sort_with_data and g_slist_sort_with_data should be used instead, as they do not rely on function pointer casting. Signed-off-by: Kohei Tokunaga Link: https://lore.kernel.org/r/7d56c82382e8b4f1694b6d7883b2ce3084fdc72d.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- target/arm/helper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index 7fb6e88630..c7ff9f657c 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -222,7 +222,7 @@ static void count_cpreg(gpointer key, gpointer opaque) } } -static gint cpreg_key_compare(gconstpointer a, gconstpointer b) +static gint cpreg_key_compare(gconstpointer a, gconstpointer b, gpointer d) { uint64_t aidx = cpreg_to_kvm_id((uintptr_t)a); uint64_t bidx = cpreg_to_kvm_id((uintptr_t)b); @@ -246,7 +246,7 @@ void init_cpreg_list(ARMCPU *cpu) int arraylen; keys = g_hash_table_get_keys(cpu->cp_regs); - keys = g_list_sort(keys, cpreg_key_compare); + keys = g_list_sort_with_data(keys, cpreg_key_compare, NULL); cpu->cpreg_array_len = 0; From 84cb7025fa9378993dbd6242ab2c3fa556ed2a2c Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:38:54 +0900 Subject: [PATCH 17/30] target/i386/cpu.c: Fix type conflict of GLib function pointers On Emscripten, function pointer casts can result in runtime failures due to strict function signature checks. This affects the use of g_list_sort and g_slist_sort, which internally perform function pointer casts that are not supported by Emscripten. To avoid these issues, g_list_sort_with_data and g_slist_sort_with_data should be used instead, as they do not rely on function pointer casting. Signed-off-by: Kohei Tokunaga Link: https://lore.kernel.org/r/8ee6c2b02c97d5db358c3eb290d00afe71d1ceb7.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 6f21d5ed22..1ca6307c72 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6240,7 +6240,7 @@ static void listflags(GList *features) } /* Sort alphabetically by type name, respecting X86CPUClass::ordering. */ -static gint x86_cpu_list_compare(gconstpointer a, gconstpointer b) +static gint x86_cpu_list_compare(gconstpointer a, gconstpointer b, gpointer d) { ObjectClass *class_a = (ObjectClass *)a; ObjectClass *class_b = (ObjectClass *)b; @@ -6261,7 +6261,7 @@ static gint x86_cpu_list_compare(gconstpointer a, gconstpointer b) static GSList *get_sorted_cpu_model_list(void) { GSList *list = object_class_get_list(TYPE_X86_CPU, false); - list = g_slist_sort(list, x86_cpu_list_compare); + list = g_slist_sort_with_data(list, x86_cpu_list_compare, NULL); return list; } @@ -6318,6 +6318,11 @@ static void x86_cpu_list_entry(gpointer data, gpointer user_data) qemu_printf(" %-20s %s\n", name, desc); } +static gint strcmp_wrap(gconstpointer a, gconstpointer b, gpointer d) +{ + return strcmp(a, b); +} + /* list available CPU models and flags */ static void x86_cpu_list(void) { @@ -6340,7 +6345,7 @@ static void x86_cpu_list(void) } } - names = g_list_sort(names, (GCompareFunc)strcmp); + names = g_list_sort_with_data(names, strcmp_wrap, NULL); qemu_printf("\nRecognized CPUID flags:\n"); listflags(names); From bea4eb18c501e8b55debb1bdc6ccfee2791e1643 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:38:57 +0900 Subject: [PATCH 18/30] target/ppc: Fix type conflict of GLib function pointers On Emscripten, function pointer casts can result in runtime failures due to strict function signature checks. This affects the use of g_list_sort and g_slist_sort, which internally perform function pointer casts that are not supported by Emscripten. To avoid these issues, g_list_sort_with_data and g_slist_sort_with_data should be used instead, as they do not rely on function pointer casting. Signed-off-by: Kohei Tokunaga Link: https://lore.kernel.org/r/d917055d35f5ff7316ccdcbdf57af9a7bd85bf29.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- target/ppc/cpu_init.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/ppc/cpu_init.c b/target/ppc/cpu_init.c index b0973b6df9..09a8d4ff72 100644 --- a/target/ppc/cpu_init.c +++ b/target/ppc/cpu_init.c @@ -7115,7 +7115,7 @@ PowerPCCPUClass *ppc_cpu_get_family_class(PowerPCCPUClass *pcc) } /* Sort by PVR, ordering special case "host" last. */ -static gint ppc_cpu_list_compare(gconstpointer a, gconstpointer b) +static gint ppc_cpu_list_compare(gconstpointer a, gconstpointer b, gpointer d) { ObjectClass *oc_a = (ObjectClass *)a; ObjectClass *oc_b = (ObjectClass *)b; @@ -7183,7 +7183,7 @@ static void ppc_cpu_list(void) qemu_printf("Available CPUs:\n"); list = object_class_get_list(TYPE_POWERPC_CPU, false); - list = g_slist_sort(list, ppc_cpu_list_compare); + list = g_slist_sort_with_data(list, ppc_cpu_list_compare, NULL); g_slist_foreach(list, ppc_cpu_list_entry, NULL); g_slist_free(list); From c4eb098bc1cf3dbcc3627352031c086a6969a7cf Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:38:58 +0900 Subject: [PATCH 19/30] target/s390x: Fix type conflict of GLib function pointers On Emscripten, function pointer casts can result in runtime failures due to strict function signature checks. This affects the use of g_list_sort and g_slist_sort, which internally perform function pointer casts that are not supported by Emscripten. To avoid these issues, g_list_sort_with_data and g_slist_sort_with_data should be used instead, as they do not rely on function pointer casting. Signed-off-by: Kohei Tokunaga Reviewed-by: Thomas Huth Link: https://lore.kernel.org/r/e91c4e266b839f62b5c41173a05896b210ae1180.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- target/s390x/cpu_models.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c index 8951f1b36f..954a7a99a9 100644 --- a/target/s390x/cpu_models.c +++ b/target/s390x/cpu_models.c @@ -373,7 +373,7 @@ static void s390_print_cpu_model_list_entry(gpointer data, gpointer user_data) g_free(name); } -static gint s390_cpu_list_compare(gconstpointer a, gconstpointer b) +static gint s390_cpu_list_compare(gconstpointer a, gconstpointer b, gpointer d) { const S390CPUClass *cc_a = S390_CPU_CLASS((ObjectClass *)a); const S390CPUClass *cc_b = S390_CPU_CLASS((ObjectClass *)b); @@ -415,7 +415,7 @@ void s390_cpu_list(void) qemu_printf("Available CPUs:\n"); list = object_class_get_list(TYPE_S390_CPU, false); - list = g_slist_sort(list, s390_cpu_list_compare); + list = g_slist_sort_with_data(list, s390_cpu_list_compare, NULL); g_slist_foreach(list, s390_print_cpu_model_list_entry, NULL); g_slist_free(list); From 4cd5a25557cab10f8ef81407bed0dd6512526c1c Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:38:59 +0900 Subject: [PATCH 20/30] include/glib-compat.h: Poison g_list_sort and g_slist_sort On Emscripten, function pointer casts can result in runtime failures due to strict function signature checks. This affects the use of g_list_sort and g_slist_sort, which internally perform function pointer casts that are not supported by Emscripten. To avoid these issues, g_list_sort_with_data and g_slist_sort_with_data should be used instead, as they do not rely on function pointer casting. Signed-off-by: Kohei Tokunaga Reviewed-by: Thomas Huth Link: https://lore.kernel.org/r/fa57c8069d6c723f5b947560677f4ca596334330.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- include/glib-compat.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/glib-compat.h b/include/glib-compat.h index 86be439ba0..2e32b90f05 100644 --- a/include/glib-compat.h +++ b/include/glib-compat.h @@ -36,6 +36,13 @@ #include #endif +/* + * These functions perform function pointer casts which can cause function call + * failure on Emscripten. Use g_slist_sort_with_data and g_list_sort_with_data + * instead of these functions. + */ +#pragma GCC poison g_slist_sort g_list_sort + /* * Note that because of the GLIB_VERSION_MAX_ALLOWED constant above, allowing * use of functions from newer GLib via this compat header needs a little From 4c7c05171902d2da678f85c035efdf412ce91c4f Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:00 +0900 Subject: [PATCH 21/30] util/cacheflush.c: Update cache flushing mechanism for Emscripten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Although __builtin___clear_cache is used to flush the instruction cache for a specified memory region, this operation doesn't apply to wasm, as its memory isn't executable. Moreover, Emscripten does not support this builtin and fails to compile it with the following error. > fatal error: error in backend: llvm.clear_cache is not supported on wasm To resolve this, this commit removes the call to __builtin___clear_cache for Emscripten build. Signed-off-by: Kohei Tokunaga Reviewed-by: Philippe Mathieu-Daudé Link: https://lore.kernel.org/r/2926a798fa52a3a5b11c3df4edd1643d2b7cdcb9.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- include/qemu/cacheflush.h | 7 +++++++ util/cacheflush.c | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/include/qemu/cacheflush.h b/include/qemu/cacheflush.h index ae20bcda73..76eb55d818 100644 --- a/include/qemu/cacheflush.h +++ b/include/qemu/cacheflush.h @@ -26,6 +26,13 @@ static inline void flush_idcache_range(uintptr_t rx, uintptr_t rw, size_t len) /* icache is coherent and does not require flushing. */ } +#elif defined(EMSCRIPTEN) + +static inline void flush_idcache_range(uintptr_t rx, uintptr_t rw, size_t len) +{ + /* Wasm doesn't have executable region of memory. */ +} + #else void flush_idcache_range(uintptr_t rx, uintptr_t rw, size_t len); diff --git a/util/cacheflush.c b/util/cacheflush.c index 1d12899a39..17c58918de 100644 --- a/util/cacheflush.c +++ b/util/cacheflush.c @@ -229,6 +229,10 @@ static void __attribute__((constructor)) init_cache_info(void) /* Caches are coherent and do not require flushing; symbol inline. */ +#elif defined(EMSCRIPTEN) + +/* Wasm doesn't have executable region of memory. */ + #elif defined(__aarch64__) && !defined(CONFIG_WIN32) /* * For Windows, we use generic implementation of flush_idcache_range, that From 45e82e495d6a9bf59ee80ed2d4c362596f1b559e Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:01 +0900 Subject: [PATCH 22/30] block: Add including of ioctl header for Emscripten build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Including is still required on Emscripten, just like on other platforms, to make the ioctl function available. Signed-off-by: Kohei Tokunaga Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Stefan Hajnoczi Link: https://lore.kernel.org/r/49b6ecdbd23ff83e3f191ef8a9f7cc2feeaea43f.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- block/file-posix.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/block/file-posix.c b/block/file-posix.c index 0d85123d0f..a845e2836f 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -110,6 +110,10 @@ #include #endif +#ifdef EMSCRIPTEN +#include +#endif + /* OS X does not have O_DSYNC */ #ifndef O_DSYNC #ifdef O_SYNC From 208808242f8a34ac93e73c1c1dbafe4c00498f5e Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:02 +0900 Subject: [PATCH 23/30] block: Fix type conflict of the copy_file_range stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emscripten doesn't provide copy_file_range implementation but it declares this function in its headers. Meson correctly detects the missing implementation and unsets HAVE_COPY_FILE_RANGE. However, the stub defined in file-posix.c causes a type conflict with the declaration from Emscripten during compilation. To fix this error, this commit updates the stub implementation in file-posix.c to exactly match the declaration in Emscripten's headers. The manpage also aligns with this signature. Signed-off-by: Kohei Tokunaga Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Stefan Hajnoczi Link: https://lore.kernel.org/r/938d2beba15d4bd496a600ee401995fbaa385c62.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- block/file-posix.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/block/file-posix.c b/block/file-posix.c index a845e2836f..ef52ed9169 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -2080,8 +2080,11 @@ static int handle_aiocb_write_zeroes_unmap(void *opaque) } #ifndef HAVE_COPY_FILE_RANGE -static off_t copy_file_range(int in_fd, off_t *in_off, int out_fd, - off_t *out_off, size_t len, unsigned int flags) +#ifndef EMSCRIPTEN +static +#endif +ssize_t copy_file_range(int in_fd, off_t *in_off, int out_fd, + off_t *out_off, size_t len, unsigned int flags) { #ifdef __NR_copy_file_range return syscall(__NR_copy_file_range, in_fd, in_off, out_fd, From 0f46bf659f015d4860c7212b4a6f0656b312d4f4 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:03 +0900 Subject: [PATCH 24/30] include/qemu/osdep.h: Add Emscripten-specific OS dependencies On emscripten, some implementations in os-posix.c can't be used such as daemonizing and changing user. This commit introduces os-wasm.c and os-wasm.h which are forked from os-posix.c and os-posix.h and patched for targetting Emscripten. Signed-off-by: Kohei Tokunaga Link: https://lore.kernel.org/r/9fc7b106ecf86675b4532bd6778b7b5945442f89.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- MAINTAINERS | 6 ++ include/qemu/osdep.h | 8 ++- include/system/os-wasm.h | 104 ++++++++++++++++++++++++++++++++++ os-wasm.c | 119 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 include/system/os-wasm.h create mode 100644 os-wasm.c diff --git a/MAINTAINERS b/MAINTAINERS index e748b6375e..2e23dad4bc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -628,6 +628,12 @@ F: .gitlab-ci.d/cirrus/macos-* F: */*.m F: scripts/entitlement.sh +WebAssembly +M: Kohei Tokunaga +S: Maintained +F: include/system/os-wasm.h +F: os-wasm.c + Alpha Machines -------------- M: Richard Henderson diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h index 4397a90680..96fe51bc39 100644 --- a/include/qemu/osdep.h +++ b/include/qemu/osdep.h @@ -8,7 +8,7 @@ * To avoid getting into possible circular include dependencies, this * file should not include any other QEMU headers, with the exceptions * of config-host.h, config-target.h, qemu/compiler.h, - * system/os-posix.h, system/os-win32.h, glib-compat.h and + * system/os-posix.h, system/os-win32.h, system/os-wasm.h, glib-compat.h and * qemu/typedefs.h, all of which are doing a similar job to this file * and are under similar constraints. * @@ -164,10 +164,14 @@ QEMU_EXTERN_C int daemon(int, int); #include "system/os-win32.h" #endif -#ifdef CONFIG_POSIX +#if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) #include "system/os-posix.h" #endif +#if defined(EMSCRIPTEN) +#include "system/os-wasm.h" +#endif + #ifdef __cplusplus extern "C" { #endif diff --git a/include/system/os-wasm.h b/include/system/os-wasm.h new file mode 100644 index 0000000000..3abb3aaa03 --- /dev/null +++ b/include/system/os-wasm.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: MIT */ +/* + * posix specific declarations forked from os-posix.h, removing functions not + * working on Emscripten + * + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2010 Jes Sorensen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef QEMU_OS_WASM_H +#define QEMU_OS_WASM_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SYSMACROS +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void os_set_line_buffering(void); +void os_setup_early_signal_handling(void); +void os_set_proc_name(const char *s); +void os_setup_signal_handling(void); +void os_setup_limits(void); +void os_setup_post(void); +int os_mlock(bool on_fault); +static inline int os_set_daemonize(bool d) +{ + return -1; +}; +bool is_daemonized(void); +static inline void os_daemonize(void) {} + +/** + * qemu_alloc_stack: + * @sz: pointer to a size_t holding the requested usable stack size + * + * Allocate memory that can be used as a stack, for instance for + * coroutines. If the memory cannot be allocated, this function + * will abort (like g_malloc()). This function also inserts an + * additional guard page to catch a potential stack overflow. + * Note that the memory required for the guard page and alignment + * and minimal stack size restrictions will increase the value of sz. + * + * The allocated stack must be freed with qemu_free_stack(). + * + * Returns: pointer to (the lowest address of) the stack memory. + */ +void *qemu_alloc_stack(size_t *sz); + +/** + * qemu_free_stack: + * @stack: stack to free + * @sz: size of stack in bytes + * + * Free a stack allocated via qemu_alloc_stack(). Note that sz must + * be exactly the adjusted stack size returned by qemu_alloc_stack. + */ +void qemu_free_stack(void *stack, size_t sz); + +/* POSIX and Mingw32 differ in the name of the stdio lock functions. */ + +static inline void qemu_flockfile(FILE *f) +{ + flockfile(f); +} + +static inline void qemu_funlockfile(FILE *f) +{ + funlockfile(f); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/os-wasm.c b/os-wasm.c new file mode 100644 index 0000000000..d240c180c5 --- /dev/null +++ b/os-wasm.c @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: MIT */ +/* + * os-wasm.c + * Forked from os-posix.c, removing functions not working on Emscripten + * + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2010 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include +#include +#include +#include +#include + +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "system/runstate.h" +#include "qemu/cutils.h" + +void os_setup_post(void){} +void os_set_line_buffering(void) +{ + setvbuf(stdout, NULL, _IOLBF, 0); +} +void os_setup_early_signal_handling(void) +{ + struct sigaction act; + sigfillset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, NULL); +} +void os_set_proc_name(const char *s) +{ + error_report("Change of process name not supported by your OS"); + exit(1); +} +static void termsig_handler(int signal, siginfo_t *info, void *c) +{ + qemu_system_killed(info->si_signo, info->si_pid); +} + +void os_setup_signal_handling(void) +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_sigaction = termsig_handler; + act.sa_flags = SA_SIGINFO; + sigaction(SIGINT, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); +} +void os_setup_limits(void) +{ + struct rlimit nofile; + + if (getrlimit(RLIMIT_NOFILE, &nofile) < 0) { + warn_report("unable to query NOFILE limit: %s", strerror(errno)); + return; + } + + if (nofile.rlim_cur == nofile.rlim_max) { + return; + } + + nofile.rlim_cur = nofile.rlim_max; + + if (setrlimit(RLIMIT_NOFILE, &nofile) < 0) { + warn_report("unable to set NOFILE limit: %s", strerror(errno)); + return; + } +} +int os_mlock(bool on_fault) +{ +#ifdef HAVE_MLOCKALL + int ret = 0; + int flags = MCL_CURRENT | MCL_FUTURE; + + if (on_fault) { +#ifdef HAVE_MLOCK_ONFAULT + flags |= MCL_ONFAULT; +#else + error_report("mlockall: on_fault not supported"); + return -EINVAL; +#endif + } + + ret = mlockall(flags); + if (ret < 0) { + error_report("mlockall: %s", strerror(errno)); + } + + return ret; +#else + (void)on_fault; + return -ENOSYS; +#endif +} From 1a8964424036cdfe1bd11180953d4e95123990f5 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:04 +0900 Subject: [PATCH 25/30] Disable options unsupported on Emscripten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Daemonizing and run-with aren't supported on Emscripten so disable these flags. Signed-off-by: Kohei Tokunaga Reviewed-by: Philippe Mathieu-Daudé Link: https://lore.kernel.org/r/79c5e591b634762703f3eef6427a192d145799e4.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- qemu-options.hx | 4 ++-- system/vl.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qemu-options.hx b/qemu-options.hx index dc694a99a3..aab53bcfe8 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -4862,7 +4862,7 @@ SRST Start right away with a saved state (``loadvm`` in monitor) ERST -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(EMSCRIPTEN) DEF("daemonize", 0, QEMU_OPTION_daemonize, \ "-daemonize daemonize QEMU after initializing\n", QEMU_ARCH_ALL) #endif @@ -5249,7 +5249,7 @@ HXCOMM Internal use DEF("qtest", HAS_ARG, QEMU_OPTION_qtest, "", QEMU_ARCH_ALL) DEF("qtest-log", HAS_ARG, QEMU_OPTION_qtest_log, "", QEMU_ARCH_ALL) -#ifdef CONFIG_POSIX +#if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) DEF("run-with", HAS_ARG, QEMU_OPTION_run_with, "-run-with [async-teardown=on|off][,chroot=dir][user=username|uid:gid]\n" " Set miscellaneous QEMU process lifecycle options:\n" diff --git a/system/vl.c b/system/vl.c index 520956f4a1..b69d5bb81e 100644 --- a/system/vl.c +++ b/system/vl.c @@ -767,7 +767,7 @@ static QemuOptsList qemu_smp_opts = { }, }; -#if defined(CONFIG_POSIX) +#if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) static QemuOptsList qemu_run_with_opts = { .name = "run-with", .head = QTAILQ_HEAD_INITIALIZER(qemu_run_with_opts.head), @@ -3678,7 +3678,7 @@ void qemu_init(int argc, char **argv) case QEMU_OPTION_nouserconfig: /* Nothing to be parsed here. Especially, do not error out below. */ break; -#if defined(CONFIG_POSIX) +#if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) case QEMU_OPTION_daemonize: os_set_daemonize(true); break; From 8e72b0eb1842ac9813228ad9d9c29203f3823d99 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:05 +0900 Subject: [PATCH 26/30] util: exclude mmap-alloc.c from compilation target on Emscripten Emscripten does not support partial unmapping of mmapped memory regions[1]. This limitation prevents correct implementation of qemu_ram_mmap and qemu_ram_munmap, which rely on partial unmap behavior. As a workaround, this commit excludes mmap-alloc.c from the Emscripten build. Instead, for Emscripten build, this modifies qemu_anon_ram_alloc to use qemu_memalign in place of qemu_ram_mmap, and disable memory backends that rely on mmap, such as memory-backend-file and memory-backend-shm. [1] https://github.com/emscripten-core/emscripten/blob/d4a74336f23214bf3304d9eb0d03966786b30a36/system/lib/libc/emscripten_mmap.c#L61 Signed-off-by: Kohei Tokunaga Link: https://lore.kernel.org/r/76834f933ee4f14eeb5289d21c59d306886e58e9.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- backends/meson.build | 6 ++++-- system/memory.c | 2 +- system/physmem.c | 9 +++++---- util/meson.build | 4 +++- util/oslib-posix.c | 28 ++++++++++++++++++++++++++++ 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/backends/meson.build b/backends/meson.build index da714b93d1..9b88d22685 100644 --- a/backends/meson.build +++ b/backends/meson.build @@ -12,8 +12,10 @@ system_ss.add([files( if host_os != 'windows' system_ss.add(files('rng-random.c')) - system_ss.add(files('hostmem-file.c')) - system_ss.add([files('hostmem-shm.c'), rt]) + if host_os != 'emscripten' + system_ss.add(files('hostmem-file.c')) + system_ss.add([files('hostmem-shm.c'), rt]) + endif endif if host_os == 'linux' system_ss.add(files('hostmem-memfd.c')) diff --git a/system/memory.c b/system/memory.c index 71434e7ad0..63b983efcd 100644 --- a/system/memory.c +++ b/system/memory.c @@ -1627,7 +1627,7 @@ bool memory_region_init_resizeable_ram(MemoryRegion *mr, return true; } -#ifdef CONFIG_POSIX +#if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) bool memory_region_init_ram_from_file(MemoryRegion *mr, Object *owner, const char *name, diff --git a/system/physmem.c b/system/physmem.c index 16cf557d1a..4a7a2e5e12 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -1243,7 +1243,7 @@ long qemu_maxrampagesize(void) return pagesize; } -#ifdef CONFIG_POSIX +#if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) static int64_t get_file_size(int fd) { int64_t size; @@ -1978,7 +1978,7 @@ out_free: } } -#ifdef CONFIG_POSIX +#if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) RAMBlock *qemu_ram_alloc_from_fd(ram_addr_t size, ram_addr_t max_size, qemu_ram_resize_cb resized, MemoryRegion *mr, uint32_t ram_flags, int fd, off_t offset, @@ -2158,7 +2158,8 @@ RAMBlock *qemu_ram_alloc_internal(ram_addr_t size, ram_addr_t max_size, assert(!host ^ (ram_flags & RAM_PREALLOC)); assert(max_size >= size); -#ifdef CONFIG_POSIX /* ignore RAM_SHARED for Windows */ + /* ignore RAM_SHARED for Windows and emscripten*/ +#if defined(CONFIG_POSIX) && !defined(EMSCRIPTEN) if (!host) { if (!share_flags && current_machine->aux_ram_share) { ram_flags |= RAM_SHARED; @@ -2255,7 +2256,7 @@ static void reclaim_ramblock(RAMBlock *block) ; } else if (xen_enabled()) { xen_invalidate_map_cache_entry(block->host); -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(EMSCRIPTEN) } else if (block->fd >= 0) { qemu_ram_munmap(block->fd, block->host, block->max_length); close(block->fd); diff --git a/util/meson.build b/util/meson.build index 780b5977a8..e5cd327e27 100644 --- a/util/meson.build +++ b/util/meson.build @@ -11,7 +11,9 @@ if host_os != 'windows' endif util_ss.add(files('compatfd.c')) util_ss.add(files('event_notifier-posix.c')) - util_ss.add(files('mmap-alloc.c')) + if host_os != 'emscripten' + util_ss.add(files('mmap-alloc.c')) + endif freebsd_dep = [] if host_os == 'freebsd' freebsd_dep = util diff --git a/util/oslib-posix.c b/util/oslib-posix.c index a697c602c6..4ff577e5de 100644 --- a/util/oslib-posix.c +++ b/util/oslib-posix.c @@ -58,6 +58,7 @@ #include #endif +#include "qemu/memalign.h" #include "qemu/mmap-alloc.h" #define MAX_MEM_PREALLOC_THREAD_COUNT 16 @@ -210,11 +211,21 @@ void *qemu_anon_ram_alloc(size_t size, uint64_t *alignment, bool shared, const uint32_t qemu_map_flags = (shared ? QEMU_MAP_SHARED : 0) | (noreserve ? QEMU_MAP_NORESERVE : 0); size_t align = QEMU_VMALLOC_ALIGN; +#ifndef EMSCRIPTEN void *ptr = qemu_ram_mmap(-1, size, align, qemu_map_flags, 0); if (ptr == MAP_FAILED) { return NULL; } +#else + /* + * qemu_ram_mmap is not implemented for Emscripten. Use qemu_memalign + * for the anonymous allocation. noreserve is ignored as there is no swap + * space on Emscripten, and shared is ignored as there is no other + * processes on Emscripten. + */ + void *ptr = qemu_memalign(align, size); +#endif if (alignment) { *alignment = align; @@ -227,7 +238,16 @@ void *qemu_anon_ram_alloc(size_t size, uint64_t *alignment, bool shared, void qemu_anon_ram_free(void *ptr, size_t size) { trace_qemu_anon_ram_free(ptr, size); +#ifndef EMSCRIPTEN qemu_ram_munmap(-1, ptr, size); +#else + /* + * qemu_ram_munmap is not implemented for Emscripten and qemu_memalign + * was used for the allocation. Use the corresponding freeing function + * here. + */ + qemu_vfree(ptr); +#endif } void qemu_socket_set_block(int fd) @@ -588,7 +608,15 @@ bool qemu_prealloc_mem(int fd, char *area, size_t sz, int max_threads, { static gsize initialized; int ret; +#ifndef EMSCRIPTEN size_t hpagesize = qemu_fd_getpagesize(fd); +#else + /* + * mmap-alloc.c is excluded from Emscripten build, so qemu_fd_getpagesize + * is unavailable. Fallback to the lower level implementation. + */ + size_t hpagesize = qemu_real_host_page_size(); +#endif size_t numpages = DIV_ROUND_UP(sz, hpagesize); bool use_madv_populate_write; struct sigaction act; From 5b78d120ffa2c343728600082777a49cbc825ee1 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:06 +0900 Subject: [PATCH 27/30] util: Add coroutine backend for emscripten Emscripten does not support couroutine methods currently used by QEMU but provides a coroutine implementation called "fiber". This commit introduces a coroutine backend using fiber. Note that fiber does not support submitting coroutines to other threads. Signed-off-by: Kohei Tokunaga Acked-by: Stefan Hajnoczi Link: https://lore.kernel.org/r/006b683fd578ed6303a2dc8679094da9a7e6dfb4.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- MAINTAINERS | 1 + util/coroutine-wasm.c | 127 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 util/coroutine-wasm.c diff --git a/MAINTAINERS b/MAINTAINERS index 2e23dad4bc..55c47fcd37 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -633,6 +633,7 @@ M: Kohei Tokunaga S: Maintained F: include/system/os-wasm.h F: os-wasm.c +F: util/coroutine-wasm.c Alpha Machines -------------- diff --git a/util/coroutine-wasm.c b/util/coroutine-wasm.c new file mode 100644 index 0000000000..cb1ec92509 --- /dev/null +++ b/util/coroutine-wasm.c @@ -0,0 +1,127 @@ +/* + * emscripten fiber coroutine initialization code + * based on coroutine-ucontext.c + * + * Copyright (C) 2006 Anthony Liguori + * Copyright (C) 2011 Kevin Wolf + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "qemu/osdep.h" +#include "qemu/coroutine_int.h" +#include "qemu/coroutine-tls.h" + +#include + +typedef struct { + Coroutine base; + void *stack; + size_t stack_size; + + void *asyncify_stack; + size_t asyncify_stack_size; + + CoroutineAction action; + + emscripten_fiber_t fiber; +} CoroutineEmscripten; + +/** + * Per-thread coroutine bookkeeping + */ +QEMU_DEFINE_STATIC_CO_TLS(Coroutine *, current); +QEMU_DEFINE_STATIC_CO_TLS(CoroutineEmscripten *, leader); +size_t leader_asyncify_stack_size = COROUTINE_STACK_SIZE; + +static void coroutine_trampoline(void *co_) +{ + Coroutine *co = co_; + + while (true) { + co->entry(co->entry_arg); + qemu_coroutine_switch(co, co->caller, COROUTINE_TERMINATE); + } +} + +Coroutine *qemu_coroutine_new(void) +{ + CoroutineEmscripten *co; + + co = g_malloc0(sizeof(*co)); + + co->stack_size = COROUTINE_STACK_SIZE; + co->stack = qemu_alloc_stack(&co->stack_size); + + co->asyncify_stack_size = COROUTINE_STACK_SIZE; + co->asyncify_stack = g_malloc0(co->asyncify_stack_size); + emscripten_fiber_init(&co->fiber, coroutine_trampoline, &co->base, + co->stack, co->stack_size, co->asyncify_stack, + co->asyncify_stack_size); + + return &co->base; +} + +void qemu_coroutine_delete(Coroutine *co_) +{ + CoroutineEmscripten *co = DO_UPCAST(CoroutineEmscripten, base, co_); + + qemu_free_stack(co->stack, co->stack_size); + g_free(co->asyncify_stack); + g_free(co); +} + +CoroutineAction qemu_coroutine_switch(Coroutine *from_, Coroutine *to_, + CoroutineAction action) +{ + CoroutineEmscripten *from = DO_UPCAST(CoroutineEmscripten, base, from_); + CoroutineEmscripten *to = DO_UPCAST(CoroutineEmscripten, base, to_); + + set_current(to_); + to->action = action; + emscripten_fiber_swap(&from->fiber, &to->fiber); + return from->action; +} + +Coroutine *qemu_coroutine_self(void) +{ + Coroutine *self = get_current(); + + if (!self) { + CoroutineEmscripten *leaderp = get_leader(); + if (!leaderp) { + leaderp = g_malloc0(sizeof(*leaderp)); + leaderp->asyncify_stack = g_malloc0(leader_asyncify_stack_size); + leaderp->asyncify_stack_size = leader_asyncify_stack_size; + emscripten_fiber_init_from_current_context( + &leaderp->fiber, + leaderp->asyncify_stack, + leaderp->asyncify_stack_size); + leaderp->stack = leaderp->fiber.stack_limit; + leaderp->stack_size = + leaderp->fiber.stack_base - leaderp->fiber.stack_limit; + set_leader(leaderp); + } + self = &leaderp->base; + set_current(self); + } + return self; +} + +bool qemu_in_coroutine(void) +{ + Coroutine *self = get_current(); + + return self && self->caller; +} From ccc403ed5844613849deeaf18cba380b55b6e9bf Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:07 +0900 Subject: [PATCH 28/30] meson: Add wasm build in build scripts has_int128_type is set to false on emscripten as of now to avoid errors by libffi. Tests are disabled on emscripten because they rely on host features that aren't supported by emscripten (e.g. fork and unix socket). Signed-off-by: Kohei Tokunaga Link: https://lore.kernel.org/r/ad03b3b180335f59e785e930968077bf15c46260.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- MAINTAINERS | 1 + configs/meson/emscripten.txt | 8 ++++++++ configure | 7 +++++++ meson.build | 29 ++++++++++++++++++++++++----- meson_options.txt | 2 +- scripts/meson-buildoptions.sh | 2 +- 6 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 configs/meson/emscripten.txt diff --git a/MAINTAINERS b/MAINTAINERS index 55c47fcd37..02b75ea9e1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -634,6 +634,7 @@ S: Maintained F: include/system/os-wasm.h F: os-wasm.c F: util/coroutine-wasm.c +F: configs/meson/emscripten.txt Alpha Machines -------------- diff --git a/configs/meson/emscripten.txt b/configs/meson/emscripten.txt new file mode 100644 index 0000000000..4230e88005 --- /dev/null +++ b/configs/meson/emscripten.txt @@ -0,0 +1,8 @@ +[built-in options] +c_args = ['-pthread'] +cpp_args = ['-pthread'] +objc_args = ['-pthread'] +# -sPROXY_TO_PTHREAD link time flag always requires -pthread even during +# configuration so explicitly add the flag here. +c_link_args = ['-pthread','-sASYNCIFY=1','-sPROXY_TO_PTHREAD=1','-sFORCE_FILESYSTEM','-sALLOW_TABLE_GROWTH','-sTOTAL_MEMORY=2GB','-sWASM_BIGINT','-sEXPORT_ES6=1','-sASYNCIFY_IMPORTS=ffi_call_js','-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction,TTY,FS'] +cpp_link_args = ['-pthread','-sASYNCIFY=1','-sPROXY_TO_PTHREAD=1','-sFORCE_FILESYSTEM','-sALLOW_TABLE_GROWTH','-sTOTAL_MEMORY=2GB','-sWASM_BIGINT','-sEXPORT_ES6=1','-sASYNCIFY_IMPORTS=ffi_call_js','-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction,TTY,FS'] diff --git a/configure b/configure index 40705afdf5..2ce8d29fac 100755 --- a/configure +++ b/configure @@ -360,6 +360,10 @@ elif check_define __NetBSD__; then host_os=netbsd elif check_define __APPLE__; then host_os=darwin +elif check_define EMSCRIPTEN ; then + host_os=emscripten + cpu=wasm32 + cross_compile="yes" else # This is a fatal error, but don't report it yet, because we # might be going to just print the --help text, or it might @@ -526,6 +530,9 @@ case "$cpu" in linux_arch=x86 CPU_CFLAGS="-m64" ;; + wasm32) + CPU_CFLAGS="-m32" + ;; esac if test -n "$host_arch" && { diff --git a/meson.build b/meson.build index 8eb3de5d68..27f1150152 100644 --- a/meson.build +++ b/meson.build @@ -50,9 +50,9 @@ genh = [] qapi_trace_events = [] bsd_oses = ['gnu/kfreebsd', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'darwin'] -supported_oses = ['windows', 'freebsd', 'netbsd', 'openbsd', 'darwin', 'sunos', 'linux'] +supported_oses = ['windows', 'freebsd', 'netbsd', 'openbsd', 'darwin', 'sunos', 'linux', 'emscripten'] supported_cpus = ['ppc', 'ppc64', 's390x', 'riscv32', 'riscv64', 'x86', 'x86_64', - 'arm', 'aarch64', 'loongarch64', 'mips', 'mips64', 'sparc64'] + 'arm', 'aarch64', 'loongarch64', 'mips', 'mips64', 'sparc64', 'wasm32'] cpu = host_machine.cpu_family() @@ -353,6 +353,8 @@ foreach lang : all_languages # endif #endif''') # ok + elif compiler.get_id() == 'emscripten' + # ok else error('You either need GCC v7.4 or Clang v10.0 (or XCode Clang v15.0) to compile QEMU') endif @@ -470,7 +472,10 @@ endif # instead, we can't add -no-pie because it overrides -shared: the linker then # tries to build an executable instead of a shared library and fails. So # don't add -no-pie anywhere and cross fingers. :( -if not get_option('b_pie') +# +# Emscripten doesn't support -no-pie but meson can't catch the compiler +# warning. So explicitly omit the flag for Emscripten. +if not get_option('b_pie') and host_os != 'emscripten' qemu_common_flags += cc.get_supported_arguments('-fno-pie', '-no-pie') endif @@ -514,6 +519,8 @@ ucontext_probe = ''' supported_backends = [] if host_os == 'windows' supported_backends += ['windows'] +elif host_os == 'emscripten' + supported_backends += ['wasm'] else if host_os != 'darwin' and cc.links(ucontext_probe) supported_backends += ['ucontext'] @@ -902,6 +909,10 @@ if get_option('tcg').allowed() if not get_option('tcg_interpreter') error('Unsupported CPU @0@, try --enable-tcg-interpreter'.format(cpu)) endif + elif host_arch == 'wasm32' + if not get_option('tcg_interpreter') + error('WebAssembly host requires --enable-tcg-interpreter') + endif elif get_option('tcg_interpreter') warning('Use of the TCG interpreter is not recommended on this host') warning('architecture. There is a native TCG execution backend available') @@ -2962,7 +2973,9 @@ config_host_data.set('CONFIG_ATOMIC64', cc.links(''' return 0; }''', args: qemu_isa_flags)) -has_int128_type = cc.compiles(''' +# has_int128_type is set to false on Emscripten to avoid errors by libffi +# during runtime. +has_int128_type = host_os != 'emscripten' and cc.compiles(''' __int128_t a; __uint128_t b; int main(void) { b = a; }''') @@ -3775,6 +3788,8 @@ if have_block # os-win32.c does not if host_os == 'windows' system_ss.add(files('os-win32.c')) + elif host_os == 'emscripten' + blockdev_ss.add(files('os-wasm.c')) else blockdev_ss.add(files('os-posix.c')) endif @@ -4516,7 +4531,11 @@ subdir('scripts') subdir('tools') subdir('pc-bios') subdir('docs') -subdir('tests') +# Tests are disabled on emscripten because they rely on host features that aren't +# supported by emscripten (e.g. fork and unix socket). +if host_os != 'emscripten' + subdir('tests') +endif if gtk.found() subdir('po') endif diff --git a/meson_options.txt b/meson_options.txt index 0b4115e733..cc66b46c63 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -34,7 +34,7 @@ option('fuzzing_engine', type : 'string', value : '', option('trace_file', type: 'string', value: 'trace', description: 'Trace file prefix for simple backend') option('coroutine_backend', type: 'combo', - choices: ['ucontext', 'sigaltstack', 'windows', 'auto'], + choices: ['ucontext', 'sigaltstack', 'windows', 'wasm', 'auto'], value: 'auto', description: 'coroutine backend to use') # Everything else can be set via --enable/--disable-* option diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index d76a239130..8a67a14e2e 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -80,7 +80,7 @@ meson_options_help() { printf "%s\n" ' --tls-priority=VALUE Default TLS protocol/cipher priority string' printf "%s\n" ' [NORMAL]' printf "%s\n" ' --with-coroutine=CHOICE coroutine backend to use (choices:' - printf "%s\n" ' auto/sigaltstack/ucontext/windows)' + printf "%s\n" ' auto/sigaltstack/ucontext/windows/wasm)' printf "%s\n" ' --with-pkgversion=VALUE use specified string as sub-version of the' printf "%s\n" ' package' printf "%s\n" ' --with-suffix=VALUE Suffix for QEMU data/modules/config directories' From 8574be0ebc8ad7729ad146055317550e5f6c3e1d Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:08 +0900 Subject: [PATCH 29/30] tests: Add Dockerfile containing dependencies for Emscripten build The added Dockerfile is based on the emsdk image, which includes the Emscripten toolchain. It also cross-compiles the necessary dependencies (glib, libffi, pixman, and zlib) for the Emscripten target environment. Signed-off-by: Kohei Tokunaga Link: https://lore.kernel.org/r/8bed6e9d46ef09328a87320928b5dec575d1e435.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- MAINTAINERS | 1 + .../dockerfiles/emsdk-wasm32-cross.docker | 145 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 tests/docker/dockerfiles/emsdk-wasm32-cross.docker diff --git a/MAINTAINERS b/MAINTAINERS index 02b75ea9e1..9b5e41f616 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -635,6 +635,7 @@ F: include/system/os-wasm.h F: os-wasm.c F: util/coroutine-wasm.c F: configs/meson/emscripten.txt +F: tests/docker/dockerfiles/emsdk-wasm32-cross.docker Alpha Machines -------------- diff --git a/tests/docker/dockerfiles/emsdk-wasm32-cross.docker b/tests/docker/dockerfiles/emsdk-wasm32-cross.docker new file mode 100644 index 0000000000..60a7d02f56 --- /dev/null +++ b/tests/docker/dockerfiles/emsdk-wasm32-cross.docker @@ -0,0 +1,145 @@ +# syntax = docker/dockerfile:1.5 + +ARG EMSDK_VERSION_QEMU=3.1.50 +ARG ZLIB_VERSION=1.3.1 +ARG GLIB_MINOR_VERSION=2.84 +ARG GLIB_VERSION=${GLIB_MINOR_VERSION}.0 +ARG PIXMAN_VERSION=0.44.2 +ARG FFI_VERSION=v3.4.7 +ARG MESON_VERSION=1.5.0 + +FROM emscripten/emsdk:$EMSDK_VERSION_QEMU AS build-base +ARG MESON_VERSION +ENV TARGET=/builddeps/target +ENV CPATH="$TARGET/include" +ENV PKG_CONFIG_PATH="$TARGET/lib/pkgconfig" +ENV EM_PKG_CONFIG_PATH="$PKG_CONFIG_PATH" +ENV CFLAGS="-O3 -pthread -DWASM_BIGINT" +ENV CXXFLAGS="$CFLAGS" +ENV LDFLAGS="-sWASM_BIGINT -sASYNCIFY=1 -L$TARGET/lib" +RUN apt-get update && apt-get install -y \ + autoconf \ + build-essential \ + libglib2.0-dev \ + libtool \ + pkgconf \ + ninja-build \ + python3-pip +RUN pip3 install meson==${MESON_VERSION} tomli +RUN mkdir /build +WORKDIR /build +RUN mkdir -p $TARGET +RUN < /cross.meson +[host_machine] +system = 'emscripten' +cpu_family = 'wasm32' +cpu = 'wasm32' +endian = 'little' + +[binaries] +c = 'emcc' +cpp = 'em++' +ar = 'emar' +ranlib = 'emranlib' +pkgconfig = ['pkg-config', '--static'] +EOT +EOF + +FROM build-base AS zlib-dev +ARG ZLIB_VERSION +RUN mkdir -p /zlib +RUN curl -Ls https://zlib.net/zlib-$ZLIB_VERSION.tar.xz | \ + tar xJC /zlib --strip-components=1 +WORKDIR /zlib +RUN emconfigure ./configure --prefix=$TARGET --static +RUN emmake make install -j$(nproc) + +FROM build-base AS libffi-dev +ARG FFI_VERSION +RUN mkdir -p /libffi +RUN git clone https://github.com/libffi/libffi /libffi +WORKDIR /libffi +RUN git checkout $FFI_VERSION +RUN autoreconf -fiv +RUN emconfigure ./configure --host=wasm32-unknown-linux \ + --prefix=$TARGET --enable-static \ + --disable-shared --disable-dependency-tracking \ + --disable-builddir --disable-multi-os-directory \ + --disable-raw-api --disable-docs +RUN emmake make install SUBDIRS='include' -j$(nproc) + +FROM build-base AS pixman-dev +ARG PIXMAN_VERSION +RUN mkdir /pixman/ +RUN git clone https://gitlab.freedesktop.org/pixman/pixman /pixman/ +WORKDIR /pixman +RUN git checkout pixman-$PIXMAN_VERSION +RUN <> /cross.meson +[built-in options] +c_args = [$(printf "'%s', " $CFLAGS | sed 's/, $//')] +cpp_args = [$(printf "'%s', " $CFLAGS | sed 's/, $//')] +objc_args = [$(printf "'%s', " $CFLAGS | sed 's/, $//')] +c_link_args = [$(printf "'%s', " $LDFLAGS | sed 's/, $//')] +cpp_link_args = [$(printf "'%s', " $LDFLAGS | sed 's/, $//')] +EOT +EOF +RUN meson setup _build --prefix=$TARGET --cross-file=/cross.meson \ + --default-library=static \ + --buildtype=release -Dtests=disabled -Ddemos=disabled +RUN meson install -C _build + +FROM build-base AS glib-dev +ARG GLIB_VERSION +ARG GLIB_MINOR_VERSION +RUN mkdir -p /stub +WORKDIR /stub +RUN < res_query.c +#include +int res_query(const char *name, int class, + int type, unsigned char *dest, int len) +{ + h_errno = HOST_NOT_FOUND; + return -1; +} +EOT +EOF +RUN emcc ${CFLAGS} -c res_query.c -fPIC -o libresolv.o +RUN ar rcs libresolv.a libresolv.o +RUN mkdir -p $TARGET/lib/ +RUN cp libresolv.a $TARGET/lib/ + +RUN mkdir -p /glib +RUN curl -Lks https://download.gnome.org/sources/glib/${GLIB_MINOR_VERSION}/glib-$GLIB_VERSION.tar.xz | \ + tar xJC /glib --strip-components=1 + +COPY --link --from=zlib-dev /builddeps/ /builddeps/ +COPY --link --from=libffi-dev /builddeps/ /builddeps/ + +WORKDIR /glib +RUN <> /cross.meson +[built-in options] +c_args = [$(printf "'%s', " $CFLAGS | sed 's/, $//')] +cpp_args = [$(printf "'%s', " $CFLAGS | sed 's/, $//')] +objc_args = [$(printf "'%s', " $CFLAGS | sed 's/, $//')] +c_link_args = [$(printf "'%s', " $LDFLAGS | sed 's/, $//')] +cpp_link_args = [$(printf "'%s', " $LDFLAGS | sed 's/, $//')] +EOT +EOF +RUN meson setup _build --prefix=$TARGET --cross-file=/cross.meson \ + --default-library=static --buildtype=release --force-fallback-for=pcre2 \ + -Dselinux=disabled -Dxattr=false -Dlibmount=disabled -Dnls=disabled \ + -Dtests=false -Dglib_debug=disabled -Dglib_assert=false -Dglib_checks=false +# FIXME: emscripten doesn't provide some pthread functions in the final link, +# which isn't detected during meson setup. +RUN sed -i -E "/#define HAVE_POSIX_SPAWN 1/d" ./_build/config.h +RUN sed -i -E "/#define HAVE_PTHREAD_GETNAME_NP 1/d" ./_build/config.h +RUN meson install -C _build + +FROM build-base +COPY --link --from=glib-dev /builddeps/ /builddeps/ +COPY --link --from=pixman-dev /builddeps/ /builddeps/ From e6b9b79c3076777b791f72ebdbc9d37ad8005fe9 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Mon, 28 Apr 2025 15:39:09 +0900 Subject: [PATCH 30/30] gitlab: Enable CI for wasm build Add GitLab CI job that builds QEMU using emscripten. The build runs in the container defined in tests/docker/dockerfiles/emsdk-wasm32-cross.docker. Signed-off-by: Kohei Tokunaga Link: https://lore.kernel.org/r/ade0deb2dc65618a91755590f6729485b4001b94.1745820062.git.ktokunaga.mail@gmail.com Signed-off-by: Paolo Bonzini --- .gitlab-ci.d/buildtest-template.yml | 27 +++++++++++++++++++++++++++ .gitlab-ci.d/buildtest.yml | 9 +++++++++ .gitlab-ci.d/container-cross.yml | 5 +++++ 3 files changed, 41 insertions(+) diff --git a/.gitlab-ci.d/buildtest-template.yml b/.gitlab-ci.d/buildtest-template.yml index d4f145fdb5..118371e377 100644 --- a/.gitlab-ci.d/buildtest-template.yml +++ b/.gitlab-ci.d/buildtest-template.yml @@ -115,3 +115,30 @@ - du -chs ${CI_PROJECT_DIR}/*-cache variables: QEMU_JOB_FUNCTIONAL: 1 + +.wasm_build_job_template: + extends: .base_job_template + stage: build + image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG + before_script: + - source scripts/ci/gitlab-ci-section + - section_start setup "Pre-script setup" + - JOBS=$(expr $(nproc) + 1) + - section_end setup + script: + - du -sh .git + - mkdir build + - cd build + - section_start configure "Running configure" + - emconfigure ../configure --disable-docs + ${TARGETS:+--target-list="$TARGETS"} + $CONFIGURE_ARGS || + { cat config.log meson-logs/meson-log.txt && exit 1; } + - if test -n "$LD_JOBS"; + then + pyvenv/bin/meson configure . -Dbackend_max_links="$LD_JOBS" ; + fi || exit 1; + - section_end configure + - section_start build "Building QEMU" + - emmake make -j"$JOBS" + - section_end build diff --git a/.gitlab-ci.d/buildtest.yml b/.gitlab-ci.d/buildtest.yml index 431bc07d8f..248aaed137 100644 --- a/.gitlab-ci.d/buildtest.yml +++ b/.gitlab-ci.d/buildtest.yml @@ -792,3 +792,12 @@ coverity: when: never # Always manual on forks even if $QEMU_CI == "2" - when: manual + +build-wasm: + extends: .wasm_build_job_template + timeout: 2h + needs: + job: wasm-emsdk-cross-container + variables: + IMAGE: emsdk-wasm32-cross + CONFIGURE_ARGS: --static --disable-tools --enable-debug --enable-tcg-interpreter diff --git a/.gitlab-ci.d/container-cross.yml b/.gitlab-ci.d/container-cross.yml index e45ea17cf1..8d3be53b75 100644 --- a/.gitlab-ci.d/container-cross.yml +++ b/.gitlab-ci.d/container-cross.yml @@ -91,3 +91,8 @@ win64-fedora-cross-container: extends: .container_job_template variables: NAME: fedora-win64-cross + +wasm-emsdk-cross-container: + extends: .container_job_template + variables: + NAME: emsdk-wasm32-cross