diff --git a/Makefile.am b/Makefile.am index 8043b18b..0a50bf18 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,7 +39,7 @@ ACLOCAL_AMFLAGS = -I m4 AM_DISTCHECK_CONFIGURE_FLAGS = --enable-silent-rules LINGUAS='*' -SUBDIRS = wrlib WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc +SUBDIRS = wrlib wutil-rs WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc DIST_SUBDIRS = $(SUBDIRS) test EXTRA_DIST = TODO BUGS BUGFORM FAQ INSTALL \ diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 60875dab..14fbeccc 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -10,10 +10,12 @@ libWUtil_la_LDFLAGS = -version-info @WUTIL_VERSION@ lib_LTLIBRARIES = libWUtil.la libWINGs.la +wutilrs = $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a +wraster = $(top_builddir)/wrlib/libwraster.la -LDADD= libWUtil.la libWINGs.la $(top_builddir)/wrlib/libwraster.la @INTLIBS@ -libWINGs_la_LIBADD = libWUtil.la $(top_builddir)/wrlib/libwraster.la @XLIBS@ @XFT_LIBS@ @FCLIBS@ @LIBM@ @PANGO_LIBS@ -libWUtil_la_LIBADD = @LIBBSD@ +LDADD= libWUtil.la libWINGs.la $(wraster) $(wutilrs) @INTLIBS@ +libWINGs_la_LIBADD = libWUtil.la $(wraster) $(wutilrs) @XLIBS@ @XFT_LIBS@ @FCLIBS@ @LIBM@ @PANGO_LIBS@ +libWUtil_la_LIBADD = @LIBBSD@ $(wutilrs) EXTRA_DIST = BUGS make-rgb Examples Extras Tests @@ -70,7 +72,6 @@ libWUtil_la_SOURCES = \ findfile.c \ handlers.c \ hashtable.c \ - memory.c \ menuparser.c \ menuparser.h \ menuparser_macros.c \ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index feff8ae8..f37662bb 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -213,10 +213,6 @@ void wfree(void *ptr); void wrelease(void *ptr); void* wretain(void *ptr); -typedef void waborthandler(int); - -waborthandler* wsetabort(waborthandler* handler); - /* ---[ WINGs/error.c ]--------------------------------------------------- */ enum { diff --git a/WINGs/memory.c b/WINGs/memory.c deleted file mode 100644 index 63a66dac..00000000 --- a/WINGs/memory.c +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Window Maker miscelaneous function library - * - * Copyright (c) 1997-2003 Alfredo K. Kojima - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -#include "wconfig.h" -#include "WUtil.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_STDNORETURN -#include -#endif - -#ifndef False -# define False 0 -#endif -#ifndef True -# define True 1 -#endif - -static void defaultHandler(int bla) -{ - if (bla) - kill(getpid(), SIGABRT); - else - exit(1); -} - -static waborthandler *aborthandler = defaultHandler; - -static inline noreturn void wAbort(int bla) -{ - (*aborthandler)(bla); - exit(-1); -} - -waborthandler *wsetabort(waborthandler * handler) -{ - waborthandler *old = aborthandler; - - aborthandler = handler; - - return old; -} - -static int Aborting = 0; /* if we're in the middle of an emergency exit */ - -static WMHashTable *table = NULL; - -void *wmalloc(size_t size) -{ - void *tmp; - - assert(size > 0); - - tmp = malloc(size); - if (tmp == NULL) { - wwarning("malloc() failed. Retrying after 2s."); - sleep(2); - tmp = malloc(size); - if (tmp == NULL) { - if (Aborting) { - fputs("Really Bad Error: recursive malloc() failure.", stderr); - exit(-1); - } else { - wfatal("virtual memory exhausted"); - Aborting = 1; - wAbort(False); - } - } - } - if (tmp != NULL) - memset(tmp, 0, size); - return tmp; -} - -void *wrealloc(void *ptr, size_t newsize) -{ - void *nptr; - - if (!ptr) { - nptr = wmalloc(newsize); - } else if (newsize == 0) { - wfree(ptr); - nptr = NULL; - } else { - nptr = realloc(ptr, newsize); - if (nptr == NULL) { - wwarning("realloc() failed. Retrying after 2s."); - sleep(2); - nptr = realloc(ptr, newsize); - if (nptr == NULL) { - if (Aborting) { - fputs("Really Bad Error: recursive realloc() failure.", stderr); - exit(-1); - } else { - wfatal("virtual memory exhausted"); - Aborting = 1; - wAbort(False); - } - } - } - } - return nptr; -} - -void *wretain(void *ptr) -{ - int *refcount; - - if (!table) { - table = WMCreateHashTable(WMIntHashCallbacks); - } - - refcount = WMHashGet(table, ptr); - if (!refcount) { - refcount = wmalloc(sizeof(int)); - *refcount = 1; - WMHashInsert(table, ptr, refcount); -#ifdef VERBOSE - printf("== %i (%p)\n", *refcount, ptr); -#endif - } else { - (*refcount)++; -#ifdef VERBOSE - printf("+ %i (%p)\n", *refcount, ptr); -#endif - } - - return ptr; -} - -void wfree(void *ptr) -{ - if (ptr) - free(ptr); - ptr = NULL; -} - -void wrelease(void *ptr) -{ - int *refcount; - - refcount = WMHashGet(table, ptr); - if (!refcount) { - wwarning("trying to release unexisting data %p", ptr); - } else { - (*refcount)--; - if (*refcount < 1) { -#ifdef VERBOSE - printf("RELEASING %p\n", ptr); -#endif - WMHashRemove(table, ptr); - wfree(refcount); - wfree(ptr); - } -#ifdef VERBOSE - else { - printf("- %i (%p)\n", *refcount, ptr); - } -#endif - } -} diff --git a/WPrefs.app/Makefile.am b/WPrefs.app/Makefile.am index 4408ff96..50c258eb 100644 --- a/WPrefs.app/Makefile.am +++ b/WPrefs.app/Makefile.am @@ -66,6 +66,7 @@ AM_CPPFLAGS = -DRESOURCE_PATH=\"$(wpdatadir)\" -DWMAKER_RESOURCE_PATH=\"$(pkgdat WPrefs_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la WPrefs_LDADD = \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\ $(top_builddir)/WINGs/libWINGs.la\ $(top_builddir)/WINGs/libWUtil.la\ $(top_builddir)/wrlib/libwraster.la \ diff --git a/WPrefs.app/main.c b/WPrefs.app/main.c index 2a76a203..0c47a2f0 100644 --- a/WPrefs.app/main.c +++ b/WPrefs.app/main.c @@ -87,8 +87,6 @@ int main(int argc, char **argv) int i; char *display_name = ""; - wsetabort(wAbort); - memset(DeadHandlers, 0, sizeof(DeadHandlers)); WMInitializeApplication("WPrefs", &argc, argv); diff --git a/configure.ac b/configure.ac index a20cc59e..37e86edd 100644 --- a/configure.ac +++ b/configure.ac @@ -955,6 +955,9 @@ AC_CONFIG_FILES( wrlib/Makefile wrlib/po/Makefile wrlib/tests/Makefile + dnl Rust implementation of WINGs libraries + wutil-rs/Makefile + dnl WINGs toolkit WINGs/Makefile WINGs/WINGs/Makefile WINGs/po/Makefile WINGs/Documentation/Makefile WINGs/Resources/Makefile WINGs/Extras/Makefile diff --git a/src/main.c b/src/main.c index 0f2f9e00..3f5fd558 100644 --- a/src/main.c +++ b/src/main.c @@ -624,7 +624,6 @@ static int real_main(int argc, char **argv) int d, s; setlocale(LC_ALL, ""); - wsetabort(wAbort); /* for telling WPrefs what's the name of the wmaker binary being ran */ setenv("WMAKER_BIN_NAME", argv[0], 1); diff --git a/wutil-rs/Cargo.toml b/wutil-rs/Cargo.toml new file mode 100644 index 00000000..29c8b68b --- /dev/null +++ b/wutil-rs/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "wutil-rs" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["staticlib"] diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am new file mode 100644 index 00000000..f8fcd540 --- /dev/null +++ b/wutil-rs/Makefile.am @@ -0,0 +1,20 @@ +AUTOMAKE_OPTIONS = + +RUST_SOURCES = \ + src/lib.rs \ + src/memory.rs + +RUST_EXTRA = \ + Cargo.lock \ + Cargo.toml + +target/debug/libwutil_rs.a: $(RUST_SOURCES) $(RUST_EXTRA) + $(CARGO) build + +check-local: + $(CARGO) test + +clean-local: + $(CARGO) clean + +all: target/debug/libwutil_rs.a diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs new file mode 100644 index 00000000..eb291915 --- /dev/null +++ b/wutil-rs/src/lib.rs @@ -0,0 +1 @@ +pub mod memory; diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs new file mode 100644 index 00000000..7ced619b --- /dev/null +++ b/wutil-rs/src/memory.rs @@ -0,0 +1,222 @@ +//! Custom implementations of malloc/free/realloc. +//! +//! These are intended for use by C functions that need to allocate. Window +//! Maker originally provided [`wmalloc`], [`wfree`], and [`wrealloc`] for +//! customizable handling of memory exhaustion (to save workspace state before +//! aborting) and to allow optional use of the Boehm GC library. It also tracked +//! reference counts, via [`wretain`] and [`wrelease`]. +//! +//! If everything gets rewritten in Rust, we won't need this module anymore. For +//! now, it helps to move our allocations into Rust so that it is more +//! straightforward to store Rust objects in heap memory that was allocated from +//! C. (Rust may have stricter requirements for heap-allocated segments than are +//! provided by arbitrary C allocators). +//! +//! TODO: We may want to restore handling of OOM errors. This would require +//! installing a customized Rust allocator, which isn't something you can do yet +//! in stable Rust. And, unless our rewrite ends up taking up obscenely more +//! memory than the baseline Window Maker code, it isn't really necessary in +//! this day and age. + +use std::{alloc, mem, ptr::{self, NonNull}}; + +/// Tracks the layout and reference count of an allocated chunk of memory. +#[derive(Clone, Copy)] +struct Header { + ptr: NonNull, + layout: alloc::Layout, + refcount: u16, +} + +impl Header { + /// Recovers the `Header` for the allocated memory chunk `b`. + /// + /// ## Safety + /// + /// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. + unsafe fn for_alloc_bytes(b: *mut u8) -> *mut Header { + unsafe { + b.sub(mem::size_of::
()) + .cast::
() + } + } +} + +/// Allocates at least `size` bytes and returns a pointer to them. +/// +/// Returns null if `size` is 0. +pub fn alloc_bytes(size: usize) -> *mut u8 { + if size == 0 { + return ptr::null_mut(); + } + let header_layout = match alloc::Layout::from_size_align(mem::size_of::
(), 8) { + Ok(x) => x, + Err(_) => return ptr::null_mut(), + }; + + let layout = match alloc::Layout::from_size_align(size, 8) { + Ok(x) => x, + Err(_) => return ptr::null_mut(), + }; + let (layout, result_offset) = match header_layout.extend(layout) { + Ok(x) => x, + Err(_) => return ptr::null_mut(), + }; + + let full_segment = unsafe { alloc::alloc_zeroed(layout) }; + if full_segment.is_null() { + return ptr::null_mut(); + } + let result = unsafe { full_segment.add(result_offset) }; + if result.is_null() { + return ptr::null_mut(); + } + + unsafe { + let header = result.sub(mem::size_of::
()).cast::
(); + header.write_unaligned(Header { + ptr: NonNull::new_unchecked(full_segment), + layout: header_layout, + refcount: 0, + }); + } + + result +} + +/// Frees the bytes pointed to by `b`. +/// +/// ## Safety +/// +/// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. +pub unsafe fn free_bytes(b: *mut u8) { + if b.is_null() { + return; + } + unsafe { + let header = &*Header::for_alloc_bytes(b); + alloc::dealloc(header.ptr.as_ptr(), header.layout); + } +} + +/// Functions to be called from C. +pub mod ffi { + use super::{alloc_bytes, free_bytes, Header}; + + use std::{ffi::c_void, ptr}; + + /// Allocates `size` bytes. Returns null if `sizes is 0. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wmalloc(size: usize) -> *mut c_void { + alloc_bytes(size).cast::() + } + + /// Frees `ptr`, which must have come from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wfree(ptr: *mut c_void) { + unsafe { free_bytes(ptr.cast::()); } + } + + /// Resizes `ptr` to be at least `newsize` bytes in size, returning the + /// start of the new segment. + /// + /// ## Safety + /// + /// Callers must ensure that `ptr` is a live allocation from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wrealloc(ptr: *mut c_void, newsize: usize) -> *mut c_void { + unsafe { + wfree(ptr); + wmalloc(newsize).cast::() + } + } + + /// Bumps the refcount for `ptr`. + /// + /// ## Safety + /// + /// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wretain(ptr: *mut c_void) -> *mut c_void { + if ptr.is_null() { + return ptr::null_mut(); + } + unsafe { + let header = Header::for_alloc_bytes(ptr.cast::()); + (*header).refcount += 1; + } + ptr + } + + /// Decrements the refcount for `ptr`. If this brings the refcount to 0, + /// frees `ptr`. + /// + /// ## Safety + /// + /// Callers must ensure that `ptr` is a live allocation from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wrelease(ptr: *mut c_void) { + let ptr = ptr.cast::(); + if ptr.is_null() { + return; + } + let header = unsafe { &mut *Header::for_alloc_bytes(ptr) }; + match header.refcount { + 0 | 1 => unsafe { free_bytes(ptr) }, + _ => header.refcount -= 1, + } + } +} + +#[cfg(test)] +mod test { + use super::{alloc_bytes, free_bytes, ffi::wrealloc, Header}; + + use std::{mem, os::raw::c_void, ptr}; + + #[test] + fn recover_header() { + unsafe { + let x = alloc_bytes(mem::size_of::()); + let header = Header::for_alloc_bytes(x); + assert_eq!(header.cast::().add(mem::size_of::
()), x); + // This may be allocator-dependent, but it's a reasonable sanity check for now. + assert!((*header).ptr.as_ptr() <= header.cast::()); + } + } + + #[test] + fn alloc_zero_returns_null() { + assert!(alloc_bytes(0).is_null()); + } + + #[test] + fn free_null() { + unsafe { free_bytes(ptr::null_mut()); } + } + + #[test] + fn realloc_null() { + unsafe { assert!(wrealloc(ptr::null_mut(), 0).is_null()); } + } + + #[test] + fn alloc_free_nonzero() { + let x = alloc_bytes(mem::size_of::()).cast::(); + assert!(!x.is_null()); + unsafe { *x = 42; } + assert_eq!(unsafe { *x }, 42); + unsafe { free_bytes(x.cast::()); } + } + + #[test] + fn realloc_nonzero() { + let x = alloc_bytes(mem::size_of::()).cast::(); + assert!(!x.is_null()); + let y = unsafe { wrealloc(x, mem::size_of::()).cast::() }; + assert!(!y.is_null()); + unsafe { *y = 17; } + assert_eq!(unsafe { *y }, 17); + unsafe { free_bytes(y.cast::()); } + } +}