Skip to content

Commit 864decf

Browse files
committed
Creating JIT infrastructure.
1 parent 7bc8ed4 commit 864decf

3 files changed

Lines changed: 159 additions & 1 deletion

File tree

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.PHONY: all hello mandelbrot clean
22

3-
CXX = clang++ -Wall -std=c++11
3+
CXX = clang++ -Wall -std=c++17
44

55
all:
66
mkdir -p bin
@@ -10,6 +10,12 @@ dbg:
1010
mkdir -p bin
1111
$(CXX) -O0 -g -o bin/zero-debug zero.cpp
1212

13+
jit:
14+
mkdir -p bin
15+
$(CXX) -O0 -g -o bin/zero-jit jit.cpp
16+
codesign -s - -f --entitlements entitlements.plist ./bin/zero-jit
17+
./bin/zero-jit ./test/helloworld.b
18+
1319
hello: all
1420
./bin/zero ./test/helloworld.b
1521

entitlements.plist

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.cs.allow-jit</key>
6+
<true/>
7+
</dict>
8+
</plist>

jit.cpp

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#include <cerrno>
2+
#include <cstdint>
3+
#include <fstream>
4+
#include <filesystem>
5+
#include <iostream>
6+
#include <libkern/OSCacheControl.h>
7+
#include <stack>
8+
#include <stdexcept>
9+
#include <sys/mman.h>
10+
11+
// The number of bytes an instruction is wide. AArch64 is 32-bit.
12+
#define INS_WIDTH 4
13+
14+
using std::uintmax_t;
15+
using std::fstream;
16+
using std::stack;
17+
18+
// We work with a 50k memory cell, could be more, could be less.
19+
// Wrap-around is forbidden, so it's good to have a large space.
20+
constexpr size_t MEMORY_SIZE = 50000;
21+
22+
// Compile a single instruction.
23+
// This will take advantage of the compilation state, and if applicable
24+
// patch previous compilations.
25+
void compile(uint32_t* baseAddress,
26+
size_t &pc,
27+
char &c,
28+
stack<size_t> &jumpPoints);
29+
30+
// Creates an executable memory region sufficiently large to contain all the
31+
// instructions in the program.
32+
uint32_t* createExecutableMemory(uintmax_t length);
33+
34+
// Sets up the stack/registers.
35+
void prelude(uint32_t* baseAddress, size_t &pc, char (&memory)[MEMORY_SIZE]);
36+
37+
// Restores the stack and returns execution.
38+
void postlude(uint32_t* baseAddress, uintmax_t instructionSize, size_t &pc);
39+
40+
// Helper function to write a 32-bit instruction to the byte array.
41+
void writeInstruction(uint32_t* baseAddress, size_t &pc, uint32_t instruction);
42+
43+
// The main function takes in arguments and then executes the code.
44+
int main(int argc, char** argv) {
45+
// We expect most of the time the program is provided.
46+
if (__builtin_expect(argc < 2, 0)) {
47+
std::cerr << "zero: please provide the input file" << std::endl;
48+
return 1;
49+
}
50+
// Get the file name, and consequently read the size of the file.
51+
// In the JIT version, we do not accept non-code characters in order to be
52+
// able to very quickly estimate the instruction file size.
53+
char* fileName = argv[1];
54+
uintmax_t fileSize = std::filesystem::file_size(fileName);
55+
// Perform the memory mapping. This is a pessimistic estimate. We assume that
56+
// we perform 4 instructions (128 bits) per BF instruction, plus an additional
57+
// 8 instructions for setup and teardown.
58+
uintmax_t instructionSize = (INS_WIDTH * 4 * fileSize) + (INS_WIDTH * 8);
59+
uint32_t* baseAddress = createExecutableMemory(instructionSize);
60+
// Keep track of the program address and memory.
61+
size_t pc = 0;
62+
char memory[MEMORY_SIZE] = {0};
63+
// Write the assembly prelude.
64+
prelude(baseAddress, pc, memory);
65+
// Keep track of the jump points for loops.
66+
stack<size_t> jumpPoints;
67+
char ch;
68+
// Read the file line by line and write it to the address space.
69+
fstream file(fileName, fstream::in);
70+
while (file >> ch) {
71+
compile(baseAddress, pc, ch, jumpPoints);
72+
// Offset by one instruction.
73+
//pc += INS_WIDTH;
74+
}
75+
if (!__builtin_expect(jumpPoints.empty(), 1)) {
76+
throw std::runtime_error("program parse error: expected ]");
77+
}
78+
// Write the assembly postlude.
79+
postlude(baseAddress, instructionSize, pc);
80+
// Jump to the actual JIT subroutine.
81+
return reinterpret_cast<uint32_t(*)()>(baseAddress)();
82+
}
83+
84+
// Actually perform the compilation. This should modify the program counter.
85+
// The register allocation is as follows:
86+
// x19 -- base address of the memory cells [callee saved].
87+
// x20 -- memory index [callee saved].
88+
inline void compile(uint32_t* baseAddress,
89+
size_t &pc,
90+
char &c,
91+
stack<size_t> &jumpPoints) {
92+
//std::cout << c << std::endl;
93+
}
94+
95+
// The kernel will automatically unmap this in program exit.
96+
// This positively influences the runtime of the compiled binary.
97+
inline uint32_t* createExecutableMemory(uintmax_t length) {
98+
void* startingAddress = mmap(nullptr,
99+
length,
100+
PROT_READ | PROT_WRITE | PROT_EXEC,
101+
MAP_PRIVATE | MAP_ANON | MAP_JIT,
102+
-1,
103+
0);
104+
return reinterpret_cast<uint32_t*>(startingAddress);
105+
}
106+
107+
inline void prelude(uint32_t* baseAddress,
108+
size_t &pc,
109+
char (&memory)[MEMORY_SIZE]) {
110+
// Need to give permission to write to the memory.
111+
pthread_jit_write_protect_np(0);
112+
writeInstruction(baseAddress, pc, 0b11010110010111110000001111000000);
113+
return;
114+
// Start by snapshotting callee saved registers to the stack:
115+
// stp x19, x20, [sp, #-16]!
116+
writeInstruction(baseAddress, pc, 0xf353bfa9);
117+
// mov x20, #0
118+
writeInstruction(baseAddress, pc, 0x140080d2);
119+
// mov x0, xzr
120+
writeInstruction(baseAddress, pc, 0xe0031faa);
121+
}
122+
123+
inline void postlude(uint32_t* baseAddress,
124+
uintmax_t instructionSize,
125+
size_t &pc) {
126+
// Restore the callee saved registers.
127+
// ldp x19, x20, [sp], #16
128+
//writeInstruction(baseAddress, pc, 0xf353c1a8);
129+
// ret
130+
//writeInstruction(baseAddress, pc, 0xc0035fd6);
131+
// Disallow writing to the memory region again.
132+
pthread_jit_write_protect_np(1);
133+
// Flush the instruction cache explicitly.
134+
sys_icache_invalidate(baseAddress, instructionSize);
135+
char* charAddress = reinterpret_cast<char*>(baseAddress);
136+
__builtin___clear_cache(charAddress, charAddress + instructionSize);
137+
}
138+
139+
inline void writeInstruction(uint32_t* baseAddress,
140+
size_t &pc,
141+
uint32_t instruction) {
142+
baseAddress[pc] = instruction;
143+
pc++;
144+
}

0 commit comments

Comments
 (0)