1/*
2 * Copyright (C) 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "CPUCount.h"
27#include "Interpreter.h"
28#include <assert.h>
29#include <cstddef>
30#include <cstdlib>
31#include <errno.h>
32#include <fcntl.h>
33#include <iostream>
34#include <string>
35#include <string.h>
36#include <sys/mman.h>
37#include <sys/stat.h>
38#include <sys/types.h>
39#include <sys/uio.h>
40#include <unistd.h>
41#include <vector>
42
43#include "mbmalloc.h"
44
45#define UNUSED_PARAM(variable) (void)variable
46
47Interpreter::Interpreter(const char* fileName, bool shouldFreeAllObjects, bool useThreadId)
48 : m_shouldFreeAllObjects(shouldFreeAllObjects)
49 , m_useThreadId(useThreadId)
50 , m_currentThreadId(0)
51 , m_ops(1024)
52{
53 m_fd = open(fileName, O_RDONLY);
54 if (m_fd == -1) {
55 fprintf(stderr, "Failed to open op file %s: ", fileName);
56 perror("");
57 exit(-1);
58 }
59
60 struct stat buf;
61 fstat(m_fd, &buf);
62
63 m_opCount = buf.st_size / sizeof(Op);
64 assert(m_opCount * sizeof(Op) == static_cast<size_t>(buf.st_size));
65
66 size_t maxSlot = 0;
67
68 std::vector<Op> ops(1024);
69 size_t remaining = m_opCount * sizeof(Op);
70 while (remaining) {
71 size_t bytes = std::min(remaining, ops.size() * sizeof(Op));
72 remaining -= bytes;
73 auto ret = read(m_fd, ops.data(), bytes);
74 UNUSED_PARAM(ret);
75
76 size_t opCount = bytes / sizeof(Op);
77 for (size_t i = 0; i < opCount; ++i) {
78 Op op = ops[i];
79 if (op.slot > maxSlot)
80 maxSlot = op.slot;
81 }
82 }
83
84 m_objects.resize(maxSlot + 1);
85}
86
87Interpreter::~Interpreter()
88{
89 int result = close(m_fd);
90 if (result == -1) {
91 perror("Failed to close op file");
92 exit(-1);
93 }
94}
95
96void Interpreter::run()
97{
98 std::vector<Op> ops(1024);
99 lseek(m_fd, 0, SEEK_SET);
100
101 m_remaining = m_opCount * sizeof(Op);
102 m_opsCursor = m_opsInBuffer = 0;
103 doOnSameThread(0);
104
105 for (auto thread : m_threads)
106 thread->stop();
107
108 for (auto thread : m_threads)
109 delete thread;
110
111 // A recording might not free all of its allocations.
112 if (!m_shouldFreeAllObjects)
113 return;
114
115 for (size_t i = 0; i < m_objects.size(); ++i) {
116 if (!m_objects[i].object)
117 continue;
118 mbfree(m_objects[i].object, m_objects[i].size);
119 m_objects[i] = { 0, 0 };
120 }
121}
122
123bool Interpreter::readOps()
124{
125 if (!m_remaining)
126 return false;
127
128 size_t bytes = std::min(m_remaining, m_ops.size() * sizeof(Op));
129 m_remaining -= bytes;
130 auto ret = read(m_fd, m_ops.data(), bytes);
131 UNUSED_PARAM(ret);
132 m_opsCursor = 0;
133 m_opsInBuffer = bytes / sizeof(Op);
134
135 if (!m_opsInBuffer)
136 return false;
137
138 return true;
139}
140
141void Interpreter::doOnSameThread(ThreadId runThreadId)
142{
143 while (true) {
144 if ((m_opsCursor >= m_opsInBuffer) && (!readOps())) {
145 if (runThreadId)
146 switchToThread(0);
147 return;
148 }
149
150 for (; m_opsCursor < m_opsInBuffer; ++m_opsCursor) {
151 Op op = m_ops[m_opsCursor];
152 ThreadId threadId = op.threadId;
153 if (m_useThreadId && (runThreadId != threadId)) {
154 switchToThread(threadId);
155 break;
156 }
157
158 doMallocOp(op, m_currentThreadId);
159 }
160 }
161}
162
163void Interpreter::switchToThread(ThreadId threadId)
164{
165 if (m_currentThreadId == threadId)
166 return;
167
168 for (ThreadId threadIndex = static_cast<ThreadId>(m_threads.size());
169 threadIndex < threadId; ++threadIndex)
170 m_threads.push_back(new Thread(this, threadId));
171
172 ThreadId currentThreadId = m_currentThreadId;
173
174 if (threadId == 0) {
175 std::unique_lock<std::mutex> lock(m_threadMutex);
176 m_currentThreadId = threadId;
177 m_shouldRun.notify_one();
178 } else
179 m_threads[threadId - 1]->switchTo();
180
181 if (currentThreadId == 0) {
182 std::unique_lock<std::mutex> lock(m_threadMutex);
183 m_shouldRun.wait(lock, [this](){return m_currentThreadId == 0; });
184 } else
185 m_threads[currentThreadId - 1]->waitToRun();
186}
187
188void Interpreter::detailedReport()
189{
190 size_t totalInUse = 0;
191 size_t smallInUse = 0;
192 size_t mediumInUse = 0;
193 size_t largeInUse = 0;
194 size_t extraLargeInUse = 0;
195 size_t memoryAllocated = 0;
196
197 for (size_t i = 0; i < m_objects.size(); ++i) {
198 if (!m_objects[i].object)
199 continue;
200 size_t objectSize = m_objects[i].size;
201 memoryAllocated += objectSize;
202 totalInUse++;
203
204 if (objectSize <= 256)
205 smallInUse++;
206 else if (objectSize <= 1024)
207 mediumInUse++;
208 else if (objectSize <= 1032192)
209 largeInUse++;
210 else
211 extraLargeInUse++;
212 }
213
214 std::cout << "0B-256B objects in use: " << smallInUse << std::endl;
215 std::cout << "257B-1K objects in use: " << mediumInUse << std::endl;
216 std::cout << " 1K-1M objects in use: " << largeInUse << std::endl;
217 std::cout << " 1M+ objects in use: " << extraLargeInUse << std::endl;
218 std::cout << " Total objects in use: " << totalInUse << std::endl;
219 std::cout << "Total allocated memory: " << memoryAllocated / 1024 << "kB" << std::endl;
220}
221static size_t compute2toPower(unsigned log2n)
222{
223 // Check for bad alignment log2 value and return a bad alignment.
224 if (log2n > 64)
225 return 0xff00;
226
227 size_t result = 1;
228 while (log2n--)
229 result <<= 1;
230
231 return result;
232}
233
234void Interpreter::doMallocOp(Op op, ThreadId)
235{
236 switch (op.opcode) {
237 case op_malloc: {
238 m_objects[op.slot] = { mbmalloc(op.size), op.size };
239 assert(m_objects[op.slot].object);
240 bzero(m_objects[op.slot].object, op.size);
241 break;
242 }
243 case op_free: {
244 if (!m_objects[op.slot].object)
245 return;
246 mbfree(m_objects[op.slot].object, m_objects[op.slot].size);
247 m_objects[op.slot] = { 0, 0 };
248 break;
249 }
250 case op_realloc: {
251 if (!m_objects[op.slot].object)
252 return;
253 m_objects[op.slot] = { mbrealloc(m_objects[op.slot].object, m_objects[op.slot].size, op.size), op.size };
254 break;
255 }
256 case op_align_malloc: {
257 size_t alignment = compute2toPower(op.alignLog2);
258 m_objects[op.slot] = { mbmemalign(alignment, op.size), op.size };
259 assert(m_objects[op.slot].object);
260 bzero(m_objects[op.slot].object, op.size);
261 break;
262 }
263 default: {
264 fprintf(stderr, "bad opcode: %d\n", op.opcode);
265 abort();
266 break;
267 }
268 }
269}
270
271Interpreter::Thread::Thread(Interpreter* myInterpreter, ThreadId threadId)
272 : m_threadId(threadId)
273 , m_myInterpreter(myInterpreter)
274{
275 m_thread = std::thread(&Thread::runThread, this);
276}
277
278void Interpreter::Thread::stop()
279{
280 m_myInterpreter->switchToThread(m_threadId);
281}
282
283Interpreter::Thread::~Thread()
284{
285 switchTo();
286 m_thread.join();
287}
288
289void Interpreter::Thread::runThread()
290{
291 waitToRun();
292 m_myInterpreter->doOnSameThread(m_threadId);
293}
294
295void Interpreter::Thread::waitToRun()
296{
297 std::unique_lock<std::mutex> lock(m_myInterpreter->m_threadMutex);
298 m_shouldRun.wait(lock, [this](){return m_myInterpreter->m_currentThreadId == m_threadId; });
299}
300
301void Interpreter::Thread::switchTo()
302{
303 std::unique_lock<std::mutex> lock(m_myInterpreter->m_threadMutex);
304 m_myInterpreter->m_currentThreadId = m_threadId;
305 m_shouldRun.notify_one();
306}
307