8351661: NMT: VMATree should support separate call-stacks for reserve and commit operations

Reviewed-by: gziemski, jsjolen
This commit is contained in:
Afshin Zafari 2025-06-12 14:46:39 +00:00
parent dd68829017
commit fae9c7a3f0
6 changed files with 2033 additions and 269 deletions

View File

@ -216,6 +216,7 @@ class outputStream;
LOG_TAG(valuebasedclasses) \
LOG_TAG(verification) \
LOG_TAG(verify) \
LOG_TAG(vmatree) \
LOG_TAG(vmmutex) \
LOG_TAG(vmoperation) \
LOG_TAG(vmthread) \

View File

@ -91,7 +91,7 @@ void MemoryFileTracker::print_report_on(const MemoryFile* file, outputStream* st
NMTUtil::tag_to_name(prev->val().out.mem_tag()));
{
StreamIndentor si(stream, 4);
_stack_storage.get(prev->val().out.stack()).print_on(stream);
_stack_storage.get(prev->val().out.reserved_stack()).print_on(stream);
}
stream->cr();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -43,11 +43,8 @@
class NativeCallStackStorage : public CHeapObjBase {
public:
using StackIndex = int;
private:
constexpr static const StackIndex invalid = std::numeric_limits<StackIndex>::max() - 1;
public:
static bool equals(const StackIndex a, const StackIndex b) {
return a == b;
}

View File

@ -28,21 +28,228 @@
#include "utilities/globalDefinitions.hpp"
#include "utilities/growableArray.hpp"
const VMATree::RegionData VMATree::empty_regiondata{NativeCallStackStorage::StackIndex{}, mtNone};
// Semantics
// This tree is used to store and track the state of virtual memory regions.
// The nodes in the tree are key-value pairs where the key is the memory address and the value is the State of the memory regions.
// The State of a region describes whether the region is released, reserved or committed, which MemTag it has and where in
// Hotspot (using call-stacks) it is reserved or committed.
// Each node holds the State of the regions to its left and right. Each memory region is described by two
// memory addresses for its start and end.
// For example, to describe the region that starts at memory address 0xA000 with size 0x1000, there will be two nodes
// with the keys 0xA000 (node A) and 0xB000 (node B) in the tree. The value of the key-value pairs of node A and
// node B describe the region's State, using right of A and left of B (<--left--A--right-->.....<--left--B--right-->...).
//
// Virtual memory can be reserved, committed, uncommitted and released. For each operation a request
// (<from-address, to-address, operation, tag, call-stack, which-tag-to-use >) is sent to the tree to handle.
//
// The expected changes are described here for each operation:
//
// ### Reserve a region
// When a region is reserved, all the overlapping regions in the tree should:
// - be marked as Reserved
// - take MemTag of the operation
// - store call-stack of the request to the reserve call-stack
// - clear commit call-stack
//
// ### Commit a region
// When a region is committed, all the overlapping regions in the tree should:
// - be marked as Committed
// - take MemTag of the operation or MemTag of the existing region, depends on which-tag-to-use in the request
// - if the region is in Released state
// - mark the region as both Reserved and Committed
// - store the call-stack of the request to the reserve call-stack
// - store the call-stack of the request to the commit call-stack
//
// ### Uncommit a region
// When a region is uncommitted, all the overlapping regions in the tree should:
// - be ignored if the region is in Released state
// - be marked as Reserved
// - not change the MemTag
// - not change the reserve call-stack
// - clear commit call-stack
//
// ### Release a region
// When a region is released, all the overlapping regions in the tree should:
// - be marked as Released
// - set the MemTag to mtNone
// - clear both reserve and commit call-stack
//
// --- Accounting
// After each operation, the tree should be able to report how much memory is reserved or committed per MemTag.
// So for each region that changes to a new State, the report should contain (separately for each tag) the amount
// of reserve and commit that are changed (increased or decreased) due to the operation.
const VMATree::RegionData VMATree::empty_regiondata{NativeCallStackStorage::invalid, mtNone};
const char* VMATree::statetype_strings[3] = {
"reserved", "committed", "released",
"released", "reserved", "committed"
};
VMATree::SummaryDiff VMATree::register_mapping(position A, position B, StateType state,
VMATree::SIndex VMATree::get_new_reserve_callstack(const SIndex es, const StateType ex, const RequestInfo& req) const {
const SIndex ES = NativeCallStackStorage::invalid; // Empty Stack
const SIndex rq = req.callstack;
const int op = req.op_to_index();
const Operation oper = req.op();
assert(op >= 0 && op < 4, "should be");
assert(op >= 0 && op < 4, "should be");
// existing state
SIndex result[4][3] = {// Rl Rs C
{ES, ES, ES}, // op == Release
{rq, rq, rq}, // op == Reserve
{es, es, es}, // op == Commit
{es, es, es} // op == Uncommit
};
// When committing a Released region, the reserve-call-stack of the region should also be as what is in the request
if (oper == Operation::Commit && ex == StateType::Released) {
return rq;
} else {
return result[op][state_to_index(ex)];
}
}
VMATree::SIndex VMATree::get_new_commit_callstack(const SIndex es, const StateType ex, const RequestInfo& req) const {
const SIndex ES = NativeCallStackStorage::invalid; // Empty Stack
const SIndex rq = req.callstack;
const int op_index = req.op_to_index();
const Operation op = req.op();
assert(op_index >= 0 && op_index < 4, "should be");
// existing state
SIndex result[4][3] = {// Rl Rs C
{ES, ES, ES}, // op == Release
{ES, ES, ES}, // op == Reserve
{rq, rq, rq}, // op == Commit
{ES, ES, ES} // op == Uncommit
};
return result[op_index][state_to_index(ex)];
}
VMATree::StateType VMATree::get_new_state(const StateType ex, const RequestInfo& req) const {
const StateType Rl = StateType::Released;
const StateType Rs = StateType::Reserved;
const StateType C = StateType::Committed;
const int op = req.op_to_index();
assert(op >= 0 && op < 4, "should be");
// existing state
StateType result[4][3] = {// Rl Rs C
{Rl, Rl, Rl}, // op == Release
{Rs, Rs, Rs}, // op == Reserve
{ C, C, C}, // op == Commit
{Rl, Rs, Rs} // op == Uncommit
};
return result[op][state_to_index(ex)];
}
MemTag VMATree::get_new_tag(const MemTag ex, const RequestInfo& req) const {
switch(req.op()) {
case Operation::Release:
return mtNone;
case Operation::Reserve:
return req.tag;
case Operation::Commit:
return req.use_tag_inplace ? ex : req.tag;
case Operation::Uncommit:
return ex;
default:
break;
}
return mtNone;
}
void VMATree::compute_summary_diff(const SingleDiff::delta region_size,
const MemTag current_tag,
const StateType& ex,
const RequestInfo& req,
const MemTag operation_tag,
SummaryDiff& diff) const {
const StateType Rl = StateType::Released;
const StateType Rs = StateType::Reserved;
const StateType C = StateType::Committed;
const int op = req.op_to_index();
const Operation oper = req.op();
assert(op >= 0 && op < 4, "should be");
SingleDiff::delta a = region_size;
// A region with size `a` has a state as <column> and an operation is requested as in <row>
// The region has tag `current_tag` and the operation has tag `operation_tag`.
// For each state, we decide how much to be added/subtracted from current_tag to operation_tag. Two tables for reserve and commit.
// Each pair of <x,y> in the table means add `x` to current_tag and add `y` to operation_tag. There are 3 pairs in each row for 3 states.
// For example, `reserve[1][4,5]` says `-a,a` means:
// - we are reserving with operation_tag a region which is already commited with current_tag
// - since we are reserving, then `a` will be added to operation_tag. (`y` is `a`)
// - since we uncommitting (by reserving) then `a` is to be subtracted from current_tag. (`x` is `-a`).
// - amount of uncommitted size is in table `commit[1][4,5]` which is `-a,0` that means subtract `a` from current_tag.
// existing state
SingleDiff::delta reserve[4][3*2] = {// Rl Rs C
{0,0, -a,0, -a,0 }, // op == Release
{0,a, -a,a, -a,a }, // op == Reserve
{0,a, -a,a, -a,a }, // op == Commit
{0,0, 0,0, 0,0 } // op == Uncommit
};
SingleDiff::delta commit[4][3*2] = {// Rl Rs C
{0,0, 0,0, -a,0 }, // op == Release
{0,0, 0,0, -a,0 }, // op == Reserve
{0,a, 0,a, -a,a }, // op == Commit
{0,0, 0,0, -a,0 } // op == Uncommit
};
SingleDiff& from_rescom = diff.tag[NMTUtil::tag_to_index(current_tag)];
SingleDiff& to_rescom = diff.tag[NMTUtil::tag_to_index(operation_tag)];
int st = state_to_index(ex);
from_rescom.reserve += reserve[op][st * 2 ];
to_rescom.reserve += reserve[op][st * 2 + 1];
from_rescom.commit += commit[op][st * 2 ];
to_rescom.commit += commit[op][st * 2 + 1];
}
// update the region state between n1 and n2. Since n1 and n2 are pointers, any update of them will be visible from tree.
// If n1 is noop, it can be removed because its left region (n1->val().in) is already decided and its right state (n1->val().out) is decided here.
// The state of right of n2 (n2->val().out) cannot be decided here yet.
void VMATree::update_region(TreapNode* n1, TreapNode* n2, const RequestInfo& req, SummaryDiff& diff) {
assert(n1 != nullptr,"sanity");
assert(n2 != nullptr,"sanity");
//.........n1......n2......
// ^------^
// |
IntervalState exSt = n1->val().out; // existing state info
StateType existing_state = exSt.type();
MemTag existing_tag = exSt.mem_tag();
SIndex existing_reserve_callstack = exSt.reserved_stack();
SIndex existing_commit_callstack = exSt.committed_stack();
StateType new_state = get_new_state(existing_state, req);
MemTag new_tag = get_new_tag(n1->val().out.mem_tag(), req);
SIndex new_reserve_callstack = get_new_reserve_callstack(existing_reserve_callstack, existing_state, req);
SIndex new_commit_callstack = get_new_commit_callstack(existing_commit_callstack, existing_state, req);
// n1........n2
// out-->
n1->val().out.set_tag(new_tag);
n1->val().out.set_type(new_state);
n1->val().out.set_reserve_stack(new_reserve_callstack);
n1->val().out.set_commit_stack(new_commit_callstack);
// n1........n2
// <--in
n2->val().in.set_tag(new_tag);
n2->val().in.set_type(new_state);
n2->val().in.set_reserve_stack(new_reserve_callstack);
n2->val().in.set_commit_stack(new_commit_callstack);
SingleDiff::delta region_size = n2->key() - n1->key();
compute_summary_diff(region_size, existing_tag, existing_state, req, new_tag, diff);
}
VMATree::SummaryDiff VMATree::register_mapping(position _A, position _B, StateType state,
const RegionData& metadata, bool use_tag_inplace) {
assert(!use_tag_inplace || metadata.mem_tag == mtNone,
"If using use_tag_inplace, then the supplied tag should be mtNone, was instead: %s", NMTUtil::tag_to_name(metadata.mem_tag));
if (A == B) {
// A 0-sized mapping isn't worth recording.
if (_A == _B) {
return SummaryDiff();
}
assert(_A < _B, "should be");
SummaryDiff diff;
RequestInfo req{_A, _B, state, metadata.mem_tag, metadata.stack_idx, use_tag_inplace};
IntervalChange stA{
IntervalState{StateType::Released, empty_regiondata},
IntervalState{ state, metadata}
@ -51,176 +258,400 @@ VMATree::SummaryDiff VMATree::register_mapping(position A, position B, StateType
IntervalState{ state, metadata},
IntervalState{StateType::Released, empty_regiondata}
};
stA.out.set_commit_stack(NativeCallStackStorage::invalid);
stB.in.set_commit_stack(NativeCallStackStorage::invalid);
VMATreap::Range rA = _tree.find_enclosing_range(_A);
VMATreap::Range rB = _tree.find_enclosing_range(_B);
// First handle A.
// Find closest node that is LEQ A
bool LEQ_A_found = false;
AddressState LEQ_A;
TreapNode* leqA_n = _tree.closest_leq(A);
if (leqA_n == nullptr) {
assert(!use_tag_inplace, "Cannot use the tag inplace if no pre-existing tag exists. From: " PTR_FORMAT " To: " PTR_FORMAT, A, B);
if (use_tag_inplace) {
log_debug(nmt)("Cannot use the tag inplace if no pre-existing tag exists. From: " PTR_FORMAT " To: " PTR_FORMAT, A, B);
// nodes: .....X.......Y...Z......W........U
// request: A------------------B
// X,Y = enclosing_nodes(A)
// W,U = enclosing_nodes(B)
// The cases are whether or not X and Y exists and X == A. (A == Y doesn't happen since it is searched by 'lt' predicate)
// The cases are whether or not W and U exists and W == B. (B == U doesn't happen since it is searched by 'lt' predicate)
// We update regions in 3 sections: 1) X..A..Y, 2) Y....W, 3) W..B..U
// Y: is the closest node greater than A, but less than B
// W: is the closest node less than B, but greater than A
// The regions in [Y,W) are updated in a loop. We update X..A..Y before the loop and W..B..U after the loop.
// The table below summarizes the overlap cases. The overlapping case depends on whether X, Y, W and U exist or not,
// and if they exist whether they are the same or not.
// In the notations here, when there is not dot ('.') between two nodes it meaans that they are the same. For example,
// ...XA....Y.... means X == A.
// row 0: .........A..................B.....
// row 1: .........A...YW.............B..... // it is impossible, since it means only one node exists in the tree.
// row 2: .........A...Y..........W...B.....
// row 3: .........A...Y.............WB.....
// row 4: .....X...A..................B.....
// row 5: .....X...A...YW.............B.....
// row 6: .....X...A...Y..........W...B.....
// row 7: .....X...A...Y.............WB.....
// row 8: ........XA..................B.....
// row 9: ........XA...YW.............B.....
// row 10: ........XA...Y..........W...B.....
// row 11: ........XA...Y.............WB.....
// row 12: .........A..................B....U
// row 13: .........A...YW.............B....U
// row 14: .........A...Y..........W...B....U
// row 15: .........A...Y.............WB....U
// row 16: .....X...A..................B....U
// row 17: .....X...A...YW.............B....U
// row 18: .....X...A...Y..........W...B....U
// row 19: .....X...A...Y.............WB....U
// row 20: ........XA..................B....U
// row 21: ........XA...YW.............B....U
// row 22: ........XA...Y..........W...B....U
// row 23: ........XA...Y.............WB....U
// We intentionally did not summarize/compress the cases to keep them as separate.
// This expanded way of describing the cases helps us to understand/analyze/verify/debug/maintain
// the corresponding code more easily.
// Mapping of table to row, row to switch-case should be consistent. If one changes, the others have
// to be updated accordingly. The sequence of dependecies is: table -> row no -> switch(row)-case -> code.
// Meaning that whenever any of one item in this sequence is changed, the rest of the consequent items to
// be checked/changed.
TreapNode* X = rA.start;
TreapNode* Y = rA.end;
TreapNode* W = rB.start;
TreapNode* U = rB.end;
TreapNode nA{_A, stA, 0}; // the node that represents A
TreapNode nB{_B, stB, 0}; // the node that represents B
TreapNode* A = &nA;
TreapNode* B = &nB;
auto upsert_if= [&](TreapNode* node) {
if (!node->val().is_noop()) {
_tree.upsert(node->key(), node->val());
}
// No match. We add the A node directly, unless it would have no effect.
if (!stA.is_noop()) {
_tree.upsert(A, stA);
};
// update region between n1 and n2
auto update = [&](TreapNode* n1, TreapNode* n2) {
update_region(n1, n2, req, diff);
};
auto remove_if = [&](TreapNode* node) -> bool{
if (node->val().is_noop()) {
_tree.remove(node->key());
return true;
}
} else {
LEQ_A_found = true;
LEQ_A = AddressState{leqA_n->key(), leqA_n->val()};
StateType leqA_state = leqA_n->val().out.type();
StateType new_state = stA.out.type();
// If we specify use_tag_inplace then the new region takes over the current tag instead of the tag in metadata.
// This is important because the VirtualMemoryTracker API doesn't require supplying the tag for some operations.
if (use_tag_inplace) {
assert(leqA_n->val().out.type() != StateType::Released, "Should not use inplace the tag of a released region");
MemTag tag = leqA_n->val().out.mem_tag();
stA.out.set_tag(tag);
stB.in.set_tag(tag);
}
// Unless we know better, let B's outgoing state be the outgoing state of the node at or preceding A.
// Consider the case where the found node is the start of a region enclosing [A,B)
stB.out = out_state(leqA_n);
// Direct address match.
if (leqA_n->key() == A) {
// Take over in state from old address.
stA.in = in_state(leqA_n);
// We may now be able to merge two regions:
// If the node's old state matches the new, it becomes a noop. That happens, for example,
// when expanding a committed area: commit [x1, A); ... commit [A, x3)
// and the result should be a larger area, [x1, x3). In that case, the middle node (A and le_n)
// is not needed anymore. So we just remove the old node.
stB.in = stA.out;
if (stA.is_noop()) {
// invalidates leqA_n
_tree.remove(leqA_n->key());
} else {
// If the state is not matching then we have different operations, such as:
// reserve [x1, A); ... commit [A, x2); or
// reserve [x1, A), mem_tag1; ... reserve [A, x2), mem_tag2; or
// reserve [A, x1), mem_tag1; ... reserve [A, x2), mem_tag2;
// then we re-use the existing out node, overwriting its old metadata.
leqA_n->val() = stA;
return false;
};
GrowableArrayCHeap<position, mtNMT> to_be_removed;
// update regions in [Y,W)
auto update_loop = [&]() {
TreapNode* prev = nullptr;
_tree.visit_range_in_order(_A + 1, _B + 1, [&](TreapNode* curr) {
if (prev != nullptr) {
update_region(prev, curr, req, diff);
// during visit, structure of the tree should not be changed
// keep the keys to be removed, and remove them later
if (prev->val().is_noop()) {
to_be_removed.push(prev->key());
}
}
} else {
// The address must be smaller.
assert(A > leqA_n->key(), "must be");
prev = curr;
});
};
// update region of [A,T)
auto update_A = [&](TreapNode* T) {
A->val().out = A->val().in;
update(A, T);
};
bool X_exists = X != nullptr;
bool Y_exists = Y != nullptr && Y->key() <= _B;
bool W_exists = W != nullptr && W->key() > _A;
bool U_exists = U != nullptr;
bool X_eq_A = X_exists && X->key() == _A;
bool W_eq_B = W_exists && W->key() == _B;
bool Y_eq_W = Y_exists && W_exists && W->key() == Y->key();
int row = -1;
#ifdef ASSERT
auto print_case = [&]() {
log_trace(vmatree)(" req: %4d---%4d", (int)_A, (int)_B);
log_trace(vmatree)(" row: %2d", row);
log_trace(vmatree)(" X: %4ld", X_exists ? (long)X->key() : -1);
log_trace(vmatree)(" Y: %4ld", Y_exists ? (long)Y->key() : -1);
log_trace(vmatree)(" W: %4ld", W_exists ? (long)W->key() : -1);
log_trace(vmatree)(" U: %4ld", U_exists ? (long)U->key() : -1);
};
#endif
// Order of the nodes if they exist are as: X <= A < Y <= W <= B < U
// A---------------------------B
// X Y YW WB U
// XA Y YW WB U
if (!X_exists && !Y_exists && !U_exists) { row = 0; }
if (!X_exists && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 1; }
if (!X_exists && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 2; }
if (!X_exists && Y_exists && W_eq_B && !U_exists) { row = 3; }
// We add a new node, but only if there would be a state change. If there would not be a
// state change, we just omit the node.
// That happens, for example, when reserving within an already reserved region with identical metadata.
stA.in = out_state(leqA_n); // .. and the region's prior state is the incoming state
if (stA.is_noop()) {
// Nothing to do.
} else {
// Add new node.
_tree.upsert(A, stA);
}
if ( X_exists && !Y_exists && !U_exists) { row = 4; }
if ( X_exists && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 5; }
if ( X_exists && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 6; }
if ( X_exists && Y_exists && W_eq_B && !U_exists) { row = 7; }
if ( X_eq_A && !Y_exists && !U_exists) { row = 8; }
if ( X_eq_A && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 9; }
if ( X_eq_A && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 10; }
if ( X_eq_A && Y_exists && W_eq_B && !U_exists) { row = 11; }
if (!X_exists && !Y_exists && U_exists) { row = 12; }
if (!X_exists && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 13; }
if (!X_exists && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 14; }
if (!X_exists && Y_exists && W_eq_B && U_exists) { row = 15; }
if ( X_exists && !Y_exists && U_exists) { row = 16; }
if ( X_exists && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 17; }
if ( X_exists && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 18; }
if ( X_exists && Y_exists && W_eq_B && U_exists) { row = 19; }
if ( X_eq_A && !Y_exists && U_exists) { row = 20; }
if ( X_eq_A && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 21; }
if ( X_eq_A && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 22; }
if ( X_eq_A && Y_exists && W_eq_B && U_exists) { row = 23; }
DEBUG_ONLY(print_case();)
switch(row) {
// row 0: .........A..................B.....
case 0: {
update_A(B);
upsert_if(A);
upsert_if(B);
break;
}
}
// Now we handle B.
// We first search all nodes that are (A, B]. All of these nodes
// need to be deleted and summary accounted for. The last node before B determines B's outgoing state.
// If there is no node between A and B, its A's incoming state.
GrowableArrayCHeap<AddressState, mtNMT> to_be_deleted_inbetween_a_b;
bool B_needs_insert = true;
// Find all nodes between (A, B] and record their addresses and values. Also update B's
// outgoing state.
_tree.visit_range_in_order(A + 1, B + 1, [&](TreapNode* head) {
int cmp_B = PositionComparator::cmp(head->key(), B);
stB.out = out_state(head);
if (cmp_B < 0) {
// Record all nodes preceding B.
to_be_deleted_inbetween_a_b.push({head->key(), head->val()});
} else if (cmp_B == 0) {
// Re-purpose B node, unless it would result in a noop node, in
// which case record old node at B for deletion and summary accounting.
if (stB.is_noop()) {
to_be_deleted_inbetween_a_b.push(AddressState{B, head->val()});
} else {
head->val() = stB;
}
B_needs_insert = false;
// row 1: .........A...YW.............B.....
case 1: {
ShouldNotReachHere();
break;
}
});
// Insert B node if needed
if (B_needs_insert && // Was not already inserted
!stB.is_noop()) // The operation is differing
{
_tree.upsert(B, stB);
}
// We now need to:
// a) Delete all nodes between (A, B]. Including B in the case of a noop.
// b) Perform summary accounting
SummaryDiff diff;
if (to_be_deleted_inbetween_a_b.length() == 0 && LEQ_A_found) {
// We must have smashed a hole in an existing region (or replaced it entirely).
// LEQ_A < A < B <= C
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(LEQ_A.out().mem_tag())];
if (LEQ_A.out().type() == StateType::Reserved) {
rescom.reserve -= B - A;
} else if (LEQ_A.out().type() == StateType::Committed) {
rescom.commit -= B - A;
rescom.reserve -= B - A;
// row 2: .........A...Y..........W...B.....
case 2: {
update_A(Y);
upsert_if(A);
update_loop();
remove_if(Y);
update(W, B);
remove_if(W);
upsert_if(B);
break;
}
}
// Track the previous node.
AddressState prev{A, stA};
for (int i = 0; i < to_be_deleted_inbetween_a_b.length(); i++) {
const AddressState delete_me = to_be_deleted_inbetween_a_b.at(i);
_tree.remove(delete_me.address);
// Perform summary accounting
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(delete_me.in().mem_tag())];
if (delete_me.in().type() == StateType::Reserved) {
rescom.reserve -= delete_me.address - prev.address;
} else if (delete_me.in().type() == StateType::Committed) {
rescom.commit -= delete_me.address - prev.address;
rescom.reserve -= delete_me.address - prev.address;
// row 3: .........A...Y.............WB.....
case 3: {
update_A(Y);
upsert_if(A);
update_loop();
remove_if(W);
break;
}
prev = delete_me;
}
if (prev.address != A && prev.out().type() != StateType::Released) {
// The last node wasn't released, so it must be connected to a node outside of (A, B)
// A - prev - B - (some node >= B)
// It might be that prev.address == B == (some node >= B), this is fine.
if (prev.out().type() == StateType::Reserved) {
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(prev.out().mem_tag())];
rescom.reserve -= B - prev.address;
} else if (prev.out().type() == StateType::Committed) {
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(prev.out().mem_tag())];
rescom.commit -= B - prev.address;
rescom.reserve -= B - prev.address;
// row 4: .....X...A..................B.....
case 4: {
A->val().in = X->val().out;
update_A(B);
upsert_if(A);
upsert_if(B);
break;
}
// row 5: .....X...A...YW.............B.....
case 5: {
A->val().in = X->val().out;
update_A(Y);
upsert_if(A);
update(Y, B);
remove_if(Y);
upsert_if(B);
break;
}
// row 6: .....X...A...Y..........W...B.....
case 6: {
A->val().in = X->val().out;
update_A(Y);
upsert_if(A);
update_loop();
update(W, B);
remove_if(W);
upsert_if(B);
break;
}
// row 7: .....X...A...Y.............WB.....
case 7: {
A->val().in = X->val().out;
update_A(Y);
upsert_if(A);
update_loop();
remove_if(W);
break;
}
// row 8: ........XA..................B.....
case 8: {
update(X, B);
remove_if(X);
upsert_if(B);
break;
}
// row 9: ........XA...YW.............B.....
case 9: {
update(X, Y);
remove_if(X);
update(W, B);
remove_if(W);
upsert_if(B);
break;
}
// row 10: ........XA...Y..........W...B.....
case 10: {
update(X, Y);
remove_if(X);
update_loop();
update(W, B);
remove_if(W);
upsert_if(B);
break;
}
// row 11: ........XA...Y.............WB.....
case 11: {
update(X, Y);
remove_if(X);
update_loop();
remove_if(W);
break;
}
// row 12: .........A..................B....U
case 12: {
update_A(B);
upsert_if(A);
upsert_if(B);
break;
}
// row 13: .........A...YW.............B....U
case 13: {
update_A(Y);
upsert_if(A);
update(W, B);
remove_if(W);
B->val().out = U->val().in;
upsert_if(B);
break;
}
// row 14: .........A...Y..........W...B....U
case 14: {
update_A(Y);
upsert_if(A);
update_loop();
update(W, B);
remove_if(W);
B->val().out = U->val().in;
upsert_if(B);
break;
}
// row 15: .........A...Y.............WB....U
case 15: {
update_A(Y);
upsert_if(A);
update_loop();
remove_if(W);
break;
}
// row 16: .....X...A..................B....U
case 16: {
A->val().in = X->val().out;
update_A(B);
upsert_if(A);
B->val().out = U->val().in;
upsert_if(B);
break;
}
// row 17: .....X...A...YW.............B....U
case 17: {
A->val().in = X->val().out;
update_A(Y);
upsert_if(A);
update(W, B);
remove_if(W);
B->val().out = U->val().in;
upsert_if(B);
break;
}
// row 18: .....X...A...Y..........W...B....U
case 18: {
A->val().in = X->val().out;
update_A(Y);
upsert_if(A);
update_loop();
update(W, B);
remove_if(W);
B->val().out = U->val().in;
upsert_if(B);
break;
}
// row 19: .....X...A...Y.............WB....U
case 19: {
A->val().in = X->val().out;
update_A(Y);
upsert_if(A);
update_loop();
remove_if(W);
break;
}
// row 20: ........XA..................B....U
case 20: {
update(X, B);
remove_if(X);
B->val().out = U->val().in;
upsert_if(B);
break;
}
// row 21: ........XA...YW.............B....U
case 21: {
update(X, Y);
remove_if(X);
update(W, B);
remove_if(W);
B->val().out = U->val().in;
upsert_if(B);
break;
}
// row 22: ........XA...Y..........W...B....U
case 22: {
update(X, Y);
remove_if(X);
update_loop();
update(W, B);
remove_if(W);
B->val().out = U->val().in;
upsert_if(B);
break;
}
// row 23: ........XA...Y.............WB....U
case 23: {
update(X, Y);
remove_if(X);
update_loop();
remove_if(W);
break;
}
default:
ShouldNotReachHere();
}
// Finally, we can register the new region [A, B)'s summary data.
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(stA.out.mem_tag())];
if (state == StateType::Reserved) {
rescom.reserve += B - A;
} else if (state == StateType::Committed) {
rescom.commit += B - A;
rescom.reserve += B - A;
// Remove the 'noop' nodes that found inside the loop
while(to_be_removed.length() != 0) {
_tree.remove(to_be_removed.pop());
}
return diff;
}
#ifdef ASSERT
void VMATree::print_on(outputStream* out) {
visit_in_order([&](TreapNode* current) {
out->print("%zu (%s) - %s - ", current->key(), NMTUtil::tag_to_name(out_state(current).mem_tag()),
statetype_to_string(out_state(current).type()));
out->print("%zu (%s) - %s [%d, %d]-> ", current->key(), NMTUtil::tag_to_name(out_state(current).mem_tag()),
statetype_to_string(out_state(current).type()), current->val().out.reserved_stack(), current->val().out.committed_stack());
});
out->cr();
}
@ -268,7 +699,7 @@ VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, con
SummaryDiff diff;
// Ignore any released ranges, these must be mtNone and have no stack
if (type != StateType::Released) {
RegionData new_data = RegionData(out.stack(), tag);
RegionData new_data = RegionData(out.reserved_stack(), tag);
SummaryDiff result = register_mapping(from, end, type, new_data);
diff.add(result);
}
@ -289,7 +720,7 @@ VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, con
StateType type = out.type();
if (type != StateType::Released) {
RegionData new_data = RegionData(out.stack(), tag);
RegionData new_data = RegionData(out.reserved_stack(), tag);
SummaryDiff result = register_mapping(from, end, type, new_data);
diff.add(result);
}

View File

@ -44,6 +44,7 @@ class VMATree {
public:
using position = size_t;
using size = size_t;
using SIndex = NativeCallStackStorage::StackIndex;
class PositionComparator {
public:
@ -55,7 +56,7 @@ public:
}
};
enum class StateType : uint8_t { Reserved, Committed, Released, LAST };
enum class StateType : uint8_t { Released, Reserved, Committed, LAST };
private:
static const char* statetype_strings[static_cast<uint8_t>(StateType::LAST)];
@ -70,12 +71,12 @@ public:
// Each point has some stack and a tag associated with it.
struct RegionData {
const NativeCallStackStorage::StackIndex stack_idx;
const SIndex stack_idx;
const MemTag mem_tag;
RegionData() : stack_idx(), mem_tag(mtNone) {}
RegionData(NativeCallStackStorage::StackIndex stack_idx, MemTag mem_tag)
RegionData(SIndex stack_idx, MemTag mem_tag)
: stack_idx(stack_idx), mem_tag(mem_tag) {}
static bool equals(const RegionData& a, const RegionData& b) {
@ -91,15 +92,27 @@ private:
private:
// Store the type and mem_tag as two bytes
uint8_t type_tag[2];
NativeCallStackStorage::StackIndex sidx;
NativeCallStackStorage::StackIndex _reserved_stack;
NativeCallStackStorage::StackIndex _committed_stack;
public:
IntervalState() : type_tag{0,0}, sidx() {}
IntervalState() : type_tag{0,0}, _reserved_stack(NativeCallStackStorage::invalid), _committed_stack(NativeCallStackStorage::invalid) {}
IntervalState(const StateType type,
const MemTag mt,
const NativeCallStackStorage::StackIndex res_stack,
const NativeCallStackStorage::StackIndex com_stack) {
assert(!(type == StateType::Released) || mt == mtNone, "Released state-type must have memory tag mtNone");
type_tag[0] = static_cast<uint8_t>(type);
type_tag[1] = static_cast<uint8_t>(mt);
_reserved_stack = res_stack;
_committed_stack = com_stack;
}
IntervalState(const StateType type, const RegionData data) {
assert(!(type == StateType::Released) || data.mem_tag == mtNone, "Released state-type must have memory tag mtNone");
type_tag[0] = static_cast<uint8_t>(type);
type_tag[1] = static_cast<uint8_t>(data.mem_tag);
sidx = data.stack_idx;
_reserved_stack = data.stack_idx;
_committed_stack = NativeCallStackStorage::invalid;
}
StateType type() const {
@ -110,16 +123,50 @@ private:
return static_cast<MemTag>(type_tag[1]);
}
RegionData regiondata() const {
return RegionData{sidx, mem_tag()};
RegionData reserved_regiondata() const {
return RegionData{_reserved_stack, mem_tag()};
}
RegionData committed_regiondata() const {
return RegionData{_committed_stack, mem_tag()};
}
void set_tag(MemTag tag) {
type_tag[1] = static_cast<uint8_t>(tag);
}
NativeCallStackStorage::StackIndex stack() const {
return sidx;
NativeCallStackStorage::StackIndex reserved_stack() const {
return _reserved_stack;
}
NativeCallStackStorage::StackIndex committed_stack() const {
return _committed_stack;
}
void set_reserve_stack(NativeCallStackStorage::StackIndex idx) {
_reserved_stack = idx;
}
void set_commit_stack(NativeCallStackStorage::StackIndex idx) {
_committed_stack = idx;
}
bool has_reserved_stack() {
return _reserved_stack != NativeCallStackStorage::invalid;
}
bool has_committed_stack() {
return _committed_stack != NativeCallStackStorage::invalid;
}
void set_type(StateType t) {
type_tag[0] = static_cast<uint8_t>(t);
}
bool equals(const IntervalState& other) const {
return mem_tag() == other.mem_tag() &&
type() == other.type() &&
reserved_stack() == other.reserved_stack() &&
committed_stack() == other.committed_stack();
}
};
@ -130,8 +177,14 @@ private:
IntervalState out;
bool is_noop() {
if (in.type() == StateType::Released &&
in.type() == out.type() &&
in.mem_tag() == out.mem_tag()) {
return true;
}
return in.type() == out.type() &&
RegionData::equals(in.regiondata(), out.regiondata());
RegionData::equals(in.reserved_regiondata(), out.reserved_regiondata()) &&
RegionData::equals(in.committed_regiondata(), out.committed_regiondata());
}
};
@ -193,8 +246,44 @@ public:
#endif
};
enum Operation {Release, Reserve, Commit, Uncommit};
struct RequestInfo {
position A, B;
StateType _op;
MemTag tag;
SIndex callstack;
bool use_tag_inplace;
Operation op() const {
return
_op == StateType::Reserved && !use_tag_inplace ? Operation::Reserve :
_op == StateType::Committed ? Operation::Commit :
_op == StateType::Reserved && use_tag_inplace ? Operation::Uncommit :
Operation::Release;
}
int op_to_index() const {
return
_op == StateType::Reserved && !use_tag_inplace ? 1 :
_op == StateType::Committed ? 2 :
_op == StateType::Reserved && use_tag_inplace ? 3 :
0;
}
};
private:
SummaryDiff register_mapping(position A, position B, StateType state, const RegionData& metadata, bool use_tag_inplace = false);
StateType get_new_state(const StateType existinting_state, const RequestInfo& req) const;
MemTag get_new_tag(const MemTag existinting_tag, const RequestInfo& req) const;
SIndex get_new_reserve_callstack(const SIndex existinting_stack, const StateType ex, const RequestInfo& req) const;
SIndex get_new_commit_callstack(const SIndex existinting_stack, const StateType ex, const RequestInfo& req) const;
void compute_summary_diff(const SingleDiff::delta region_size, const MemTag t1, const StateType& ex, const RequestInfo& req, const MemTag new_tag, SummaryDiff& diff) const;
void update_region(TreapNode* n1, TreapNode* n2, const RequestInfo& req, SummaryDiff& diff);
int state_to_index(const StateType st) const {
return
st == StateType::Released ? 0 :
st == StateType::Reserved ? 1 :
st == StateType::Committed ? 2 : -1;
}
public:
SummaryDiff reserve_mapping(position from, size size, const RegionData& metadata) {

File diff suppressed because it is too large Load Diff