Changeset 283178 in webkit


Ignore:
Timestamp:
Sep 28, 2021 9:59:52 AM (3 years ago)
Author:
fpizlo@apple.com
Message:

[libpas] Fix coalescing of the large sharing pool and make it easy to introspect it (update to e4d20851ee9ff00f2962b349a9ff8465695a83d7)
https://bugs.webkit.org/show_bug.cgi?id=230867

Reviewed by Yusuke Suzuki.

This adds the ability to enable the libpas status reporter, adds a large sharing pool dump to
the status report, and fixes a large sharing pool coalescing bug found by doing that. Previously
we weren't coalescing things that are not free+committed.

Also updates the export script that I use to keep the libpas git repo in sync with what's in WK.

The large sharing pool is the mechanism by which libpas can find memory that can be decommitted
across isolated large heaps, even if those large heaps share pages with one another. The main
data structure is a red-black tree of nodes that represent memory ranges. If there are two
adjacent ranges of memory that are both fully live and committed or both decommitted, then we
want those to be represented using a single node. That wasn't quite working right. Even the
libpas test for this was testing the wrong thing. This fixes the behavior and the test. It's
perf-neutral since large heaps usually have a small number of objects in them anyway.

The new status reporting functionality can be enabled with the WebKitPasStatusReporter
environment variable. This takes an integer that tells the amount of data in the report. Here
are the recognized values:

1 - just report number of heaps
2 - something in between 1 and 3
3 - report everything that the status reporter can report right now (per-page data for

segregated/bitfit heaps, lots of details for large heaps)

If the status reporter ever reported per-object information, it would be at level 4 or higher.
It's safe to pass 9999 or whatever if you just want the maximum report that libpas supports.
TL;DR for now you usually want WebKitPasStatusReporter=3.

  • bmalloc/Environment.cpp:

(bmalloc::Environment::Environment):

  • libpas/export.rb: Added.
  • libpas/export.sh: Removed.
  • libpas/src/libpas/pas_bitfit_directory.c:

(pas_bitfit_directory_construct): I needed to rationalize how we initialize disabled directories to make status reporting work.
(pas_bitfit_directory_get_first_free_view):

  • libpas/src/libpas/pas_large_sharing_pool.c:

(states_match):

  • libpas/src/libpas/pas_status_reporter.c:

(pas_status_reporter_dump_bitfit_directory):
(dump_large_sharing_pool_node_callback):
(pas_status_reporter_dump_large_sharing_pool):
(pas_status_reporter_dump_everything):

  • libpas/src/libpas/pas_status_reporter.h:
  • libpas/src/test/LargeSharingPoolDump.cpp:
  • libpas/src/test/LargeSharingPoolDump.h:
  • libpas/src/test/LargeSharingPoolTests.cpp:

(std::Range::Range):
(std::Range::operator== const):
(std::Range::operator!= const):
(std::operator<<):
(std::assertState):
(std::testGoodCoalesceEpochUpdate):
(addLargeSharingPoolTests):
(std::testBadCoalesceEpochUpdate): Deleted.

Location:
trunk/Source/bmalloc
Files:
1 added
1 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/bmalloc/ChangeLog

    r282899 r283178  
     12021-09-27  Filip Pizlo  <fpizlo@apple.com>
     2
     3        [libpas] Fix coalescing of the large sharing pool and make it easy to introspect it (update to e4d20851ee9ff00f2962b349a9ff8465695a83d7)
     4        https://bugs.webkit.org/show_bug.cgi?id=230867
     5
     6        Reviewed by Yusuke Suzuki.
     7
     8        This adds the ability to enable the libpas status reporter, adds a large sharing pool dump to
     9        the status report, and fixes a large sharing pool coalescing bug found by doing that. Previously
     10        we weren't coalescing things that are not free+committed.
     11
     12        Also updates the export script that I use to keep the libpas git repo in sync with what's in WK.
     13
     14        The large sharing pool is the mechanism by which libpas can find memory that can be decommitted
     15        across isolated large heaps, even if those large heaps share pages with one another. The main
     16        data structure is a red-black tree of nodes that represent memory ranges. If there are two
     17        adjacent ranges of memory that are both fully live and committed or both decommitted, then we
     18        want those to be represented using a single node. That wasn't quite working right. Even the
     19        libpas test for this was testing the wrong thing. This fixes the behavior and the test. It's
     20        perf-neutral since large heaps usually have a small number of objects in them anyway.
     21
     22        The new status reporting functionality can be enabled with the WebKitPasStatusReporter
     23        environment variable. This takes an integer that tells the amount of data in the report. Here
     24        are the recognized values:
     25
     26        1 - just report number of heaps
     27        2 - something in between 1 and 3
     28        3 - report everything that the status reporter can report right now (per-page data for
     29            segregated/bitfit heaps, lots of details for large heaps)
     30
     31        If the status reporter ever reported per-object information, it would be at level 4 or higher.
     32        It's safe to pass 9999 or whatever if you just want the maximum report that libpas supports.
     33        TL;DR for now you usually want WebKitPasStatusReporter=3.
     34
     35        * bmalloc/Environment.cpp:
     36        (bmalloc::Environment::Environment):
     37        * libpas/export.rb: Added.
     38        * libpas/export.sh: Removed.
     39        * libpas/src/libpas/pas_bitfit_directory.c:
     40        (pas_bitfit_directory_construct): I needed to rationalize how we initialize disabled directories to make status reporting work.
     41        (pas_bitfit_directory_get_first_free_view):
     42        * libpas/src/libpas/pas_large_sharing_pool.c:
     43        (states_match):
     44        * libpas/src/libpas/pas_status_reporter.c:
     45        (pas_status_reporter_dump_bitfit_directory):
     46        (dump_large_sharing_pool_node_callback):
     47        (pas_status_reporter_dump_large_sharing_pool):
     48        (pas_status_reporter_dump_everything):
     49        * libpas/src/libpas/pas_status_reporter.h:
     50        * libpas/src/test/LargeSharingPoolDump.cpp:
     51        * libpas/src/test/LargeSharingPoolDump.h:
     52        * libpas/src/test/LargeSharingPoolTests.cpp:
     53        (std::Range::Range):
     54        (std::Range::operator== const):
     55        (std::Range::operator!= const):
     56        (std::operator<<):
     57        (std::assertState):
     58        (std::testGoodCoalesceEpochUpdate):
     59        (addLargeSharingPoolTests):
     60        (std::testBadCoalesceEpochUpdate): Deleted.
     61
    1622021-09-22  Filip Pizlo  <fpizlo@apple.com>
    263
  • trunk/Source/bmalloc/bmalloc/Environment.cpp

    r261207 r283178  
    4848int malloc_engaged_nano(void);
    4949}
     50#endif
     51
     52#if BUSE(LIBPAS)
     53#include "pas_status_reporter.h"
    5054#endif
    5155
     
    137141    : m_isDebugHeapEnabled(computeIsDebugHeapEnabled())
    138142{
     143#if BUSE(LIBPAS)
     144    const char* statusReporter = getenv("WebKitPasStatusReporter");
     145    if (statusReporter) {
     146        unsigned enabled;
     147        if (sscanf(statusReporter, "%u", &enabled) == 1)
     148            pas_status_reporter_enabled = enabled;
     149    }
     150#endif
    139151}
    140152
  • trunk/Source/bmalloc/libpas/src/libpas/pas_bitfit_directory.c

    r282556 r283178  
    4747    static const bool verbose = false;
    4848
     49    /* NOTE - this works even if the config is disabled, and produces a directory that is empty and
     50       does nothing. This makes sense because it makes it easy to iterate over the directories in a heap
     51       without knowing what your config is. */
     52
    4953    if (verbose)
    5054        pas_log("Creating directory %p\n", directory);
     
    5559    pas_bitfit_directory_view_vector_construct(&directory->views);
    5660    pas_compact_atomic_bitfit_size_class_ptr_store(&directory->largest_size_class, NULL);
    57     directory->config_kind = config->kind;
     61    directory->config_kind = config->base.is_enabled ? config->kind : pas_bitfit_page_config_kind_null;
    5862
    5963    directory->heap = heap;
     
    6367    /* We could have been lazy about this - but it's probably not super necessary since the point
    6468       of bitfit global directories is that there won't be too many of them. */
    65     if (pas_bitfit_directory_does_sharing(directory)) {
     69    if (config->base.is_enabled && pas_bitfit_directory_does_sharing(directory)) {
    6670        pas_page_sharing_pool_add(
    6771            &pas_physical_page_sharing_pool,
     
    103107{
    104108    static const bool verbose = false;
     109
     110    PAS_ASSERT(page_config->base.is_enabled);
    105111   
    106112    for (;;) {
  • trunk/Source/bmalloc/libpas/src/libpas/pas_large_sharing_pool.c

    r279867 r283178  
    288288                         pas_large_sharing_node* right)
    289289{
    290     return left->is_committed == right->is_committed
    291         && left->synchronization_style == right->synchronization_style
    292         && ((!left->num_live_bytes && !right->num_live_bytes) ||
    293             (pas_range_size(left->range) == left->num_live_bytes &&
    294              pas_range_size(right->range) == right->num_live_bytes))
    295         && (left->use_epoch == right->use_epoch
    296             || (!left->is_committed && !left->num_live_bytes));
     290    bool both_empty;
     291    bool both_full;
     292   
     293    if (left->is_committed != right->is_committed)
     294        return false;
     295
     296    if (left->synchronization_style != right->synchronization_style)
     297        return false;
     298
     299    both_empty =
     300        !left->num_live_bytes &&
     301        !right->num_live_bytes;
     302   
     303    both_full =
     304        pas_range_size(left->range) == left->num_live_bytes &&
     305        pas_range_size(right->range) == right->num_live_bytes;
     306
     307    if (!both_empty && !both_full)
     308        return false;
     309
     310    /* Right now: both sides have identical commit states and identical synchronization styes. And
     311       either both sides are empty or both sides are full.
     312   
     313       The only reason why we wouldn't want to coalesce is if epochs didn't match. But that only
     314       matters when the memory is free and committed. */
     315
     316    if (!left->is_committed)
     317        return true;
     318
     319    if (both_full)
     320        return true;
     321
     322    return left->use_epoch == right->use_epoch;
    297323}
    298324
  • trunk/Source/bmalloc/libpas/src/libpas/pas_status_reporter.c

    r282556 r283178  
    4646#include "pas_large_heap.h"
    4747#include "pas_large_map.h"
     48#include "pas_large_sharing_pool.h"
    4849#include "pas_large_utility_free_heap.h"
    4950#include "pas_log.h"
     
    197198    pas_stream* stream, pas_bitfit_directory* directory)
    198199{
     200    if (directory->config_kind == pas_bitfit_page_config_kind_null)
     201        return;
     202   
    199203    pas_stream_printf(stream, "            %s Global Dir (%p): ",
    200204                      pas_bitfit_page_config_variant_get_capitalized_string(
     
    643647    pas_heap_summary_dump(pas_all_heaps_compute_total_non_utility_large_summary(), stream);
    644648    pas_stream_printf(stream, "\n");
     649}
     650
     651static bool dump_large_sharing_pool_node_callback(pas_large_sharing_node* node,
     652                                                  void* arg)
     653{
     654    pas_stream* stream;
     655
     656    stream = arg;
     657
     658    pas_stream_printf(stream, "        %p...%p: %s, %zu/%zu live (%.0lf%%), %llu",
     659                      (void*)node->range.begin,
     660                      (void*)node->range.end,
     661                      pas_commit_mode_get_string(node->is_committed),
     662                      node->num_live_bytes,
     663                      pas_range_size(node->range),
     664                      100. * (double)node->num_live_bytes / (double)pas_range_size(node->range),
     665                      node->use_epoch);
     666
     667    if (node->synchronization_style != pas_physical_memory_is_locked_by_virtual_range_common_lock) {
     668        pas_stream_printf(
     669            stream, ", %s",
     670            pas_physical_memory_synchronization_style_get_string(node->synchronization_style));
     671    }
     672
     673    pas_stream_printf(stream, "\n");
     674
     675    return true;
     676}
     677
     678void pas_status_reporter_dump_large_sharing_pool(pas_stream* stream)
     679{
     680    pas_stream_printf(stream, "    Large sharing pool contents:\n");
     681    pas_large_sharing_pool_for_each(dump_large_sharing_pool_node_callback, stream, pas_lock_is_held);
    645682}
    646683
     
    959996    pas_status_reporter_dump_all_shared_page_directories(stream);
    960997    pas_status_reporter_dump_all_heaps_non_utility_summaries(stream);
     998   
     999    if (pas_status_reporter_enabled >= 3)
     1000        pas_status_reporter_dump_large_sharing_pool(stream);
     1001   
    9611002    pas_status_reporter_dump_utility_heap(stream);
    9621003
  • trunk/Source/bmalloc/libpas/src/libpas/pas_status_reporter.h

    r282556 r283178  
    11/*
    2  * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
     2 * Copyright (c) 2019-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    6969PAS_API void pas_status_reporter_dump_all_shared_page_directories(pas_stream* stream);
    7070PAS_API void pas_status_reporter_dump_all_heaps_non_utility_summaries(pas_stream* stream);
     71PAS_API void pas_status_reporter_dump_large_sharing_pool(pas_stream* stream);
    7172PAS_API void pas_status_reporter_dump_utility_heap(pas_stream* stream);
    7273PAS_API void pas_status_reporter_dump_total_fragmentation(pas_stream* stream);
  • trunk/Source/bmalloc/libpas/src/test/LargeSharingPoolDump.cpp

    r279867 r283178  
    5050}
    5151
     52vector<pas_large_sharing_node*> largeSharingPoolAsVector()
     53{
     54    vector<pas_large_sharing_node*> result;
     55    forEachLargeSharingPoolNode([&] (pas_large_sharing_node* node) -> bool {
     56        result.push_back(node);
     57        return true;
     58    });
     59    return result;
     60}
     61
    5262void dumpLargeSharingPool()
    5363{
  • trunk/Source/bmalloc/libpas/src/test/LargeSharingPoolDump.h

    r279867 r283178  
    3232#include <functional>
    3333#include "pas_large_sharing_pool.h"
     34#include <vector>
    3435
    3536void forEachLargeSharingPoolNode(std::function<bool(pas_large_sharing_node*)> visitor);
     37std::vector<pas_large_sharing_node*> largeSharingPoolAsVector();
    3638void dumpLargeSharingPool();
    3739
  • trunk/Source/bmalloc/libpas/src/test/LargeSharingPoolTests.cpp

    r279867 r283178  
    11/*
    2  * Copyright (c) 2018-2019 Apple Inc. All rights reserved.
     2 * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    5757    {
    5858    }
     59
     60    explicit Range(pas_large_sharing_node* node)
     61        : begin(node->range.begin)
     62        , end(node->range.end)
     63        , isCommitted(node->is_committed)
     64        , numLiveBytes(node->num_live_bytes)
     65        , epoch(node->use_epoch)
     66    {
     67    }
     68
     69    bool operator==(Range other) const
     70    {
     71        return begin == other.begin
     72            && end == other.end
     73            && isCommitted == other.isCommitted
     74            && numLiveBytes == other.numLiveBytes
     75            && epoch == other.epoch;
     76    }
     77
     78    bool operator!=(Range other) const { return !(*this == other); }
    5979   
    6080    uintptr_t begin;
     
    6585};
    6686
     87ostream& operator<<(ostream& out, Range range)
     88{
     89    out << reinterpret_cast<void*>(range.begin) << "..." << reinterpret_cast<void*>(range.end)
     90        << ", " << (range.isCommitted ? "committed" : "decommitted") << ", "
     91        << range.numLiveBytes << "/" << (range.end - range.begin) << ", " << range.epoch;
     92    return out;
     93}
     94
    6795void assertState(const vector<Range>& ranges)
    6896{
    69     size_t index = 0;
    70     forEachLargeSharingPoolNode(
    71         [&] (pas_large_sharing_node* node) -> bool {
    72             CHECK_LESS(index, ranges.size());
    73             Range expectedRange = ranges[index++];
    74             CHECK_EQUAL(node->range.begin, expectedRange.begin);
    75             CHECK_EQUAL(node->range.end, expectedRange.end);
    76             CHECK_EQUAL(node->is_committed, expectedRange.isCommitted);
    77             CHECK_EQUAL(node->num_live_bytes, expectedRange.numLiveBytes);
    78             CHECK_EQUAL(node->use_epoch, expectedRange.epoch);
    79             return true;
    80         });
    81     CHECK_EQUAL(index, ranges.size());
     97    vector<pas_large_sharing_node*> nodes = largeSharingPoolAsVector();
     98   
     99    bool allGood = true;
     100
     101    if (nodes.size() != ranges.size()) {
     102        cout << "State does not match because we expected " << ranges.size() << " ranges but got "
     103             << nodes.size() << " ranges.\n";
     104        allGood = false;
     105    } else {
     106        for (size_t index = 0; index < nodes.size(); ++index) {
     107            pas_large_sharing_node* node = nodes[index];
     108            Range actualRange(node);
     109            Range expectedRange = ranges[index];
     110            if (expectedRange != actualRange) {
     111                cout << "State does not match at index " << index << ": expected:\n"
     112                     << "    " << expectedRange << ", but got:\n"
     113                     << "    " << actualRange << "\n";
     114                allGood = false;
     115            }
     116        }
     117    }
     118
     119    if (!allGood) {
     120        cout << "Got mismatch in states. Expected the state to be:\n";
     121        for (Range range : ranges)
     122            cout << "    " << range << "\n";
     123        cout << "But got:\n";
     124        for (pas_large_sharing_node* node : nodes)
     125            cout << "    " << Range(node) << "\n";
     126    }
     127
     128    CHECK(allGood);
    82129}
    83130
    84 void testBadCoalesceEpochUpdate()
     131void testGoodCoalesceEpochUpdate()
    85132{
    86133    static constexpr bool verbose = false;
     
    124171        dumpLargeSharingPool();
    125172
    126     assertState({ Range(0, 10 * PG, pas_committed, 10 * PG, 0),
    127                   Range(10 * PG, 30 * PG, pas_committed, 20 * PG, 3),
    128                   Range(30 * PG, END, pas_committed, END - 30 * PG, 0) });
    129    
     173    assertState({ Range(0, END, pas_committed, END, 3) });
    130174}
    131175
     
    139183    EpochIsCounter epochIsCounter;
    140184   
    141     ADD_TEST(testBadCoalesceEpochUpdate());
     185    ADD_TEST(testGoodCoalesceEpochUpdate());
    142186#endif // TLC
    143187}
Note: See TracChangeset for help on using the changeset viewer.