Merge tag 'pull-tcg-20221220' of https://gitlab.com/rth7680/qemu into staging

Use interval trees for user-only vma mappings.
Assorted cleanups to page locking.

# gpg: Signature made Wed 21 Dec 2022 05:00:30 GMT
# gpg:                using RSA key 7A481E78868B4DB6A85A05C064DF38E8AF7E215F
# gpg:                issuer "richard.henderson@linaro.org"
# gpg: Good signature from "Richard Henderson <richard.henderson@linaro.org>" [full]
# Primary key fingerprint: 7A48 1E78 868B 4DB6 A85A  05C0 64DF 38E8 AF7E 215F

* tag 'pull-tcg-20221220' of https://gitlab.com/rth7680/qemu:
  accel/tcg: Restrict page_collection structure to system TB maintainance
  accel/tcg: Factor tb_invalidate_phys_range_fast() out
  accel/tcg: Rename tb_invalidate_phys_page_fast{,__locked}()
  accel/tcg: Remove trace events from trace-root.h
  accel/tcg: Restrict cpu_io_recompile() to system emulation
  accel/tcg: Move remainder of page locking to tb-maint.c
  accel/tcg: Move PageDesc tree into tb-maint.c for system
  accel/tcg: Use interval tree for user-only page tracking
  accel/tcg: Move page_{get,set}_flags to user-exec.c
  accel/tcg: Drop PAGE_RESERVED for CONFIG_BSD
  accel/tcg: Use interval tree for TARGET_PAGE_DATA_SIZE
  accel/tcg: Use interval tree for TBs in user-only mode
  accel/tcg: Rename page_flush_tb
  util: Add interval-tree.c

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell
2022-12-21 14:15:18 +00:00
15 changed files with 2662 additions and 1162 deletions
+2 -5
View File
@@ -33,7 +33,7 @@
#include "qemu/atomic.h"
#include "qemu/atomic128.h"
#include "exec/translate-all.h"
#include "trace/trace-root.h"
#include "trace.h"
#include "tb-hash.h"
#include "internal.h"
#ifdef CONFIG_PLUGIN
@@ -1508,10 +1508,7 @@ static void notdirty_write(CPUState *cpu, vaddr mem_vaddr, unsigned size,
trace_memory_notdirty_write_access(mem_vaddr, ram_addr, size);
if (!cpu_physical_memory_get_dirty_flag(ram_addr, DIRTY_MEMORY_CODE)) {
struct page_collection *pages
= page_collection_lock(ram_addr, ram_addr + size);
tb_invalidate_phys_page_fast(pages, ram_addr, size, retaddr);
page_collection_unlock(pages);
tb_invalidate_phys_range_fast(ram_addr, size, retaddr);
}
/*
+14 -69
View File
@@ -23,83 +23,28 @@
#define assert_memory_lock() tcg_debug_assert(have_mmap_lock())
#endif
typedef struct PageDesc {
/* list of TBs intersecting this ram page */
uintptr_t first_tb;
#ifdef CONFIG_USER_ONLY
unsigned long flags;
void *target_data;
#endif
#ifdef CONFIG_SOFTMMU
QemuSpin lock;
#endif
} PageDesc;
/* Size of the L2 (and L3, etc) page tables. */
#define V_L2_BITS 10
#define V_L2_SIZE (1 << V_L2_BITS)
/*
* L1 Mapping properties
*/
extern int v_l1_size;
extern int v_l1_shift;
extern int v_l2_levels;
/*
* The bottom level has pointers to PageDesc, and is indexed by
* anything from 4 to (V_L2_BITS + 3) bits, depending on target page size.
*/
#define V_L1_MIN_BITS 4
#define V_L1_MAX_BITS (V_L2_BITS + 3)
#define V_L1_MAX_SIZE (1 << V_L1_MAX_BITS)
extern void *l1_map[V_L1_MAX_SIZE];
PageDesc *page_find_alloc(tb_page_addr_t index, bool alloc);
static inline PageDesc *page_find(tb_page_addr_t index)
{
return page_find_alloc(index, false);
}
/* list iterators for lists of tagged pointers in TranslationBlock */
#define TB_FOR_EACH_TAGGED(head, tb, n, field) \
for (n = (head) & 1, tb = (TranslationBlock *)((head) & ~1); \
tb; tb = (TranslationBlock *)tb->field[n], n = (uintptr_t)tb & 1, \
tb = (TranslationBlock *)((uintptr_t)tb & ~1))
#define PAGE_FOR_EACH_TB(pagedesc, tb, n) \
TB_FOR_EACH_TAGGED((pagedesc)->first_tb, tb, n, page_next)
#define TB_FOR_EACH_JMP(head_tb, tb, n) \
TB_FOR_EACH_TAGGED((head_tb)->jmp_list_head, tb, n, jmp_list_next)
/* In user-mode page locks aren't used; mmap_lock is enough */
#ifdef CONFIG_USER_ONLY
#define assert_page_locked(pd) tcg_debug_assert(have_mmap_lock())
static inline void page_lock(PageDesc *pd) { }
static inline void page_unlock(PageDesc *pd) { }
#else
#ifdef CONFIG_DEBUG_TCG
void do_assert_page_locked(const PageDesc *pd, const char *file, int line);
#define assert_page_locked(pd) do_assert_page_locked(pd, __FILE__, __LINE__)
#else
#define assert_page_locked(pd)
#endif
void page_lock(PageDesc *pd);
void page_unlock(PageDesc *pd);
#endif
#if !defined(CONFIG_USER_ONLY) && defined(CONFIG_DEBUG_TCG)
#if defined(CONFIG_SOFTMMU) && defined(CONFIG_DEBUG_TCG)
void assert_no_pages_locked(void);
#else
static inline void assert_no_pages_locked(void) { }
#endif
#ifdef CONFIG_USER_ONLY
static inline void page_table_config_init(void) { }
#else
void page_table_config_init(void);
#endif
#ifdef CONFIG_SOFTMMU
void tb_invalidate_phys_range_fast(ram_addr_t ram_addr,
unsigned size,
uintptr_t retaddr);
G_NORETURN void cpu_io_recompile(CPUState *cpu, uintptr_t retaddr);
#endif /* CONFIG_SOFTMMU */
TranslationBlock *tb_gen_code(CPUState *cpu, target_ulong pc,
target_ulong cs_base, uint32_t flags,
int cflags);
G_NORETURN void cpu_io_recompile(CPUState *cpu, uintptr_t retaddr);
void page_init(void);
void tb_htable_init(void);
void tb_reset_jump(TranslationBlock *tb, int n);
+755 -239
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -6,5 +6,9 @@ exec_tb(void *tb, uintptr_t pc) "tb:%p pc=0x%"PRIxPTR
exec_tb_nocache(void *tb, uintptr_t pc) "tb:%p pc=0x%"PRIxPTR
exec_tb_exit(void *last_tb, unsigned int flags) "tb:%p flags=0x%x"
# cputlb.c
memory_notdirty_write_access(uint64_t vaddr, uint64_t ram_addr, unsigned size) "0x%" PRIx64 " ram_addr 0x%" PRIx64 " size %u"
memory_notdirty_set_dirty(uint64_t vaddr) "0x%" PRIx64
# translate-all.c
translate_block(void *tb, uintptr_t pc, const void *tb_code) "tb:%p, pc:0x%"PRIxPTR", tb_code:%p"
File diff suppressed because it is too large Load Diff
+633 -25
View File
File diff suppressed because it is too large Load Diff
+40 -3
View File
@@ -24,6 +24,7 @@
#ifdef CONFIG_TCG
#include "exec/cpu_ldst.h"
#endif
#include "qemu/interval-tree.h"
/* allow to see translation results - the slowdown should be negligible, so we leave it */
#define DEBUG_DISAS
@@ -559,11 +560,20 @@ struct TranslationBlock {
struct tb_tc tc;
/* first and second physical page containing code. The lower bit
of the pointer tells the index in page_next[].
The list is protected by the TB's page('s) lock(s) */
/*
* Track tb_page_addr_t intervals that intersect this TB.
* For user-only, the virtual addresses are always contiguous,
* and we use a unified interval tree. For system, we use a
* linked list headed in each PageDesc. Within the list, the lsb
* of the previous pointer tells the index of page_next[], and the
* list is protected by the PageDesc lock(s).
*/
#ifdef CONFIG_USER_ONLY
IntervalTreeNode itree;
#else
uintptr_t page_next[2];
tb_page_addr_t page_addr[2];
#endif
/* jmp_lock placed here to fill a 4-byte hole. Its documentation is below */
QemuSpin jmp_lock;
@@ -619,24 +629,51 @@ static inline uint32_t tb_cflags(const TranslationBlock *tb)
static inline tb_page_addr_t tb_page_addr0(const TranslationBlock *tb)
{
#ifdef CONFIG_USER_ONLY
return tb->itree.start;
#else
return tb->page_addr[0];
#endif
}
static inline tb_page_addr_t tb_page_addr1(const TranslationBlock *tb)
{
#ifdef CONFIG_USER_ONLY
tb_page_addr_t next = tb->itree.last & TARGET_PAGE_MASK;
return next == (tb->itree.start & TARGET_PAGE_MASK) ? -1 : next;
#else
return tb->page_addr[1];
#endif
}
static inline void tb_set_page_addr0(TranslationBlock *tb,
tb_page_addr_t addr)
{
#ifdef CONFIG_USER_ONLY
tb->itree.start = addr;
/*
* To begin, we record an interval of one byte. When the translation
* loop encounters a second page, the interval will be extended to
* include the first byte of the second page, which is sufficient to
* allow tb_page_addr1() above to work properly. The final corrected
* interval will be set by tb_page_add() from tb->size before the
* node is added to the interval tree.
*/
tb->itree.last = addr;
#else
tb->page_addr[0] = addr;
#endif
}
static inline void tb_set_page_addr1(TranslationBlock *tb,
tb_page_addr_t addr)
{
#ifdef CONFIG_USER_ONLY
/* Extend the interval to the first byte of the second page. See above. */
tb->itree.last = addr;
#else
tb->page_addr[1] = addr;
#endif
}
/* current cflags for hashing/comparison */
-6
View File
@@ -23,12 +23,6 @@
/* translate-all.c */
struct page_collection *page_collection_lock(tb_page_addr_t start,
tb_page_addr_t end);
void page_collection_unlock(struct page_collection *set);
void tb_invalidate_phys_page_fast(struct page_collection *pages,
tb_page_addr_t start, int len,
uintptr_t retaddr);
void tb_invalidate_phys_page(tb_page_addr_t addr);
void tb_check_watchpoint(CPUState *cpu, uintptr_t retaddr);
+99
View File
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Interval trees.
*
* Derived from include/linux/interval_tree.h and its dependencies.
*/
#ifndef QEMU_INTERVAL_TREE_H
#define QEMU_INTERVAL_TREE_H
/*
* For now, don't expose Linux Red-Black Trees separately, but retain the
* separate type definitions to keep the implementation sane, and allow
* the possibility of disentangling them later.
*/
typedef struct RBNode
{
/* Encodes parent with color in the lsb. */
uintptr_t rb_parent_color;
struct RBNode *rb_right;
struct RBNode *rb_left;
} RBNode;
typedef struct RBRoot
{
RBNode *rb_node;
} RBRoot;
typedef struct RBRootLeftCached {
RBRoot rb_root;
RBNode *rb_leftmost;
} RBRootLeftCached;
typedef struct IntervalTreeNode
{
RBNode rb;
uint64_t start; /* Start of interval */
uint64_t last; /* Last location _in_ interval */
uint64_t subtree_last;
} IntervalTreeNode;
typedef RBRootLeftCached IntervalTreeRoot;
/**
* interval_tree_is_empty
* @root: root of the tree.
*
* Returns true if the tree contains no nodes.
*/
static inline bool interval_tree_is_empty(const IntervalTreeRoot *root)
{
return root->rb_root.rb_node == NULL;
}
/**
* interval_tree_insert
* @node: node to insert,
* @root: root of the tree.
*
* Insert @node into @root, and rebalance.
*/
void interval_tree_insert(IntervalTreeNode *node, IntervalTreeRoot *root);
/**
* interval_tree_remove
* @node: node to remove,
* @root: root of the tree.
*
* Remove @node from @root, and rebalance.
*/
void interval_tree_remove(IntervalTreeNode *node, IntervalTreeRoot *root);
/**
* interval_tree_iter_first:
* @root: root of the tree,
* @start, @last: the inclusive interval [start, last].
*
* Locate the "first" of a set of nodes within the tree at @root
* that overlap the interval, where "first" is sorted by start.
* Returns NULL if no overlap found.
*/
IntervalTreeNode *interval_tree_iter_first(IntervalTreeRoot *root,
uint64_t start, uint64_t last);
/**
* interval_tree_iter_next:
* @node: previous search result
* @start, @last: the inclusive interval [start, last].
*
* Locate the "next" of a set of nodes within the tree that overlap the
* interval; @next is the result of a previous call to
* interval_tree_iter_{first,next}. Returns NULL if @next was the last
* node in the set.
*/
IntervalTreeNode *interval_tree_iter_next(IntervalTreeNode *node,
uint64_t start, uint64_t last);
#endif /* QEMU_INTERVAL_TREE_H */
+22
View File
@@ -0,0 +1,22 @@
/*
* Test very large vma allocations.
* The qemu out-of-memory condition was within the mmap syscall itself.
* If the syscall actually returns with MAP_FAILED, the test succeeded.
*/
#include <sys/mman.h>
int main()
{
int n = sizeof(size_t) == 4 ? 32 : 45;
for (int i = 28; i < n; i++) {
size_t l = (size_t)1 << i;
void *p = mmap(0, l, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
if (p == MAP_FAILED) {
break;
}
munmap(p, l);
}
return 0;
}
+1
View File
@@ -47,6 +47,7 @@ tests = {
'ptimer-test': ['ptimer-test-stubs.c', meson.project_source_root() / 'hw/core/ptimer.c'],
'test-qapi-util': [],
'test-smp-parse': [qom, meson.project_source_root() / 'hw/core/machine-smp.c'],
'test-interval-tree': [],
}
if have_system or have_tools
+209
View File
@@ -0,0 +1,209 @@
/*
* Test interval trees
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/interval-tree.h"
static IntervalTreeNode nodes[20];
static IntervalTreeRoot root;
static void rand_interval(IntervalTreeNode *n, uint64_t start, uint64_t last)
{
gint32 s_ofs, l_ofs, l_max;
if (last - start > INT32_MAX) {
l_max = INT32_MAX;
} else {
l_max = last - start;
}
s_ofs = g_test_rand_int_range(0, l_max);
l_ofs = g_test_rand_int_range(s_ofs, l_max);
n->start = start + s_ofs;
n->last = start + l_ofs;
}
static void test_empty(void)
{
g_assert(root.rb_root.rb_node == NULL);
g_assert(root.rb_leftmost == NULL);
g_assert(interval_tree_iter_first(&root, 0, UINT64_MAX) == NULL);
}
static void test_find_one_point(void)
{
/* Create a tree of a single node, which is the point [1,1]. */
nodes[0].start = 1;
nodes[0].last = 1;
interval_tree_insert(&nodes[0], &root);
g_assert(interval_tree_iter_first(&root, 0, 9) == &nodes[0]);
g_assert(interval_tree_iter_next(&nodes[0], 0, 9) == NULL);
g_assert(interval_tree_iter_first(&root, 0, 0) == NULL);
g_assert(interval_tree_iter_next(&nodes[0], 0, 0) == NULL);
g_assert(interval_tree_iter_first(&root, 0, 1) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 1, 1) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 1, 2) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 2, 2) == NULL);
interval_tree_remove(&nodes[0], &root);
g_assert(root.rb_root.rb_node == NULL);
g_assert(root.rb_leftmost == NULL);
}
static void test_find_two_point(void)
{
IntervalTreeNode *find0, *find1;
/* Create a tree of a two nodes, which are both the point [1,1]. */
nodes[0].start = 1;
nodes[0].last = 1;
nodes[1] = nodes[0];
interval_tree_insert(&nodes[0], &root);
interval_tree_insert(&nodes[1], &root);
find0 = interval_tree_iter_first(&root, 0, 9);
g_assert(find0 == &nodes[0] || find0 == &nodes[1]);
find1 = interval_tree_iter_next(find0, 0, 9);
g_assert(find1 == &nodes[0] || find1 == &nodes[1]);
g_assert(find0 != find1);
interval_tree_remove(&nodes[1], &root);
g_assert(interval_tree_iter_first(&root, 0, 9) == &nodes[0]);
g_assert(interval_tree_iter_next(&nodes[0], 0, 9) == NULL);
interval_tree_remove(&nodes[0], &root);
}
static void test_find_one_range(void)
{
/* Create a tree of a single node, which is the range [1,8]. */
nodes[0].start = 1;
nodes[0].last = 8;
interval_tree_insert(&nodes[0], &root);
g_assert(interval_tree_iter_first(&root, 0, 9) == &nodes[0]);
g_assert(interval_tree_iter_next(&nodes[0], 0, 9) == NULL);
g_assert(interval_tree_iter_first(&root, 0, 0) == NULL);
g_assert(interval_tree_iter_first(&root, 0, 1) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 1, 1) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 4, 6) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 8, 8) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 9, 9) == NULL);
interval_tree_remove(&nodes[0], &root);
}
static void test_find_one_range_many(void)
{
int i;
/*
* Create a tree of many nodes in [0,99] and [200,299],
* but only one node with exactly [110,190].
*/
nodes[0].start = 110;
nodes[0].last = 190;
for (i = 1; i < ARRAY_SIZE(nodes) / 2; ++i) {
rand_interval(&nodes[i], 0, 99);
}
for (; i < ARRAY_SIZE(nodes); ++i) {
rand_interval(&nodes[i], 200, 299);
}
for (i = 0; i < ARRAY_SIZE(nodes); ++i) {
interval_tree_insert(&nodes[i], &root);
}
/* Test that we find exactly the one node. */
g_assert(interval_tree_iter_first(&root, 100, 199) == &nodes[0]);
g_assert(interval_tree_iter_next(&nodes[0], 100, 199) == NULL);
g_assert(interval_tree_iter_first(&root, 100, 109) == NULL);
g_assert(interval_tree_iter_first(&root, 100, 110) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 111, 120) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 111, 199) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 190, 199) == &nodes[0]);
g_assert(interval_tree_iter_first(&root, 192, 199) == NULL);
/*
* Test that if there are multiple matches, we return the one
* with the minimal start.
*/
g_assert(interval_tree_iter_first(&root, 100, 300) == &nodes[0]);
/* Test that we don't find it after it is removed. */
interval_tree_remove(&nodes[0], &root);
g_assert(interval_tree_iter_first(&root, 100, 199) == NULL);
for (i = 1; i < ARRAY_SIZE(nodes); ++i) {
interval_tree_remove(&nodes[i], &root);
}
}
static void test_find_many_range(void)
{
IntervalTreeNode *find;
int i, n;
n = g_test_rand_int_range(ARRAY_SIZE(nodes) / 3, ARRAY_SIZE(nodes) / 2);
/*
* Create a fair few nodes in [2000,2999], with the others
* distributed around.
*/
for (i = 0; i < n; ++i) {
rand_interval(&nodes[i], 2000, 2999);
}
for (; i < ARRAY_SIZE(nodes) * 2 / 3; ++i) {
rand_interval(&nodes[i], 1000, 1899);
}
for (; i < ARRAY_SIZE(nodes); ++i) {
rand_interval(&nodes[i], 3100, 3999);
}
for (i = 0; i < ARRAY_SIZE(nodes); ++i) {
interval_tree_insert(&nodes[i], &root);
}
/* Test that we find all of the nodes. */
find = interval_tree_iter_first(&root, 2000, 2999);
for (i = 0; find != NULL; i++) {
find = interval_tree_iter_next(find, 2000, 2999);
}
g_assert_cmpint(i, ==, n);
g_assert(interval_tree_iter_first(&root, 0, 999) == NULL);
g_assert(interval_tree_iter_first(&root, 1900, 1999) == NULL);
g_assert(interval_tree_iter_first(&root, 3000, 3099) == NULL);
g_assert(interval_tree_iter_first(&root, 4000, UINT64_MAX) == NULL);
for (i = 0; i < ARRAY_SIZE(nodes); ++i) {
interval_tree_remove(&nodes[i], &root);
}
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/interval-tree/empty", test_empty);
g_test_add_func("/interval-tree/find-one-point", test_find_one_point);
g_test_add_func("/interval-tree/find-two-point", test_find_two_point);
g_test_add_func("/interval-tree/find-one-range", test_find_one_range);
g_test_add_func("/interval-tree/find-one-range-many",
test_find_one_range_many);
g_test_add_func("/interval-tree/find-many-range", test_find_many_range);
return g_test_run();
}
-4
View File
@@ -42,10 +42,6 @@ find_ram_offset(uint64_t size, uint64_t offset) "size: 0x%" PRIx64 " @ 0x%" PRIx
find_ram_offset_loop(uint64_t size, uint64_t candidate, uint64_t offset, uint64_t next, uint64_t mingap) "trying size: 0x%" PRIx64 " @ 0x%" PRIx64 ", offset: 0x%" PRIx64" next: 0x%" PRIx64 " mingap: 0x%" PRIx64
ram_block_discard_range(const char *rbname, void *hva, size_t length, bool need_madvise, bool need_fallocate, int ret) "%s@%p + 0x%zx: madvise: %d fallocate: %d ret: %d"
# accel/tcg/cputlb.c
memory_notdirty_write_access(uint64_t vaddr, uint64_t ram_addr, unsigned size) "0x%" PRIx64 " ram_addr 0x%" PRIx64 " size %u"
memory_notdirty_set_dirty(uint64_t vaddr) "0x%" PRIx64
# job.c
job_state_transition(void *job, int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)"
job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)"
File diff suppressed because it is too large Load Diff
+1
View File
@@ -57,6 +57,7 @@ util_ss.add(files('guest-random.c'))
util_ss.add(files('yank.c'))
util_ss.add(files('int128.c'))
util_ss.add(files('memalign.c'))
util_ss.add(files('interval-tree.c'))
if have_user
util_ss.add(files('selfmap.c'))