-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathFunction.cpp
417 lines (371 loc) · 14.5 KB
/
Function.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "Function.h"
#include <errno.h>
#include <sstream>
#include <stdexcept>
#include <unistd.h>
#include "valgrind.h"
#include "const.h"
#include "LLVMStuff.h"
#include "M6502Internal.h"
#include "Registers.h"
#include "util.h"
// Note that we call update_memory_snapshot() after invoking callbacks here, but
// not before. It would be correct to do so, but it's not necessary. Firstly, we
// arrange that the memory snapshot is kept up-to-date during execution under
// our control (i.e. not involving callbacks), so it isn't necessary. Secondly,
// even if it were necessary, it would be redundant, since any actions needed
// as a result of the update can wait until after the callback is called and the
// call after the callback would perform them.
namespace
{
// We have the callback_pc argument to allow us to special-case the
// contents of the PC register for lib6502 compatibility. Without this
// we would always pass registers.pc, which is "address of the next
// instruction to execute if the callback doesn't intervene" in PC;
// this agrees with lib6502 for JMP (absolute and indirect) but not for JSR
// or BRK.
uint16_t handle_call_callback(M6502 *mpu, uint16_t callback_pc,
uint8_t opcode)
{
Registers ®isters = mpu->internal->registers_;
uint16_t default_next_pc = registers.pc;
if (mpu->callbacks->call[registers.pc] != 0)
{
registers.pc = callback_pc;
registers.to_M6502_Registers(mpu);
TRACE("Call callback, mpu " << mpu << ", address 0x" << std::hex <<
std::setfill('0') << std::setw(4) << default_next_pc <<
", data 0x" << std::setw(2) << static_cast<int>(opcode));
uint16_t address = default_next_pc;
if (opcode == opcode_brk)
{
address = callback_pc - 2; // lib6502 does this
}
int callback_result =
mpu->callbacks->call[default_next_pc](mpu, address, opcode);
TRACE("Callback returned 0x" << std::hex << std::setfill('0') <<
std::setw(4) << callback_result);
registers.from_M6502_Registers(mpu);
mpu->internal->function_manager_.update_memory_snapshot();
if (callback_result != 0)
{
return callback_result;
}
}
return default_next_pc;
}
uint16_t get_stacked_pc(M6502 *mpu, int offset)
{
uint8_t s = mpu->internal->registers_.s;
for (; offset > 0; --offset)
{
++s;
}
++s;
uint8_t pushed_pc_low = mpu->memory[0x100 + s];
++s;
uint8_t pushed_pc_high = mpu->memory[0x100 + s];
return pushed_pc_low | (pushed_pc_high << 8);
}
uint16_t handle_push_and_control_transfer_opcode(
M6502 *mpu, uint16_t callback_pc, uint8_t opcode, int bytes_pushed)
{
assert(bytes_pushed >= 2);
uint8_t s = mpu->internal->registers_.s;
for (int i = 0; i < bytes_pushed; ++i)
{
++s;
mpu->internal->function_manager_.code_modified_at(0x100 + s);
}
return handle_call_callback(mpu, callback_pc, opcode);
}
}
Function::Function(
M6502 *mpu, uint16_t address, const AddressSet &code_range,
const AddressSet &optimistic_writes, llvm::Function *llvm_function)
: mpu_(mpu),
llvm_stuff_(mpu->internal->llvm_stuff_),
address_(address),
code_range_(code_range),
optimistic_writes_(optimistic_writes),
llvm_function_(llvm_function),
jitted_function_(reinterpret_cast<Function::JitFunction>(
llvm_stuff_.execution_engine_->getPointerToFunction(llvm_function)))
{
llvm_stuff_.execution_engine_->runJITOnFunction(llvm_function_, &mci_);
}
Function::~Function()
{
TRACE("Destructor for Function at address " << std::hex <<
std::setfill('0') << std::setw(4) << address_);
VALGRIND_DISCARD_TRANSLATIONS(mci_.address(), mci_.size());
llvm_function_->eraseFromParent();
}
void Function::handle_complex_result(FunctionBuilder::Result result) const
{
Registers ®isters = mpu_->internal->registers_;
switch (result)
{
case FunctionBuilder::result_control_transfer_direct:
CANT_HAPPEN("Direct case reached handle_complex_result()");
case FunctionBuilder::result_control_transfer_indirect:
registers.pc = handle_call_callback(mpu_, registers.pc,
registers.data);
break;
case FunctionBuilder::result_brk:
registers.pc = handle_push_and_control_transfer_opcode(
mpu_, get_stacked_pc(mpu_, 1), opcode_brk, 3);
break;
case FunctionBuilder::result_jsr_complex:
registers.pc = handle_push_and_control_transfer_opcode(
mpu_, get_stacked_pc(mpu_, 0) + 1, opcode_jsr, 2);
break;
case FunctionBuilder::result_illegal_instruction:
{
registers.to_M6502_Registers(mpu_);
TRACE("Illegal instruction callback, mpu " << mpu_ <<
", address 0x" << std::hex << std::setfill('0') <<
std::setw(4) << registers.addr << ", data 0x" <<
std::setw(2) << static_cast<int>(registers.data));
uint16_t new_pc =
mpu_->callbacks->illegal_instruction[registers.data](
mpu_, registers.addr, registers.data);
TRACE("Callback returned 0x" << std::hex << std::setfill('0') <<
std::setw(4) << new_pc);
registers.from_M6502_Registers(mpu_);
mpu_->internal->function_manager_.update_memory_snapshot();
if (new_pc != 0)
{
registers.pc = new_pc;
}
break;
}
case FunctionBuilder::result_write_to_code:
TRACE("Code modified at 0x" << std::hex << std::setfill('0') <<
std::setw(4) << registers.addr);
mpu_->internal->function_manager_.code_modified_at(registers.addr);
break;
case FunctionBuilder::result_write_callback:
{
TRACE("Write callback at 0x" << std::hex << std::setfill('0') <<
std::setw(4) << registers.addr << " with data 0x" <<
std::setw(4) << static_cast<int>(registers.data));
// We *don't* invoke Registers.{to,from}_M6502Registers() before
// and after the callback. We could do this, but lib6502 itself
// (and therefore the lib6502 code used for interpreting in
// lib6502-jit) doesn't do that, so this could be confusing
// for client code. (For example, a callback might be written
// to rely on this, it would work if called from compiled code
// but wouldn't work if called from interpreted mode. So its
// behaviour in hybrid mode would be random.)
(void) mpu_->callbacks->write[registers.addr](
mpu_, registers.addr, registers.data);
mpu_->internal->function_manager_.update_memory_snapshot();
break;
}
case FunctionBuilder::result_invalid_bounds:
CANT_HAPPEN("Invalid bounds inside Function for address 0x" <<
std::hex << std::setfill('0') << std::setw(4) <<
address_);
default:
CANT_HAPPEN("Unknown result " << result << " from JIT function");
}
}
#ifdef LOG
namespace
{
std::string indent(int n, const std::string &s)
{
std::string prefix = spaces(n);
return apply_prefix(prefix, s);
}
}
std::string Function::dump_all() const
{
std::stringstream s;
s << "Function at 0x" << std::hex << std::setfill('0') << std::setw(4) <<
address_ << ":\n";
s << spaces(1) << "Code range:\n" << code_range_.dump(2) << "\n";
s << spaces(1) << "Optimistic writes at:\n" << optimistic_writes_.dump(2) <<
"\n";
s << spaces(1) << "6502 machine code:\n" << indent(2, disassembly_) << "\n";
s << spaces(1) << "Unoptimised IR:\n" << indent(2, unoptimised_ir_) << "\n";
s << spaces(1) << "Optimised IR:\n" << indent(2, optimised_ir_) << "\n";;
s << spaces(1) << "Host machine code:\n" << indent(2, dump_machine_code());
return s.str();
}
#endif
namespace
{
template <class Handle, class CloseFnType, CloseFnType close_fn>
class AutoClose : boost::noncopyable
{
public:
AutoClose(Handle h)
: open_(true), h_(h)
{
}
int close()
{
open_ = false;
return close_fn(h_);
}
~AutoClose()
{
if (open_)
{
close_fn(h_); // ignore return code, nothing we can do if it fails
}
}
private:
bool open_;
Handle h_;
};
typedef int (*FdClose)(int);
typedef AutoClose<int, FdClose, ::close> FdAutoClose;
typedef int (*PopenClose)(FILE *);
typedef AutoClose<FILE *, PopenClose, ::pclose> PopenAutoClose;
}
#ifdef LOG
std::string Function::dump_machine_code() const
{
try
{
// What a performance! The basic idea of outputting .bytes directives,
// assembling those and then disassembling the result is taken from
// libjit's dump_object_code(); the implementation is not copied.
char as_output_file[] = "/tmp/lib6502-jit-XXXXXX";
errno = 0;
// mkstemp() creates a unique filename and opens it. We unlink the file
// immediately so it has no name; this minimises (but does not
// eliminate; we might be killed between mkstemp() and unlink()) the
// chance of the file being left lying around. Since we need a name for
// the 'as' and 'objdump' commands, we use /dev/fd/nn to refer to it
// afterwards.
int fd = mkstemp(as_output_file);
if (fd == -1)
{
fail_errno_or("mkstemp() failed");
}
FdAutoClose auto_close_fd(fd);
if (unlink(as_output_file) == -1)
{
fail_errno_or("unlink() failed");
}
{
std::stringstream as_command;
as_command << "as -o /dev/fd/" << fd << " 2>/dev/null";
FILE *f = popen(as_command.str().c_str(), "w");
if (f == 0)
{
fail_errno_or("popen() failed (for 'as')");
}
PopenAutoClose auto_close_f(f);
unsigned char *p = static_cast<unsigned char *>(mci_.address());
unsigned char *end = p + mci_.size();
for (; p < end; ++p)
{
if (fprintf(f, ".byte %d\n", *p) < 0)
{
fail("Error writing to 'as' pipe");
}
}
if (auto_close_f.close() != 0)
{
fail_errno_or("Error closing 'as' pipe");
}
}
if (lseek(fd, 0, SEEK_SET) == static_cast<off_t>(-1))
{
fail_errno_or("Error seeking on temporary file");
}
std::stringstream objdump_command;
// As far as I can tell, there's no guarantee how mci_.address() [a
// pointer type] will be represented in the stringstream, but in
// practice this code is not very portable anyway and this is the least
// of our worries...
objdump_command << "objdump --adjust-vma=" <<
mci_.address() << " -d /dev/fd/" << fd << " 2>&1";
FILE *g = popen(objdump_command.str().c_str(), "r");
if (g == 0)
{
fail_errno_or("popen() failed (for 'objdump')");
}
PopenAutoClose auto_close_g(g);
std::stringstream code;
char buffer[1024];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), g)) > 0)
{
code << std::string(buffer, bytes_read);
}
if (ferror(g))
{
fail("Error reading from 'objdump' pipe");
}
if (auto_close_g.close() != 0)
{
fail_errno_or("Error closing 'objdump' pipe");
}
if (auto_close_fd.close() != 0)
{
fail_errno_or("Error closing temporary file");
}
return code.str();
}
catch (std::exception &e)
{
// Dumping out the generated machine code is decidedly not critical, so
// we don't allow the exception to propagate.
return std::string("Unable to dump machine code: ") + e.what();
}
}
void Function::fail(const std::string &error) const
{
throw std::runtime_error(error);
}
void Function::fail_errno_or(const std::string &error) const
{
if (errno == 0)
{
fail(error);
}
else
{
// strerror_r() exists in various versions. If you have problems getting
// this to compile, it's probably OK to just use:
// const char *error = strerror(errno);
// given a) the limited amount of threading here and b) the fact this is
// only used to report rare errors in debug-only logging code. If push
// really comes to shove you can just do:
// const char *error = 0;
// and you'll just get unhelpful error messages.
char buffer[1024];
const char *error = strerror_r(errno, buffer, sizeof(buffer));
if (error != 0)
{
fail(error);
}
else
{
fail("Error occurred, and strerror() probably failed as well");
}
}
}
#endif