Skip to content

Commit

Permalink
runtime/type-registry: add new type-registry library for the type c…
Browse files Browse the repository at this point in the history
…onfusion APIs

This is written in Rust like `memory-map` is, so I made the `CMakeLists.txt` very similar.
Since the library may be called concurrently, it uses a hash map which is non-trivial in C,
and we already have Rust libraries like `memory-map`, Rust seemed like a good choice here.

There are a few other things I think we want to do here,
such as ensuring a type doesn't change across the duration of the function call
(not just the beginning); this would be done with a read lock.
Also, we probably want to store the compartment number along with the type ID.
But I'll do these in follow-up PRs once we get the initial parts working.
  • Loading branch information
kkysen committed Jan 6, 2025
1 parent 5adf715 commit a86cd53
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 0 deletions.
1 change: 1 addition & 0 deletions runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ project(runtime)
add_subdirectory(libia2)
add_subdirectory(partition-alloc)
add_subdirectory(tracer)
add_subdirectory(type-registry)
1 change: 1 addition & 0 deletions runtime/tracer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ else()
set(CARGO_TARGET_FLAG "")
set(CARGO_ARCH_SUFFIX "")
endif()

add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_ARCH_SUFFIX}/release/libmemory_map.so
COMMAND CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} cargo build ${CARGO_TARGET_FLAG} --manifest-path ${CMAKE_CURRENT_SOURCE_DIR}/memory-map/Cargo.toml --release
Expand Down
21 changes: 21 additions & 0 deletions runtime/type-registry/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.12)
project(type-registry)

if (LIBIA2_AARCH64)
set(CARGO_TARGET_FLAG "--target=aarch64-unknown-linux-gnu" "--config" "target.aarch64-unknown-linux-gnu.linker=\\\"aarch64-linux-gnu-gcc\\\"")
set(CARGO_ARCH_SUFFIX "aarch64-unknown-linux-gnu")
else()
set(CARGO_TARGET_FLAG "")
set(CARGO_ARCH_SUFFIX "")
endif()

add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_ARCH_SUFFIX}/release/libtype_registry.so
COMMAND CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} cargo build ${CARGO_TARGET_FLAG} --manifest-path ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml --release
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml ${CMAKE_CURRENT_SOURCE_DIR}/src/lib.rs
)

add_library(type-registry STATIC IMPORTED)
set_property(TARGET type-registry PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_ARCH_SUFFIX}/release/libtype_registry.so)
add_custom_target(type-registry-tgt DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_ARCH_SUFFIX}/release/libtype_registry.so)
add_dependencies(type-registry type-registry-tgt)
7 changes: 7 additions & 0 deletions runtime/type-registry/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions runtime/type-registry/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "type-registry"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
176 changes: 176 additions & 0 deletions runtime/type-registry/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::sync::LazyLock;
use std::sync::RwLock;

/// A pointer's address, used purely as an address for comparisons.
#[derive(PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct PtrAddr(*const ());

/// SAFETY: [`PtrAddr`] is used purely as an address.
unsafe impl Send for PtrAddr {}

/// SAFETY: [`PtrAddr`] is used purely as an address.
unsafe impl Sync for PtrAddr {}

impl Display for PtrAddr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}

impl Debug for PtrAddr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}

/// A unique ID for a type.
///
/// This can be anything, as long as it's unique for a type in a program.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct TypeId(usize);

impl Display for TypeId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Type{}", self.0)
}
}

impl Debug for TypeId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}

static GLOBAL_TYPE_REGISTRY: LazyLock<TypeRegistry> = LazyLock::new(TypeRegistry::default);

/// See [`TypeRegistry::construct`].
#[no_mangle]
pub extern "C" fn ia2_type_registry_construct(ptr: PtrAddr, type_id: TypeId) {
GLOBAL_TYPE_REGISTRY.construct(ptr, type_id);
}

/// See [`TypeRegistry::destruct`].
#[no_mangle]
pub extern "C" fn ia2_type_registry_destruct(ptr: PtrAddr, expected_type_id: TypeId) {
GLOBAL_TYPE_REGISTRY.destruct(ptr, expected_type_id);
}

/// See [`TypeRegistry::check`].
#[no_mangle]
pub extern "C" fn ia2_type_registry_check(ptr: PtrAddr, expected_type_id: TypeId) {
GLOBAL_TYPE_REGISTRY.check(ptr, expected_type_id);
}

#[derive(Default)]
pub struct TypeRegistry {
map: RwLock<HashMap<PtrAddr, TypeId>>,
}

impl TypeRegistry {
/// Called after an object is constructed to register it and its type.
///
/// Panics if `ptr` is already constructed.
#[track_caller]
pub fn construct(&self, ptr: PtrAddr, type_id: TypeId) {
let guard = &mut *self.map.write().unwrap();
let prev_type_id = guard.insert(ptr, type_id);
assert_eq!(prev_type_id, None);
}

/// Called after an object is destructured to unregister it and its type.
///
/// Panics if `ptr` has a different type.
#[track_caller]
pub fn destruct(&self, ptr: PtrAddr, expected_type_id: TypeId) {
let guard = &mut *self.map.write().unwrap();
let type_id = guard.remove(&ptr);
assert_eq!(type_id, Some(expected_type_id));
}

/// Called to check a pointer has the expected type.
///
/// Panics if `ptr` is not registered or has a different type.
#[track_caller]
pub fn check(&self, ptr: PtrAddr, expected_type_id: TypeId) {
let guard = &*self.map.read().unwrap();
let type_id = guard.get(&ptr).copied();
assert_eq!(type_id, Some(expected_type_id));
}
}

impl Drop for TypeRegistry {
fn drop(&mut self) {
match self.map.get_mut() {
Ok(map) => {
if !map.is_empty() {
eprintln!("warning: leak: {map:?}");
}
}
Err(e) => {
eprintln!("poison: {e}")
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;

fn ptr(addr: usize) -> PtrAddr {
PtrAddr(addr as *const ())
}

#[test]
fn normal() {
let registry = TypeRegistry::default();
registry.construct(ptr(0xA), TypeId(1));
registry.check(ptr(0xA), TypeId(1));
registry.construct(ptr(0xB), TypeId(2));
registry.check(ptr(0xA), TypeId(1));
registry.check(ptr(0xB), TypeId(2));
registry.destruct(ptr(0xA), TypeId(1));
registry.check(ptr(0xB), TypeId(2));
registry.destruct(ptr(0xB), TypeId(2));
}

#[test]
#[should_panic]
fn destruct_non_existent_ptr() {
let registry = TypeRegistry::default();
registry.destruct(ptr(0xA), TypeId(1));
}

#[test]
#[should_panic]
fn check_non_existent_ptr() {
let registry = TypeRegistry::default();
registry.check(ptr(0xA), TypeId(1));
}

#[test]
#[should_panic]
fn check_freed_ptr() {
let registry = TypeRegistry::default();
registry.construct(ptr(0xA), TypeId(1));
registry.check(ptr(0xA), TypeId(1));
registry.destruct(ptr(0xA), TypeId(1));
registry.check(ptr(0xA), TypeId(1));
}

#[test]
#[should_panic]
fn construct_existing_ptr() {
let registry = TypeRegistry::default();
registry.construct(ptr(0xA), TypeId(1));
registry.check(ptr(0xA), TypeId(1));
registry.construct(ptr(0xA), TypeId(1));
}
}

0 comments on commit a86cd53

Please sign in to comment.