/*
 * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "TestHarness.h"

#if TLC

#include "LargeSharingPoolDump.h"
#include "pas_epoch.h"
#include "pas_heap_lock.h"
#include "pas_large_sharing_pool.h"
#include "pas_page_malloc.h"
#include "pas_physical_memory_transaction.h"
#include "pas_scavenger.h"
#include <vector>

#define PG (pas_page_malloc_alignment())
#define END ((uintptr_t)1 << PAS_ADDRESS_BITS)

using namespace std;

namespace {

struct Range {
    Range(uintptr_t begin,
          uintptr_t end,
          pas_commit_mode isCommitted,
          size_t numLiveBytes,
          uint64_t epoch)
        : begin(begin)
        , end(end)
        , isCommitted(isCommitted)
        , numLiveBytes(numLiveBytes)
        , epoch(epoch)
    {
    }

    explicit Range(pas_large_sharing_node* node)
        : begin(node->range.begin)
        , end(node->range.end)
        , isCommitted(node->is_committed)
        , numLiveBytes(node->num_live_bytes)
        , epoch(node->use_epoch)
    {
    }

    bool operator==(Range other) const
    {
        return begin == other.begin
            && end == other.end
            && isCommitted == other.isCommitted
            && numLiveBytes == other.numLiveBytes
            && epoch == other.epoch;
    }

    bool operator!=(Range other) const { return !(*this == other); }
    
    uintptr_t begin;
    uintptr_t end;
    pas_commit_mode isCommitted;
    size_t numLiveBytes;
    uint64_t epoch;
};

ostream& operator<<(ostream& out, Range range)
{
    out << reinterpret_cast<void*>(range.begin) << "..." << reinterpret_cast<void*>(range.end)
        << ", " << (range.isCommitted ? "committed" : "decommitted") << ", "
        << range.numLiveBytes << "/" << (range.end - range.begin) << ", " << range.epoch;
    return out;
}

void assertState(const vector<Range>& ranges)
{
    vector<pas_large_sharing_node*> nodes = largeSharingPoolAsVector();
    
    bool allGood = true;

    if (nodes.size() != ranges.size()) {
        cout << "State does not match because we expected " << ranges.size() << " ranges but got "
             << nodes.size() << " ranges.\n";
        allGood = false;
    } else {
        for (size_t index = 0; index < nodes.size(); ++index) {
            pas_large_sharing_node* node = nodes[index];
            Range actualRange(node);
            Range expectedRange = ranges[index];
            if (expectedRange != actualRange) {
                cout << "State does not match at index " << index << ": expected:\n"
                     << "    " << expectedRange << ", but got:\n"
                     << "    " << actualRange << "\n";
                allGood = false;
            }
        }
    }

    if (!allGood) {
        cout << "Got mismatch in states. Expected the state to be:\n";
        for (Range range : ranges)
            cout << "    " << range << "\n";
        cout << "But got:\n";
        for (pas_large_sharing_node* node : nodes)
            cout << "    " << Range(node) << "\n";
    }

    CHECK(allGood);
}

void testGoodCoalesceEpochUpdate()
{
    static constexpr bool verbose = false;
    
    pas_physical_memory_transaction transaction;
    
    pas_scavenger_suspend();
    pas_physical_memory_transaction_construct(&transaction);

    CHECK_EQUAL(pas_current_epoch, 0);
    
    pas_heap_lock_lock();
    pas_large_sharing_pool_boot_free(
        pas_range_create(10 * PG, 20 * PG),
        pas_physical_memory_is_locked_by_virtual_range_common_lock,
        pas_page_flags_none,
        pas_committed);
    pas_heap_lock_unlock();

    assertState({ Range(0, 10 * PG, pas_committed, 10 * PG, 0),
                  Range(10 * PG, 20 * PG, pas_committed, 0, 1),
                  Range(20 * PG, END, pas_committed, END - 20 * PG, 0) });

    pas_heap_lock_lock();
    pas_large_sharing_pool_boot_free(
        pas_range_create(20 * PG, 30 * PG),
        pas_physical_memory_is_locked_by_virtual_range_common_lock,
        pas_page_flags_none,
        pas_committed);
    pas_heap_lock_unlock();

    assertState({ Range(0, 10 * PG, pas_committed, 10 * PG, 0),
                  Range(10 * PG, 20 * PG, pas_committed, 0, 1),
                  Range(20 * PG, 30 * PG, pas_committed, 0, 2),
                  Range(30 * PG, END, pas_committed, END - 30 * PG, 0) });
    
    pas_heap_lock_lock();
    CHECK(pas_large_sharing_pool_allocate_and_commit(
              pas_range_create(10 * PG, 30 * PG),
              &transaction,
              pas_physical_memory_is_locked_by_virtual_range_common_lock,
              pas_page_flags_none));
    pas_heap_lock_unlock();
    
    if (verbose)
        dumpLargeSharingPool();

    assertState({ Range(0, END, pas_committed, END, 3) });
}

} // anonymous namespace

#endif // TLC

void addLargeSharingPoolTests()
{
#if TLC
    EpochIsCounter epochIsCounter;
    
    ADD_TEST(testGoodCoalesceEpochUpdate());
#endif // TLC
}

