/* * Copyright (C) 2014 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 "CPUCount.h" #include "Interpreter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mbmalloc.h" #define UNUSED_PARAM(variable) (void)variable Interpreter::Interpreter(const char* fileName, bool shouldFreeAllObjects, bool useThreadId) : m_shouldFreeAllObjects(shouldFreeAllObjects) , m_useThreadId(useThreadId) , m_currentThreadId(0) , m_ops(1024) { m_fd = open(fileName, O_RDONLY); if (m_fd == -1) { fprintf(stderr, "Failed to open op file %s: ", fileName); perror(""); exit(-1); } struct stat buf; fstat(m_fd, &buf); m_opCount = buf.st_size / sizeof(Op); assert(m_opCount * sizeof(Op) == static_cast(buf.st_size)); size_t maxSlot = 0; std::vector ops(1024); size_t remaining = m_opCount * sizeof(Op); while (remaining) { size_t bytes = std::min(remaining, ops.size() * sizeof(Op)); remaining -= bytes; auto ret = read(m_fd, ops.data(), bytes); UNUSED_PARAM(ret); size_t opCount = bytes / sizeof(Op); for (size_t i = 0; i < opCount; ++i) { Op op = ops[i]; if (op.slot > maxSlot) maxSlot = op.slot; } } m_objects.resize(maxSlot + 1); } Interpreter::~Interpreter() { int result = close(m_fd); if (result == -1) { perror("Failed to close op file"); exit(-1); } } void Interpreter::run() { std::vector ops(1024); lseek(m_fd, 0, SEEK_SET); m_remaining = m_opCount * sizeof(Op); m_opsCursor = m_opsInBuffer = 0; doOnSameThread(0); for (auto thread : m_threads) thread->stop(); for (auto thread : m_threads) delete thread; // A recording might not free all of its allocations. if (!m_shouldFreeAllObjects) return; for (size_t i = 0; i < m_objects.size(); ++i) { if (!m_objects[i].object) continue; mbfree(m_objects[i].object, m_objects[i].size); m_objects[i] = { 0, 0 }; } } bool Interpreter::readOps() { if (!m_remaining) return false; size_t bytes = std::min(m_remaining, m_ops.size() * sizeof(Op)); m_remaining -= bytes; auto ret = read(m_fd, m_ops.data(), bytes); UNUSED_PARAM(ret); m_opsCursor = 0; m_opsInBuffer = bytes / sizeof(Op); if (!m_opsInBuffer) return false; return true; } void Interpreter::doOnSameThread(ThreadId runThreadId) { while (true) { if ((m_opsCursor >= m_opsInBuffer) && (!readOps())) { if (runThreadId) switchToThread(0); return; } for (; m_opsCursor < m_opsInBuffer; ++m_opsCursor) { Op op = m_ops[m_opsCursor]; ThreadId threadId = op.threadId; if (m_useThreadId && (runThreadId != threadId)) { switchToThread(threadId); break; } doMallocOp(op, m_currentThreadId); } } } void Interpreter::switchToThread(ThreadId threadId) { if (m_currentThreadId == threadId) return; for (ThreadId threadIndex = static_cast(m_threads.size()); threadIndex < threadId; ++threadIndex) m_threads.push_back(new Thread(this, threadId)); ThreadId currentThreadId = m_currentThreadId; if (threadId == 0) { std::unique_lock lock(m_threadMutex); m_currentThreadId = threadId; m_shouldRun.notify_one(); } else m_threads[threadId - 1]->switchTo(); if (currentThreadId == 0) { std::unique_lock lock(m_threadMutex); m_shouldRun.wait(lock, [this](){return m_currentThreadId == 0; }); } else m_threads[currentThreadId - 1]->waitToRun(); } void Interpreter::detailedReport() { size_t totalInUse = 0; size_t smallInUse = 0; size_t mediumInUse = 0; size_t largeInUse = 0; size_t extraLargeInUse = 0; size_t memoryAllocated = 0; for (size_t i = 0; i < m_objects.size(); ++i) { if (!m_objects[i].object) continue; size_t objectSize = m_objects[i].size; memoryAllocated += objectSize; totalInUse++; if (objectSize <= 256) smallInUse++; else if (objectSize <= 1024) mediumInUse++; else if (objectSize <= 1032192) largeInUse++; else extraLargeInUse++; } std::cout << "0B-256B objects in use: " << smallInUse << std::endl; std::cout << "257B-1K objects in use: " << mediumInUse << std::endl; std::cout << " 1K-1M objects in use: " << largeInUse << std::endl; std::cout << " 1M+ objects in use: " << extraLargeInUse << std::endl; std::cout << " Total objects in use: " << totalInUse << std::endl; std::cout << "Total allocated memory: " << memoryAllocated / 1024 << "kB" << std::endl; } static size_t compute2toPower(unsigned log2n) { // Check for bad alignment log2 value and return a bad alignment. if (log2n > 64) return 0xff00; size_t result = 1; while (log2n--) result <<= 1; return result; } void Interpreter::doMallocOp(Op op, ThreadId) { switch (op.opcode) { case op_malloc: { m_objects[op.slot] = { mbmalloc(op.size), op.size }; assert(m_objects[op.slot].object); bzero(m_objects[op.slot].object, op.size); break; } case op_free: { if (!m_objects[op.slot].object) return; mbfree(m_objects[op.slot].object, m_objects[op.slot].size); m_objects[op.slot] = { 0, 0 }; break; } case op_realloc: { if (!m_objects[op.slot].object) return; m_objects[op.slot] = { mbrealloc(m_objects[op.slot].object, m_objects[op.slot].size, op.size), op.size }; break; } case op_align_malloc: { size_t alignment = compute2toPower(op.alignLog2); m_objects[op.slot] = { mbmemalign(alignment, op.size), op.size }; assert(m_objects[op.slot].object); bzero(m_objects[op.slot].object, op.size); break; } default: { fprintf(stderr, "bad opcode: %d\n", op.opcode); abort(); break; } } } Interpreter::Thread::Thread(Interpreter* myInterpreter, ThreadId threadId) : m_threadId(threadId) , m_myInterpreter(myInterpreter) { m_thread = std::thread(&Thread::runThread, this); } void Interpreter::Thread::stop() { m_myInterpreter->switchToThread(m_threadId); } Interpreter::Thread::~Thread() { switchTo(); m_thread.join(); } void Interpreter::Thread::runThread() { waitToRun(); m_myInterpreter->doOnSameThread(m_threadId); } void Interpreter::Thread::waitToRun() { std::unique_lock lock(m_myInterpreter->m_threadMutex); m_shouldRun.wait(lock, [this](){return m_myInterpreter->m_currentThreadId == m_threadId; }); } void Interpreter::Thread::switchTo() { std::unique_lock lock(m_myInterpreter->m_threadMutex); m_myInterpreter->m_currentThreadId = m_threadId; m_shouldRun.notify_one(); }