199 lines
7.2 KiB
C
Executable File
199 lines
7.2 KiB
C
Executable File
/* JIT compiler - Flushing the instruction cache.
|
|
|
|
Copyright (C) 1995-2024 Free Software Foundation, Inc.
|
|
|
|
This file 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.1 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This file 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 program. If not, see <https://www.gnu.org/licenses/>. */
|
|
|
|
/* Written by Bruno Haible <bruno@clisp.org>, 2020. */
|
|
|
|
#include <stdint.h>
|
|
#if ENABLE_VALGRIND_SUPPORT
|
|
# include <valgrind/valgrind.h>
|
|
#endif
|
|
#if defined _WIN32 && !defined __CYGWIN__
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
#endif
|
|
#if defined __APPLE__ && defined __MACH__
|
|
# include <libkern/OSCacheControl.h>
|
|
#endif
|
|
#if defined _AIX
|
|
# include <sys/cache.h>
|
|
#endif
|
|
#if defined __sgi
|
|
# include <sys/cachectl.h>
|
|
#endif
|
|
#if defined __sun
|
|
# include <stddef.h>
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
|
|
/* Clears the instruction cache for addresses
|
|
start <= address < end.
|
|
We need this because some CPUs have separate data cache and instruction
|
|
cache. The freshly built trampoline is visible to the data cache, but
|
|
maybe not to the instruction cache. This is hairy. */
|
|
static inline void
|
|
clear_cache (void *start, void *end)
|
|
{
|
|
#if ENABLE_VALGRIND_SUPPORT
|
|
/* Documentation:
|
|
<https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq> */
|
|
VALGRIND_DISCARD_TRANSLATIONS (start, (char *) end - (char *) start);
|
|
#endif
|
|
#if (defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86)
|
|
/* On this architecture, data cache and instruction cache are not separate.
|
|
Therefore, nothing to do.
|
|
For details, see
|
|
<https://stackoverflow.com/questions/10989403/how-is-x86-instruction-cache-synchronized> */
|
|
|
|
/* Use the operating system provided function, when available. */
|
|
#elif defined _WIN32 && !defined __CYGWIN__
|
|
/* Native Windows.
|
|
FlushInstructionCache
|
|
<https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-flushinstructioncache> */
|
|
HANDLE process = GetCurrentProcess ();
|
|
while (!FlushInstructionCache (process, start, (char *) end - (char *) start))
|
|
;
|
|
#elif defined __APPLE__ && defined __MACH__
|
|
/* macOS */
|
|
sys_icache_invalidate (start, (char *) end - (char *) start);
|
|
#elif defined _AIX
|
|
/* AIX. */
|
|
_sync_cache_range (start, (char *) end - (char *) start);
|
|
#elif defined __sgi
|
|
/* IRIX. */
|
|
cacheflush (start, (char *) end - (char *) start, ICACHE);
|
|
#elif defined __sun
|
|
/* Solaris. */
|
|
extern void sync_instruction_memory (char *, size_t);
|
|
sync_instruction_memory (start, (char *) end - (char *) start);
|
|
|
|
/* No operating system provided function. Dispatch according to the CPU. */
|
|
#elif (defined __GNUC__ || defined __clang__) && defined __powerpc__
|
|
/* XXX Is this enough, or do we also need the 'clf' instruction? */
|
|
uintptr_t addr = (uintptr_t) start & ~(intptr_t)3;
|
|
uintptr_t end_addr = (uintptr_t) end;
|
|
do
|
|
{
|
|
asm volatile ("icbi 0,%0; dcbf 0,%0" : : "r" (addr));
|
|
addr += 4;
|
|
}
|
|
while (addr < end_addr);
|
|
asm volatile ("sync; isync");
|
|
#elif (defined __GNUC__ || defined __clang__) && defined __sparc
|
|
/* Use inline assembly. */
|
|
/* The 'flush' instruction was earlier called 'iflush'. */
|
|
uintptr_t addr = (uintptr_t) start & ~(intptr_t)7;
|
|
uintptr_t end_addr = (uintptr_t) end;
|
|
do
|
|
{
|
|
asm volatile ("flush %0+0" : : "r" (addr));
|
|
addr += 8;
|
|
}
|
|
while (addr < end_addr);
|
|
#elif (defined __GNUC__ || defined __clang__) && defined __hppa
|
|
/* Use inline assembly. */
|
|
/* The PA-RISC 1.1 Architecture and Instruction Set Reference Manual says:
|
|
"A cache line can be 16, 32, or 64 bytes in length." */
|
|
/* XXX Is this good enough, or do we need the space register business
|
|
like in gcc/gcc/config/pa/pa.md and libffcall/trampoline/cache-hppa.c? */
|
|
intptr_t cache_line_size = 16;
|
|
uintptr_t addr = (uintptr_t) start & ~cache_line_size;
|
|
uintptr_t end_addr = (uintptr_t) end;
|
|
do
|
|
{
|
|
asm volatile ("fdc 0(0,%0)"
|
|
"\n\t" "sync"
|
|
"\n\t" "fic 0(0,%0)"
|
|
"\n\t" "sync" : : "r" (addr));
|
|
addr += cache_line_size;
|
|
}
|
|
while (addr < end_addr);
|
|
asm volatile ("nop"
|
|
"\n\t" "nop"
|
|
"\n\t" "nop"
|
|
"\n\t" "nop"
|
|
"\n\t" "nop"
|
|
"\n\t" "nop");
|
|
#elif (defined __GNUC__ || defined __clang__) && defined __ia64
|
|
/* Use inline assembly. */
|
|
/* The Intel IA-64 Architecture Software Developer's Manual volume 3 says:
|
|
"The line size affected is at least 32 bytes." */
|
|
intptr_t cache_line_size = 32;
|
|
uintptr_t addr = (uintptr_t) start & ~cache_line_size;
|
|
uintptr_t end_addr = (uintptr_t) end;
|
|
do
|
|
{
|
|
/* Flush a cache line. */
|
|
asm volatile ("fc %0" : : "r" (addr));
|
|
addr += cache_line_size;
|
|
}
|
|
while (addr < end_addr);
|
|
/* Ensure the preceding 'fc' instructions become effective in the local
|
|
processor and all remote processors. */
|
|
asm volatile ("sync.i");
|
|
/* Ensure the preceding 'sync.i' instruction becomes effective in the
|
|
local processor's instruction cache. */
|
|
asm volatile ("srlz.i");
|
|
#elif (defined __GNUC__ || defined __clang__) && defined __m68k__ && defined __linux__
|
|
/* Use inline assembly to call the 'cacheflush' system call.
|
|
sys_cacheflush (addr, scope, cache, len)
|
|
d1 d2 d3 d4
|
|
*/
|
|
register uintptr_t addr __asm__ ("%d1") = (uintptr_t) start;
|
|
register uintptr_t len __asm__ ("%d4") = (uintptr_t) end - addr;
|
|
__asm__ __volatile__ (
|
|
"move%.l %#123,%/d0" /* __NR_cacheflush */
|
|
"\n\t" "move%.l %#1,%/d2" /* FLUSH_SCOPE_LINE */
|
|
"\n\t" "move%.l %#3,%/d3" /* FLUSH_CACHE_BOTH */
|
|
"\n\t" "trap %#0"
|
|
:
|
|
: "d" (addr), "d" (len)
|
|
: "%d0", "%d2", "%d3"
|
|
);
|
|
#elif (__GNUC__ + (__GNUC_MINOR__ >= 3) > 4 && !defined __clang__) \
|
|
|| ((__clang_major__ + (__clang_minor__ >= 4) > 3) \
|
|
&& (defined __aarch64__ /* arm64 */ || defined __arm__))
|
|
/* GCC >= 4.3 has a GCC built-in.
|
|
<https://gcc.gnu.org/onlinedocs/gcc-4.3.6/gcc/Other-Builtins.html>
|
|
But it's sometimes not correctly implemented.
|
|
clang >= 3.4 has it as well, at least on ARM and ARM64. */
|
|
/* On ARM, cache flushing can only be done through a system call.
|
|
GCC implements it for Linux with EABI, through an "swi 0" with code
|
|
0xf0002. For other systems, it may be an "swi 0x9f0002",
|
|
an "swi 0xf00000", or similar. */
|
|
/* On ARM64, cache flushing is done through special instructions,
|
|
and the length of the cache lines must be determined at runtime.
|
|
See gcc/libgcc/config/aarch64/sync-cache.c. */
|
|
__builtin___clear_cache (start, end);
|
|
#elif HAVE___CLEAR_CACHE
|
|
/* Older versions of GCC have this libgcc function, but only on some
|
|
platforms. */
|
|
extern void __clear_cache (char *, char *);
|
|
__clear_cache (start, end);
|
|
#else
|
|
# error "Don't know how to implement clear_cache on this platform."
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|