Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revive C-based file mapping API and be smart about it #3909

Open
12 tasks
benvanik opened this issue Nov 18, 2020 · 2 comments
Open
12 tasks

Revive C-based file mapping API and be smart about it #3909

benvanik opened this issue Nov 18, 2020 · 2 comments
Assignees
Labels
performance ⚡ Performance/optimization related work across the compiler and runtime runtime/api IREE's public C runtime API and iree-run-module runtime Relating to the IREE runtime library

Comments

@benvanik
Copy link
Collaborator

benvanik commented Nov 18, 2020

This used to be part of the C API but I dropped it in favor of waiting util we had a bit more usage (and a cleaner API) before exposing it. https://github.com/google/iree/blob/08237ac1c7bec3435cd7c626802dfad1b91bbd0e/iree/base/api.h#L462-L488

We should do this again but this time add some of the functionality that lets us abstract over mapped and unmapped memory. Something like iree_memory_buffer_t or something that could either be a mmapped file, shared memory (shm/etc), or just a normal range of bytes.

Features

  • page size support query (ideal page size, page granularity) and large pages
  • nocache/streaming
  • flush
  • locking (wire to physical pages like VirtualLock)
  • discard (DiscardVirtualMemory for writes) or offer (OfferVirtualMemory for reads)
  • subrange prefetching (madvice/PrefetchVirtualMemory/touch striding for malloc)
  • protection (read/readwrite/execute/etc) - useful for JIT/sharing
  • interface + implementations:
    • allocate buffer and manage lifetime with iree_allocator_t (possibly wrap, etc)
    • circular buffer address space overlays: see 1 and 2
    • file mapping:
      • from fd/HANDLE/path

Unblocks better filesystem-less dylib loading (#3845) and enclave explorations (#3910).

@benvanik benvanik added runtime Relating to the IREE runtime library performance ⚡ Performance/optimization related work across the compiler and runtime labels Nov 18, 2020
@benvanik benvanik added this to the 2020Q4 Core milestone Nov 18, 2020
@benvanik benvanik added this to Ideas in Runtime Development via automation Nov 18, 2020
@benvanik benvanik self-assigned this Nov 18, 2020
@benvanik benvanik moved this from Ideas to To do in Runtime Development Nov 19, 2020
@benvanik benvanik added the runtime/api IREE's public C runtime API and iree-run-module label Nov 20, 2020
@benvanik benvanik added this to To do in Integration/Middleware via automation Nov 21, 2020
@benvanik
Copy link
Collaborator Author

From #3427:

If MAP_HUGETLB is supported we definitely want to use that for our constant rodata so we get better TLB cache usage and fewer page faults while doing matmuls. Hard to tell it it's supported, though.

We may also want to tag rodata that is only used once (such as initializer data) with MADV_DONTNEED and all rodata with MADV_WILLNEED (so it's read forward).

@benvanik benvanik added this to In progress in CUDA Backend Jan 28, 2021
benvanik added a commit that referenced this issue Feb 3, 2021
It'll come back as part of #3909.
benvanik added a commit that referenced this issue Feb 3, 2021
It'll come back as part of #3909.
benvanik added a commit that referenced this issue Feb 3, 2021
It'll come back as part of #3909.
benvanik added a commit that referenced this issue Feb 3, 2021
It'll come back as part of #3909.
@benvanik
Copy link
Collaborator Author

benvanik commented Jul 1, 2022

Backlogging this; as a library we leave the actual file IO up to hosting applications. IREE bindings may want to do this and we may want to have the code in iree/base/ to support it. I've been carrying around a branch with some work on this for 2 years now but am not planning on finishing it any time soon. Here for posterity:

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef IREE_BASE_INTERNAL_MEMORY_OBJECT_H_
#define IREE_BASE_INTERNAL_MEMORY_OBJECT_H_

#include "iree/base/api.h"

#if 0

https://github.com/v8/v8/blob/master/src/base/platform/platform-posix.cc#L248

// Defines a range of bytes with page-appropriate alignment.
// Any operation taking page ranges requires that the offset and length respect
// the size and granularity requirements of the page mode the memory was defined
// with. For example, if an allocation is using large pages then both offset and
// length must be multiples of the iree_memory_info_t::large_page_granularity.
typedef struct iree_page_range_t {
  iree_host_size_t offset;
  iree_host_size_t length;
} iree_page_range_t;

// Unions two page ranges to create the min/max extents of both.
static inline iree_page_range_t iree_page_range_union(
    const iree_page_range_t a, const iree_page_range_t b) {
  iree_host_size_t start = iree_min(a.offset, b.offset);
  iree_host_size_t end = iree_max(a.offset + a.length, b.offset + b.length);
  iree_page_range_t union_range = {
      .offset = start,
      .length = end - start,
  };
  return union_range;
}

static inline uintptr_t iree_page_align_start(uintptr_t addr,
                                              iree_host_size_t page_alignment) {
  return addr & (~(page_alignment - 1));
}

static inline uintptr_t iree_page_align_end(uintptr_t addr,
                                            iree_host_size_t page_alignment) {
  return iree_page_align_start(addr + (page_alignment - 1), page_alignment);
}

// Aligns a byte range to page boundaries defined by |page_alignment|.
static inline iree_page_range_t iree_align_byte_range_to_pages(
    const iree_byte_range_t byte_range, iree_host_size_t page_alignment) {
  iree_page_range_t page_range = {
      .offset = iree_host_align(byte_range.offset, page_alignment),
      .length = iree_host_align(byte_range.length, page_alignment),
  };
  return page_range;
}

#endif  // 0

//==============================================================================
// Memory subsystem information
//==============================================================================

// System platform/environment information defining memory parameters.
// These can be used to control application behavior (such as whether to enable
// a JIT if executable pages can be allocated) and allow callers to compute
// memory ranges based on the variable page size of the platform.
typedef struct iree_memory_info_t {
  // The page size and the granularity of page protection and commitment. This
  // is the page size used by the iree_memory_view_t functions.
  iree_host_size_t normal_page_size;

  // The granularity for the starting address at which virtual memory can be
  // allocated.
  iree_host_size_t normal_page_granularity;

  // The minimum page size and granularity for large pages or 0 if unavailable.
  // To use large pages the size and alignment must be a multiple of this value
  // and the IREE_MEMORY_VIEW_FLAG_LARGE_PAGES must be set.
  iree_host_size_t large_page_granularity;

  // Indicates whether executable pages may be allocated within the process.
  // Some platforms or release environments have restrictions on whether
  // executable pages may be allocated from user code (such as iOS).
  bool can_allocate_executable_pages;
} iree_memory_info_t;

// Queries the system platform/environment memory information.
// Callers should cache the results to avoid repeated queries, such as storing
// the used fields in an allocator upon initialization to reuse during
// allocations made via the allocator.
void iree_memory_query_info(iree_memory_info_t* out_info);

//==============================================================================
// Memory types and flags
//==============================================================================

// A reference-counted handle to a memory object.
// Memory objects represent some system-level object that can be mapped into the
// local process address space and manipulated as memory.
//
// Thread-compatible: it's possible to cause races by manipulating the object
// (such as by resizing it) while simultaneously mapping it. External
// synchronization is required particularly when mutating shared memory across
// processes.
typedef struct iree_memory_object_t iree_memory_object_t;

// A reference-counted mapped view into a memory object.
// Views represent virtual memory accessible in the process address space that
// are backed by a memory object.
//
// Thread-compatible: it's possible to cause races by manipulating the view
// (such as by protecting/unprotecting it) while simultaneously using it.
// Treating views as immutable once initialized is recommended and otherwise
// external synchronization is required.
typedef struct iree_memory_view_t iree_memory_view_t;

// Defines which access operations are allowed on a view of memory.
// Attempts to perform an access not originally allowed when the view was
// defined may result in process termination/exceptions/sadness on platforms
// with real MMUs and are generally not detectable: treat limited access as a
// fail-safe mechanism only.
typedef enum iree_memory_access_e {
  // Pages in the view may be read by the process.
  // Some platforms may not respect this value being unset meaning that reads
  // will still succeed.
  IREE_MEMORY_ACCESS_READ = 1u << 0,
  // Pages in the view may be written by the process.
  // If unset then writes will result in process termination.
  IREE_MEMORY_ACCESS_WRITE = 1u << 1,
  // Pages in the view can be executed as native machine code.
  // Callers must ensure iree_memory_info_t::can_allocate_executable_pages is
  // true prior to requesting executable memory as certain platforms or release
  // environments may not support allocating/using executable pages.
  IREE_MEMORY_ACCESS_EXECUTE = 1u << 2,
} iree_memory_access_t;

// Flags used to control the behavior of memory objects.
typedef enum iree_memory_object_flags_e {
  // Enables large page support for the given object, if available.
  // The memory object will be padded to the iree_memory_info_t::large_page_size
  // regardless of whether the pages are actually large to the system.
  //
  // Large pages are only valid for non-file-backed objects and will be ignored
  // when using iree_memory_object_create_from_file.
  //
  // Use large pages to reduce the overhead involved in accessing
  // hot-but-non-localized memory views that may otherwise spend a significant
  // amount of time/capacity maintaining the TLB. As the platform and
  // machine-dependent large page size is often several orders of magnitude
  // larger than the normal page size (MB vs. KB) care should be used to only
  // apply this to large allocations.
  //
  // Implemented by SEC_LARGE_PAGES/MFD_HUGETLB, where available.
  IREE_MEMORY_OBJECT_FLAG_LARGE_PAGES = 1u << 0,
} iree_memory_object_flags_t;

// Flags used to control the behavior of allocated memory views.
typedef enum iree_memory_view_flags_e {
  // Indicates that the memory access pattern of the view is mostly sequential.
  // Hints to the system that an LRU page cache and sequential prefetching are
  // likely to be worth it.
  //
  // Implemented by MADV_SEQUENTIAL.
  IREE_MEMORY_VIEW_FLAG_SEQUENTIAL_ACCESS = 1u << 2,

  // Enables large page support for the given view, if available.
  // Certain mapping modes such as mapping of existing files or opening
  // mappings from another process where the allocation was not made with large
  // pages may not support large pages and the flag will be silently ignored.
  // In either case the memory view will be padded to the
  // iree_memory_info_t::large_page_size regardless of whether the pages are
  // actually large to the system.
  //
  // Use large pages to reduce the overhead involved in accessing
  // hot-but-non-localized memory views that may otherwise spend a significant
  // amount of time/capacity maintaining the TLB. As the platform and
  // machine-dependent large page size is often several orders of magnitude
  // larger than the normal page size (MB vs. KB) care should be used to only
  // apply this to large allocations.
  //
  // Implemented by FILE_MAP_LARGE_PAGES/MAP_HUGETLB, where available.
  IREE_MEMORY_VIEW_FLAG_LARGE_PAGES = 1u << 3,

  // Excludes the view memory from minidumps/coredumps.
  // This is a hint that the memory in the ranges are not useful to include in
  // dumps, such as large chunks of read-only file data (model weights, images,
  // etc).
  //
  // Implemented by WerRegisterExcludedMemoryBlock/MADV_DONTDUMP, where
  // available.
  IREE_MEMORY_VIEW_FLAG_EXCLUDE_FROM_DUMPS = 1u << 4,
} iree_memory_view_flags_t;

// Defines a range of bytes with any arbitrary alignment.
// Most operations will adjust this range by the allocation granularity, meaning
// that a range that stradles a page boundary will be specifying multiple pages
// (such as offset=1, length=4096 with a page size of 4096 indicating 2 pages).
typedef struct iree_byte_range_t {
  iree_host_size_t offset;
  iree_host_size_t length;
} iree_byte_range_t;

// Defines a range of bytes with page-appropriate alignment.
// Any operation taking page ranges requires that the offset and length respect
// the size and granularity requirements of the page mode the memory was defined
// with. For example, if an allocation is using large pages then both offset and
// length must be multiples of the iree_memory_info_t::large_page_granularity.
typedef struct iree_page_range_t {
  iree_host_size_t offset;
  iree_host_size_t length;
} iree_page_range_t;

//==============================================================================
// iree_memory_object_t
//==============================================================================

// Allocates a private memory object in the current local process.
// |identifier| may be used to annotate the view in debuggers and profilers.
//
// |maximum_size| defines the maximum reserved size the allocation may grow to
// while |initial_size| is the committed range. iree_memory_object_resize can be
// used to change the size to any value in [0, maximum_size]. Both values will
// be padded to match the alignment requirements of the memory. If an
// implementation cannot support reservation then the entire |maximum_size|
// range will be committed immediately and resizes will be ignored.
//
// |flags| and |access| define the behavior and protection of the object. Views
// may narrow the protection for their particular ranges.
//
// Upon success |out_memory_object| will be set to the newly-created object.
// Callers must use iree_memory_object_retain and iree_memory_object_release to
// manage the object lifetime.
//
// Implemented by VirtualAlloc/mmap:
//  https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc2
//  https://man7.org/linux/man-pages/man2/mmap.2.html
iree_status_t iree_memory_object_create_private(
    iree_string_view_t identifier, iree_memory_object_flags_t flags,
    iree_memory_access_t access, iree_host_size_t initial_size,
    iree_host_size_t maximum_size, iree_allocator_t allocator,
    iree_memory_object_t** out_memory_object);

// TODO(benvanik): define shared memory interactions (including SEAL).
// iree_status_t iree_memory_object_create_shared();
// iree_status_t iree_memory_object_open_shared();

// Creates a memory object mapping the contents of the given file |path|.
// The file region defined by |file_range| will be opened and made accessible
// for views. |offset| must be a multiple of the requested page size (such as
// iree_memory_info_t::normal_page_size for normal pages).
//
// |flags| define allocation-static behavior and |access| is the initial memory
// protection access mask that can later be modified with
// iree_memory_view_protect. The user and process must have rights to the file
// matching those requested in |access| (such as write access if opening for
// write).
//
// Upon success |out_memory_object| will be set to the newly-created object.
// Callers must use iree_memory_object_retain and iree_memory_object_release to
// manage the object lifetime.
//
// Implemented by CreateFileMapping/memfd_create(/etc)+mmap:
//  https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga
//  https://man7.org/linux/man-pages/man2/memfd_create.2.html
//  https://man7.org/linux/man-pages/man2/mmap.2.html
iree_status_t iree_memory_object_open_file(
    iree_string_view_t file_path, iree_memory_object_flags_t flags,
    iree_memory_access_t access, iree_byte_range_t file_range,
    iree_allocator_t allocator, iree_memory_object_t** out_memory_object);

// Retains the |memory_object| for the caller.
void iree_memory_object_retain(iree_memory_object_t* memory_object);

// Releases the |memory_object| for the caller.
// If the caller is the last retainer of the object the memory will be closed
// (or disposed, if private).
void iree_memory_object_release(iree_memory_object_t* memory_object);

// Resizes the memory object to |new_size|, if supported.
// This may either truncate or extend the object depending on the previous size.
// The maximum size of |new_size| is defined by the |maximum_size| passed to
// iree_memory_object_create_private/iree_memory_object_create_shared. If the
// implementation does not support resizing then the call is ignored; treat
// truncated memory as undefined regardless.
//
// Implemented by VirtualAlloc trickery/ftruncate:
//  https://devblogs.microsoft.com/oldnewthing/20150130-00/?p=44793
//  https://man7.org/linux/man-pages/man2/ftruncate.2.html
iree_status_t iree_memory_object_resize(iree_memory_object_t* memory_object,
                                        iree_host_size_t new_size);

// Creates a memory view mapping the contents of the given |memory_object|.
// The byte range defined by |range| will be mapped and made accessible.
// Both the offset and length of |range| must be a multiple of the requested
// page size (such as iree_memory_info_t::normal_page_size for normal pages).
//
// |base_address| may be NULL to place the view at any available virtual address
// in the calling process. If specified the view will be placed at the address
// assuming that the entire span of pages is not currently in use.
//
// |flags| define allocation-static behavior and |access| is the initial memory
// protection access mask that can later be modified with
// iree_memory_view_protect_ranges. The user and process must have rights to the
// memory object matching those requested in |access| (such as write access if
// opening for write). Both |flags| and |access| must be compatible wtih the
// |memory_object|: for example, if the object was created without execute
// access then a view cannot be set to IREE_MEMORY_ACCESS_EXECUTE.
//
// Upon success |out_memory_view| will be set to the newly-created view.
// Callers must use iree_memory_view_retain and iree_memory_view_release to
// manage the view lifetime.
//
// Implemented by MapViewOfFileEx/mmap:
//  https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffileex
//  https://man7.org/linux/man-pages/man2/mmap.2.html
iree_status_t iree_memory_object_map_view(
    iree_memory_object_t* memory_object, iree_memory_view_flags_t flags,
    iree_memory_access_t access, void* base_address, iree_byte_range_t range,
    iree_allocator_t allocator, iree_memory_view_t** out_memory_view);

//==============================================================================
// iree_memory_view_t
//==============================================================================

// Retains the |memory_view| for the caller.
void iree_memory_view_retain(iree_memory_view_t* memory_view);

// Releases the |memory_view| for the caller.
// If the caller is the last retainer of the view the memory will be unmapped.
void iree_memory_view_release(iree_memory_view_t* memory_view);

// Returns the total size, in bytes, of the memory view. This may be larger
// than the originally requested range if the value was padded to match
// alignment requirements.
iree_host_size_t iree_memory_view_size(iree_memory_view_t* memory_view);

// Returns a byte span into the full view of the memory (including padding).
iree_byte_span_t iree_memory_view_data(iree_memory_view_t* memory_view);

// Returns the alignment, in bytes, of pages within the memory view.
iree_host_size_t iree_memory_view_alignment(iree_memory_view_t* memory_view);

// Changes the access protection of view byte ranges defined by |byte_ranges|.
// Ranges will be adjusted to the page granularity of the view.
//
// Implemented by VirtualProtect/mprotect:
//  https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
//  https://man7.org/linux/man-pages/man2/mprotect.2.html
iree_status_t iree_memory_view_protect_ranges(iree_memory_view_t* memory_view,
                                              iree_host_size_t range_count,
                                              const iree_byte_range_t* ranges,
                                              iree_memory_access_t new_access);

// Hints that a set of readable |memory_view| byte ranges should be paged in.
// Byte ranges will be adjusted to the page granularity of the view.
//
// Some implementations may ignore this such as when the pages are private or
// locked (and thus don't need to be fetched).
//
// Implemented by PrefetchVirtualMemory/madvise(MADV_WILLNEED):
//  https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-prefetchvirtualmemory
//  https://man7.org/linux/man-pages/man2/madvise.2.html
iree_status_t iree_memory_view_prefetch_ranges(iree_memory_view_t* memory_view,
                                               iree_host_size_t range_count,
                                               const iree_byte_range_t* ranges);

// Invalidates a set of |memory_view| byte ranges defined by |byte_ranges|.
// Byte ranges will be adjusted to the page granularity of the view.
//
// Some implementations may ignore this such as when all views of the memory are
// coherent like on Windows.
//
// Implemented by msync(MS_INVALIDATE):
//  https://man7.org/linux/man-pages/man2/msync.2.html
iree_status_t iree_memory_view_invalidate_ranges(
    iree_memory_view_t* memory_view, iree_host_size_t range_count,
    const iree_byte_range_t* ranges);

// Flushes a set of |memory_view| byte ranges defined by |byte_ranges|.
// Byte ranges will be adjusted to the page granularity of the view. The
// contents of the page ranges will become _available_ to other viewers but may
// not become _visible_ until the other process invalidates their view of the
// ranges.
//
// Some implementations may ignore this such as when pages are flushed
// immediately on write or if the memory ranges are locked and cannot be
// flushed.
//
// Locked pages may not be flushed and if any range provided overlaps with
// a locked range the operation will be ignored.
//
// Implemented by FlushViewOfFile/msync:
//  https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-flushviewoffile
//  https://man7.org/linux/man-pages/man2/msync.2.html
iree_status_t iree_memory_view_flush_ranges(iree_memory_view_t* memory_view,
                                            iree_host_size_t range_count,
                                            const iree_byte_range_t* ranges);

// Hints that a set of writable |memory_view| byte ranges should be discarded.
// WARNING: byte ranges will be adjusted to the page granularity of the view.
// This means that it's possible to unintentionally discard pages if for example
// a single byte from a page is included in the range that otherwise has useful
// bytes that should not be discarded.
//
// Discarding is not guaranteed to zero the memory but instead just makes them
// undefined. Upon return the contents of the memory may be the same, garbage,
// or zeros: don't assume anything!
//
// Locked pages may not be discarded and if any range provided overlaps with
// a locked range the operation will be ignored.
//
// Implemented by DiscardVirtualMemory/madvise(MADV_DONTNEED):
//  https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-discardvirtualmemory
//  https://man7.org/linux/man-pages/man2/madvise.2.html
iree_status_t iree_memory_view_discard_ranges(iree_memory_view_t* memory_view,
                                              iree_host_size_t range_count,
                                              const iree_byte_range_t* ranges);

// Locks a set of |memory_view| byte ranges, wiring them to physical memory.
// The locked pages will remain resident until they are unlocked or the view is
// closed.
//
// Whether a page is locked is global state of that page: overlapping locks
// will not stack and unlocking a page will always unlock it regardless of how
// many times it was locked.
//
// Implemented by VirtualLock/mlock:
//  https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtuallock
//  https://man7.org/linux/man-pages/man2/mlock.2.html
iree_status_t iree_memory_view_lock_ranges(iree_memory_view_t* memory_view,
                                           iree_host_size_t range_count,
                                           const iree_byte_range_t* ranges);

// Unlocks a set of |memory_view| byte ranges, possibly unwiring them.
// The system is allowed to swap out the pages but may not do so immediately.
//
// Whether a page is locked is global state of that page: overlapping locks
// will not stack and unlocking a page will always unlock it regardless of how
// many times it was locked.
//
// Implemented by VirtualUnlock/munlock:
//  https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualunlock
//  https://man7.org/linux/man-pages/man2/mlock.2.html
iree_status_t iree_memory_view_unlock_ranges(iree_memory_view_t* memory_view,
                                             iree_host_size_t range_count,
                                             const iree_byte_range_t* ranges);

#endif  // IREE_BASE_INTERNAL_MEMORY_OBJECT_H_
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "iree/base/internal/atomics.h"
#include "iree/base/memory_object.h"
#include "iree/base/target_platform.h"
#include "iree/base/tracing.h"

#if defined(IREE_PLATFORM_WINDOWS)

//==============================================================================
// Memory subsystem information
//==============================================================================

void iree_memory_query_info(iree_memory_info_t* out_info) {
  memset(out_info, 0, sizeof(*out_info));

  SYSTEM_INFO system_info;
  GetSystemInfo(&system_info);
  out_info->normal_page_size = system_info.dwPageSize;
  out_info->normal_page_granularity = system_info.dwAllocationGranularity;

  out_info->large_page_size = GetLargePageMinimum();

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
  out_info->can_allocate_executable_pages = true;
#else
  // The application can define the `codeGeneration` property to enable use of
  // PAGE_EXECUTE but cannot use PAGE_EXECUTE_READWRITE - it's still possible to
  // make that work but it requires aliasing views (one with READWRITE and one
  // with EXECUTE) and I'm not sure if anyone will ever care.
  out_info->can_allocate_executable_pages = false;
#endif  // WINAPI_PARTITION_DESKTOP
}

//==============================================================================
// Memory types and utilities
//==============================================================================

struct iree_memory_object_t {
  iree_atomic_ref_count_t ref_count;
  iree_allocator_t allocator;
  HANDLE handle;
};

struct iree_memory_view_t {
  iree_atomic_ref_count_t ref_count;
  iree_memory_object_t* parent_object;
  iree_host_size_t size;
  uint8_t* base_address;
  iree_host_size_t page_alignment;
};

// Aligns a byte range to page boundaries defined by |page_alignment|.
static iree_page_range_t iree_align_byte_range_to_pages(
    iree_byte_range_t byte_range, iree_host_size_t page_alignment) {
  iree_page_range_t page_range;
  // DO NOT SUBMIT align to page_alignment
  page_range.offset = byte_range.offset;
  page_range.length = byte_range.length;
  return page_range;
}

//==============================================================================
// iree_memory_object_t
//==============================================================================

iree_status_t iree_memory_object_create_private(
    iree_string_view_t identifier, iree_memory_object_flags_t flags,
    iree_memory_access_t access, iree_host_size_t initial_size,
    iree_host_size_t maximum_size, iree_allocator_t allocator,
    iree_memory_object_t** out_memory_object) {
  IREE_TRACE_ZONE_BEGIN(z0);

  // DO NOT SUBMIT
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED);

  IREE_TRACE_ZONE_END(z0);
  return status;
}

iree_status_t iree_memory_object_open_file(
    iree_string_view_t file_path, iree_memory_object_flags_t flags,
    iree_memory_access_t access, iree_byte_range_t file_range,
    iree_allocator_t allocator, iree_memory_object_t** out_memory_object) {
  IREE_TRACE_ZONE_BEGIN(z0);

  // DO NOT SUBMIT
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED);
  // iree_atomic_ref_count_init(&memory_object->ref_count);

  IREE_TRACE_ZONE_END(z0);
  return status;
}

void iree_memory_object_retain(iree_memory_object_t* memory_object) {
  if (memory_object) {
    iree_atomic_ref_count_inc(&memory_object->ref_count);
  }
}

void iree_memory_object_release(iree_memory_object_t* memory_object) {
  if (!memory_object ||
      iree_atomic_ref_count_dec(&memory_object->ref_count) > 1) {
    return;
  }

  iree_allocator_t allocator = memory_object->allocator;
  CloseHandle(memory_object->handle);
  iree_allocator_free(memory_object);
}

iree_status_t iree_memory_object_resize(iree_memory_object_t* memory_object,
                                        iree_host_size_t new_size) {
  IREE_TRACE_ZONE_BEGIN(z0);

  // We could implement this by using MEM_COMMIT/MEM_RESET to commit/decommit
  // reserved ranges but let's wait until we need that.
  // See: https://devblogs.microsoft.com/oldnewthing/20150130-00/?p=44793
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED);

  IREE_TRACE_ZONE_END(z0);
  return status;
}

//==============================================================================
// iree_memory_view_t
//==============================================================================

iree_status_t iree_memory_object_map_view(
    iree_memory_object_t* memory_object, iree_memory_view_flags_t flags,
    iree_memory_access_t access, void* base_address, iree_byte_range_t range,
    iree_allocator_t allocator, iree_memory_view_t** out_memory_view) {
  IREE_TRACE_ZONE_BEGIN(z0);

  DWORD desired_access = 0;
  if (flags & IREE_MEMORY_ACCESS_WRITE) {
    desired_access |= FILE_MAP_WRITE;
  } else if (flags & IREE_MEMORY_ACCESS_READ) {
    desired_access |= FILE_MAP_READ;
  }
  if (flags & IREE_MEMORY_ACCESS_EXECUTE) {
    desired_access |= FILE_MAP_EXECUTE;
  }
  if (flags & IREE_MEMORY_VIEW_FLAG_LARGE_PAGES) {
    desired_access |= FILE_MAP_LARGE_PAGES;
  }

  DWORD file_offset_high = (DWORD)(range.offset >> 32);
  DWORD file_offset_low = (DWORD)range.offset;
  void* view_address =
      MapViewOfFileEx(memory_object->handle, desired_access, file_offset_high,
                      file_offset_low, range.length, base_address);
  if (view_address == NULL) {
    IREE_TRACE_ZONE_END(z0);
    return iree_make_status(iree_status_code_from_win32_error(GetLastError()),
                            "MapViewOfFileEx failed");
  }

  iree_memory_view_t* memory_view = NULL;
  iree_status_t status = iree_allocator_malloc(allocator, sizeof(*memory_view),
                                               (void**)&memory_view);
  if (iree_status_is_ok(status)) {
    iree_atomic_ref_count_init(&memory_view->ref_count);
    memory_view->parent_object = memory_object;
    iree_memory_object_retain(memory_object);
    memory_view->size = range.length;
    memory_view->base_address = view_address;

    iree_memory_info_t memory_info;
    iree_memory_query_info(&memory_info);
    if (flags & IREE_MEMORY_VIEW_FLAG_LARGE_PAGES) {
      memory_view->page_alignment = memory_info.large_page_granularity;
    } else {
      memory_view->page_alignment = memory_info.normal_page_size;
    }

    if (flags & IREE_MEMORY_VIEW_FLAG_EXCLUDE_FROM_DUMPS) {
      WerRegisterExcludedMemoryBlock(view_address, range.length);
    }
  } else {
    UnmapViewOfFile(view_address);
  }

  IREE_TRACE_ZONE_END(z0);
  return status;
}

void iree_memory_view_retain(iree_memory_view_t* memory_view) {
  if (memory_view) {
    iree_atomic_ref_count_inc(&memory_view->ref_count);
  }
}

void iree_memory_view_release(iree_memory_view_t* memory_view) {
  if (!memory_view || iree_atomic_ref_count_dec(&memory_view->ref_count) > 1) {
    return;
  }

  iree_allocator_t allocator = memory_view->parent_object->allocator;
  UnmapViewOfFile(memory_view->base_address);
  iree_memory_object_release(memory_view->parent_object);
  iree_allocator_free(allocator, memory_view);
}

iree_host_size_t iree_memory_view_size(iree_memory_view_t* memory_view) {
  return memory_view->size;
}

iree_byte_span_t iree_memory_view_data(iree_memory_view_t* memory_view) {
  return iree_make_byte_span(memory_view->base_address, memory_view->size);
}

iree_host_size_t iree_memory_view_alignment(iree_memory_view_t* memory_view) {
  return memory_view->page_alignment;
}

iree_status_t iree_memory_view_protect_ranges(iree_memory_view_t* memory_view,
                                              iree_host_size_t range_count,
                                              const iree_byte_range_t* ranges,
                                              iree_memory_access_t new_access) {
  IREE_TRACE_ZONE_BEGIN(z0);

  // https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants
  DWORD new_protect = 0;
  if (new_access & IREE_MEMORY_ACCESS_EXECUTE) {
    if (new_access & IREE_MEMORY_ACCESS_WRITE) {
      new_protect |= PAGE_EXECUTE_READWRITE;
    } else if (new_access & IREE_MEMORY_ACCESS_READ) {
      new_protect |= PAGE_EXECUTE_READ;
    } else {
      new_protect |= PAGE_EXECUTE;
    }
  } else if (new_access & IREE_MEMORY_ACCESS_WRITE) {
    new_protect |= PAGE_READWRITE;
  } else if (new_access & IREE_MEMORY_ACCESS_READ) {
    new_protect |= PAGE_READONLY;
  } else {
    new_protect |= PAGE_NOACCESS;
  }

  uint8_t* base_address = iree_memory_view_data(memory_view).data;

  BOOL ret = TRUE;
  for (iree_host_size_t i = 0; i < range_count; ++i) {
    DWORD old_protect;
    ret = VirtualProtect(base_address + ranges[i].offset, ranges[i].length,
                         new_protect, &old_protect);
    if (!ret) break;
  }

  IREE_TRACE_ZONE_END(z0);
  return ret ? iree_ok_status()
             : iree_make_status(
                   iree_status_code_from_win32_error(GetLastError()),
                   "VirtualProtect failed");

  IREE_TRACE_ZONE_END(z0);
  return status;
}

iree_status_t iree_memory_view_prefetch_ranges(
    iree_memory_view_t* memory_view, iree_host_size_t range_count,
    const iree_byte_range_t* ranges) {
  IREE_TRACE_ZONE_BEGIN(z0);

  uint8_t* base_address = iree_memory_view_data(memory_view).data;

  // NOTE: this function is only a hint and we ignore errors.
  for (iree_host_size_t base_i = 0; base_i < range_count; base_i += 32) {
    iree_host_size_t remaining_count = iree_min(32, range_count - base_i);
    PWIN32_MEMORY_RANGE_ENTRY entries = (WIN32_MEMORY_RANGE_ENTRY*)_alloca(
        sizeof(WIN32_MEMORY_RANGE_ENTRY) * remaining_count);
    for (iree_host_size_t i = 0; i < remaining_count; ++i) {
      entries[i].VirtualAddress = base_address + ranges[base_i + i].offset;
      entries[i].NumberOfBytes = ranges[base_i + i].length;
    }
    PrefetchVirtualMemory(GetCurrentProcess(), remaining_count, entries, 0);
  }

  IREE_TRACE_ZONE_END(z0);
  return iree_ok_status();
}

iree_status_t iree_memory_view_invalidate_ranges(
    iree_memory_view_t* memory_view, iree_host_size_t range_count,
    const iree_byte_range_t* ranges) {
  IREE_TRACE_ZONE_BEGIN(z0);
  // No-op on Windows; all views are coherent.
  IREE_TRACE_ZONE_END(z0);
  return iree_ok_status();
}

iree_status_t iree_memory_view_flush_ranges(iree_memory_view_t* memory_view,
                                            iree_host_size_t range_count,
                                            const iree_byte_range_t* ranges) {
  IREE_TRACE_ZONE_BEGIN(z0);

  iree_host_size_t page_alignment = iree_memory_view_alignment(memory_view);
  uint8_t* base_address = iree_memory_view_data(memory_view).data;

  BOOL ret = TRUE;
  for (iree_host_size_t i = 0; i < range_count; ++i) {
    iree_page_range_t range =
        iree_align_byte_range_to_pages(ranges[i], page_alignment);
    ret = FlushViewOfFile(base_address + range.offset, range.length);
    if (!ret) break;
  }

  IREE_TRACE_ZONE_END(z0);
  return ret ? iree_ok_status()
             : iree_make_status(
                   iree_status_code_from_win32_error(GetLastError()),
                   "FlushViewOfFile failed");
}

iree_status_t iree_memory_view_discard_ranges(iree_memory_view_t* memory_view,
                                              iree_host_size_t range_count,
                                              const iree_byte_range_t* ranges) {
  IREE_TRACE_ZONE_BEGIN(z0);

  iree_host_size_t page_alignment = iree_memory_view_alignment(memory_view);
  uint8_t* base_address = iree_memory_view_data(memory_view).data;

  // NOTE: this function is only a hint and we ignore errors.
  for (iree_host_size_t i = 0; i < range_count; ++i) {
    iree_page_range_t range =
        iree_align_byte_range_to_pages(ranges[i], page_alignment);
    DiscardVirtualMemory(base_address + range.offset, range.length);
  }

  IREE_TRACE_ZONE_END(z0);
  return iree_ok_status();
}

iree_status_t iree_memory_view_lock_ranges(iree_memory_view_t* memory_view,
                                           iree_host_size_t range_count,
                                           const iree_byte_range_t* ranges) {
  IREE_TRACE_ZONE_BEGIN(z0);

  iree_host_size_t page_alignment = iree_memory_view_alignment(memory_view);
  uint8_t* base_address = iree_memory_view_data(memory_view).data;

  BOOL ret = TRUE;
  for (iree_host_size_t i = 0; i < range_count; ++i) {
    iree_page_range_t range =
        iree_align_byte_range_to_pages(ranges[i], page_alignment);
    ret = VirtualLock(base_address + range.offset, range.length);
    if (!ret) break;
  }

  IREE_TRACE_ZONE_END(z0);
  return ret ? iree_ok_status()
             : iree_make_status(
                   iree_status_code_from_win32_error(GetLastError()),
                   "VirtualLock failed");
}

iree_status_t iree_memory_view_unlock_ranges(iree_memory_view_t* memory_view,
                                             iree_host_size_t range_count,
                                             const iree_byte_range_t* ranges) {
  IREE_TRACE_ZONE_BEGIN(z0);

  iree_host_size_t page_alignment = iree_memory_view_alignment(memory_view);
  uint8_t* base_address = iree_memory_view_data(memory_view).data;

  BOOL ret = TRUE;
  for (iree_host_size_t i = 0; i < range_count; ++i) {
    iree_page_range_t range =
        iree_align_byte_range_to_pages(ranges[i], page_alignment);
    ret = VirtualUnlock(base_address + range.offset, range.length);
    if (!ret) break;
  }

  IREE_TRACE_ZONE_END(z0);
  return ret ? iree_ok_status()
             : iree_make_status(
                   iree_status_code_from_win32_error(GetLastError()),
                   "VirtualUnlock failed");
}

#endif  // IREE_PLATFORM_WINDOWS

@julianwa julianwa removed the backlog label Apr 5, 2023
@julianwa julianwa removed this from the 2020Q4 Core milestone Apr 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance ⚡ Performance/optimization related work across the compiler and runtime runtime/api IREE's public C runtime API and iree-run-module runtime Relating to the IREE runtime library
Projects
No open projects
CUDA Backend
In progress
Status: No status
Development

No branches or pull requests

2 participants