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
|
#include "stdafx.h"
#include "CodeX64.h"
#include "Gc.h"
#include "Core/GcCode.h"
#include "DwarfTable.h"
#include "Utils/Cache.h"
namespace storm {
namespace x64 {
// Will not work properly unless on a 64-bit machine.
#ifdef X64
static inline bool singleInt(Word value) {
const Long limit = Long(1) << Long(31);
Long v(value);
return v >= -limit && v < limit;
}
static inline void writeJump(void *code, const GcCodeRef &ref) {
// We're dealing with 6 bytes of data. The pointer, located at 'now->offset' and two
// bytes of op-codes located just before the pointer. For simplicity, we will read 2
// additional bytes after the pointer so that we can manipulate an entire machine word
// of 8 bytes when reading/writing to the machine code. This allows us to use atomic
// instructions properly. We can always do this safely, as the GcCode object will always
// be allocated directly after the code segment. Thereby, there will always be memory we
// can legally access (even though we should not trash it).
//
// There are two variants of OP-codes used here, short and long. The short variants are
// used when the target address is within 2GB of the end of the called pointer. The long
// variant is used otherwise. Both variants need to be the same length, and we can not
// use a 'nop' instruction to for padding (since then we could alter the instructions
// after the 'nop' has been executed, but before the actual jump has been executed,
// causing execution to resume in the middle of an instruction). Therefore, we pad the
// short variant using a REX.W prefix.
//
// The variants are encoded as follows:
// short long
// call: 48 E8 <offset> FF 15 <offset>
// jmp: 48 E9 <offset> FF 25 <offset>
//
// For the short variant, offset is the offset of the actual jump or call target. In the
// long variant, the offset is the offset to an 8-byte location containing the jump
// target. We will use this to refer to 'now->pointer'. Both offsets are relative to the
// last byte of the offset.
void *mem = ((byte *)code) + ref.offset - 2;
// Read the current contents of the memory.
size_t original = unalignedAtomicRead(*(size_t *)mem);
// Find out if 'jmp' or 'call' was used.
bool call = false;
switch (original & 0xFFFF) {
case 0xE848:
case 0x15FF:
call = true;
break;
case 0xE948:
case 0x25FF:
call = false;
break;
default:
dbg_assert(false, L"Unknown machine code. Jump or call expected!");
break;
}
// Add the uninteresting bytes from the original.
size_t insert = original & (size_t(0xFFFF) << 48);
size_t delta = size_t(ref.pointer) - (size_t(mem) + 6);
if (singleInt(delta)) {
// Use the short variant.
insert |= (call ? 0xE848 : 0xE948);
insert |= (delta & 0xFFFFFFFF) << 16;
} else {
// Use the long variant.
// Compute the offset to '&pointer'.
delta = size_t(&ref.pointer) - (size_t(mem) + 6);
insert |= (call ? 0x15FF : 0x25FF);
insert |= (delta & 0xFFFFFFFF) << 16;
}
// Write the value back to memory.
unalignedAtomicWrite(*(size_t *)mem, insert);
invalidateICache(mem, (byte *)mem + sizeof(size_t));
}
#endif
void writePtr(void *code, const GcCode *refs, Nat id) {
const GcCodeRef &ref = refs->refs[id];
switch (ref.kind) {
case GcCodeRef::jump:
#ifdef X64
writeJump(code, ref);
#endif
break;
default:
dbg_assert(false, L"Only 'jump' is supported by this backend.");
break;
}
}
void finalize(void *code) {
(void)code;
}
}
}
|