Add assembly memset for fast filling zeros

This commit is contained in:
Shaowei Song 2024-08-28 09:38:43 +00:00 committed by Tate, Hongliang Tian
parent a72c7dadf3
commit aae9fdb331
6 changed files with 80 additions and 9 deletions

View File

@ -20,11 +20,7 @@ impl Device for Zero {
impl FileIo for Zero {
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
// TODO: Use more efficient way when need to read a bunch of zeros once.
let read_len = writer.avail();
for _ in 0..read_len {
writer.write_val(&0u8)?;
}
let read_len = writer.fill_zeros(writer.avail())?;
Ok(read_len)
}

View File

@ -15,12 +15,12 @@ __memcpy_fallible: # (dst: *mut u8, src: *const u8, size: usize) -> usize
.move:
rep movsb
.exit:
.memcpy_exit:
mov rax, rcx
ret
.pushsection .ex_table, "a"
.align 8
.quad [.move]
.quad [.exit]
.quad [.memcpy_exit]
.popsection

View File

@ -0,0 +1,27 @@
/* SPDX-License-Identifier: MPL-2.0 */
// Sets `size` bytes of memory at `dst` to the byte value given by `value`.
// This function works with exception handling and can recover from a page fault.
//
// Returns number of bytes that failed to set.
//
// Ref: [https://github.com/torvalds/linux/blob/2ab79514109578fc4b6df90633d500cf281eb689/arch/x86/lib/memset_64.S]
.text
.global __memset_fallible
.code64
__memset_fallible: # (dst: *mut u8, value: u8, size: usize) -> usize
mov rcx, rdx # Move the size to rcx for counting
mov al, sil # Move the value to al
.set:
rep stosb # Store the value byte repeatedly
.memset_exit:
mov rax, rcx # Return the size remaining
ret
.pushsection .ex_table, "a"
.align 8
.quad [.set]
.quad [.memset_exit]
.popsection

View File

@ -6,7 +6,7 @@ use alloc::fmt;
use core::ops::Range;
use cfg_if::cfg_if;
pub(crate) use util::__memcpy_fallible;
pub(crate) use util::{__memcpy_fallible, __memset_fallible};
use x86_64::{instructions::tlb, structures::paging::PhysFrame, VirtAddr};
use crate::{

View File

@ -1,10 +1,15 @@
// SPDX-License-Identifier: MPL-2.0
core::arch::global_asm!(include_str!("memcpy_fallible.S"));
core::arch::global_asm!(include_str!("memset_fallible.S"));
extern "C" {
/// Copies `size` bytes from `src` to `dst`. This function works with exception handling
/// and can recover from page fault.
/// Returns number of bytes that failed to copy.
pub(crate) fn __memcpy_fallible(dst: *mut u8, src: *const u8, size: usize) -> usize;
/// Fills `size` bytes in the memory pointed to by `dst` with the value `value`.
/// This function works with exception handling and can recover from page fault.
/// Returns number of bytes that failed to set.
pub(crate) fn __memset_fallible(dst: *mut u8, value: u8, size: usize) -> usize;
}

View File

@ -48,7 +48,7 @@ use const_assert::{Assert, IsTrue};
use inherit_methods_macro::inherit_methods;
use crate::{
arch::mm::__memcpy_fallible,
arch::mm::{__memcpy_fallible, __memset_fallible},
mm::{
kspace::{KERNEL_BASE_VADDR, KERNEL_END_VADDR},
MAX_USERSPACE_VADDR,
@ -326,6 +326,21 @@ unsafe fn memcpy_fallible(dst: *mut u8, src: *const u8, len: usize) -> usize {
len - failed_bytes
}
/// Fills `len` bytes of memory at `dst` with the specified `value`.
/// This function will early stop filling if encountering an unresolvable page fault.
///
/// Returns the number of successfully set bytes.
///
/// # Safety
///
/// - `dst` must either be [valid] for writes of `len` bytes or be in user space for `len` bytes.
///
/// [valid]: crate::mm::io#safety
unsafe fn memset_fallible(dst: *mut u8, value: u8, len: usize) -> usize {
let failed_bytes = __memset_fallible(dst, value, len);
len - failed_bytes
}
/// Fallible memory read from a `VmWriter`.
pub trait FallibleVmRead<F> {
/// Reads all data into the writer until one of the three conditions is met:
@ -825,6 +840,34 @@ impl<'a> VmWriter<'a, Fallible> {
})?;
Ok(())
}
/// Writes `len` zeros to the target memory.
///
/// This method attempts to fill up to `len` bytes with zeros. If the available
/// memory from the current cursor position is less than `len`, it will only fill
/// the available space.
///
/// If the memory write failed due to an unresolvable page fault, this method
/// will return `Err` with the length set so far.
pub fn fill_zeros(&mut self, len: usize) -> core::result::Result<usize, (Error, usize)> {
let len_to_set = self.avail().min(len);
if len_to_set == 0 {
return Ok(0);
}
// SAFETY: The destination is a subset of the memory range specified by
// the current writer, so it is either valid for writing or in user space.
let set_len = unsafe {
let set_len = memset_fallible(self.cursor, 0u8, len_to_set);
self.cursor = self.cursor.add(set_len);
set_len
};
if set_len < len_to_set {
Err((Error::PageFault, set_len))
} else {
Ok(len_to_set)
}
}
}
impl<'a, Fallibility> VmWriter<'a, Fallibility> {