├── tier0.h ├── tier1.h ├── memory.c ├── Makefile ├── tier0.c ├── code_block.c ├── p1s.c ├── LICENSE.md ├── .gitignore ├── p1s.h ├── README.md ├── code_generation.c ├── compile.c └── tier1.c /tier0.h: -------------------------------------------------------------------------------- 1 | #ifndef TIER0_H_ 2 | #define TIER0_H_ 3 | 4 | #include 5 | 6 | int64_t tier0_interpret(int64_t * byte_code); 7 | 8 | #endif // TIER0_H_ 9 | 10 | -------------------------------------------------------------------------------- /tier1.h: -------------------------------------------------------------------------------- 1 | #ifndef TIER1_H_ 2 | #define TIER1_H_ 3 | 4 | #include "p1s.h" 5 | 6 | void tier1_generate_function(struct code_block * block, int64_t * byte_code); 7 | 8 | #endif // TIER1_H_ 9 | 10 | -------------------------------------------------------------------------------- /memory.c: -------------------------------------------------------------------------------- 1 | #include "p1s.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | void * alloc_page(size_t size) 9 | { 10 | void * ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 11 | if (ptr == MAP_FAILED) 12 | { 13 | perror("mmap"); 14 | return NULL; 15 | } 16 | return ptr; 17 | } 18 | 19 | void free_page(void * ptr, size_t size) 20 | { 21 | if (munmap(ptr, size) == -1) 22 | { 23 | perror("munmap"); 24 | exit(EXIT_FAILURE); 25 | } 26 | } 27 | 28 | void protect_page(void * ptr, size_t size) 29 | { 30 | if (mprotect(ptr, size, PROT_READ | PROT_EXEC) == -1) 31 | { 32 | perror("mprotect"); 33 | exit(EXIT_FAILURE); 34 | } 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | RM = rm -f 3 | CFLAGS = -g -Wall -std=gnu99 4 | LDFLAGS = 5 | 6 | all: p1s 7 | p1s: p1s.o code_block.o code_generation.o memory.o compile.o tier0.o tier1.o 8 | $(CC) $(CFLAGS) $(LDFLAGS) p1s.o code_block.o code_generation.o memory.o compile.o tier0.o tier1.o -o p1s 9 | p1s.o: p1s.c p1s.h tier0.h tier1.h 10 | $(CC) -c $(CFLAGS) p1s.c -o p1s.o 11 | code_block.o: code_block.c p1s.h 12 | $(CC) -c $(CFLAGS) code_block.c -o code_block.o 13 | code_generation.o: code_generation.c p1s.h 14 | $(CC) -c $(CFLAGS) code_generation.c -o code_generation.o 15 | memory.o: memory.c p1s.h 16 | $(CC) -c $(CFLAGS) memory.c -o memory.o 17 | compile.o: compile.c p1s.h 18 | $(CC) -c $(CFLAGS) compile.c -o compile.o 19 | tier0.o: tier0.c tier0.h 20 | $(CC) -c $(CFLAGS) tier0.c -o tier0.o 21 | tier1.o: tier1.c tier1.h 22 | $(CC) -c $(CFLAGS) tier1.c -o tier1.o 23 | 24 | clean: 25 | $(RM) *.o p1s 26 | 27 | -------------------------------------------------------------------------------- /tier0.c: -------------------------------------------------------------------------------- 1 | #include "tier0.h" 2 | 3 | #include 4 | 5 | 6 | static int64_t * interpret(int64_t * result_ptr, int64_t * byte_code) 7 | { 8 | int64_t * loop_end_ptr; 9 | while (1) 10 | { 11 | switch (*byte_code) 12 | { 13 | case 1: 14 | ++*result_ptr; 15 | break; 16 | case 2: 17 | ++byte_code; 18 | for (int i = 0 ; i < *byte_code ; ++i) 19 | loop_end_ptr = interpret(result_ptr, byte_code); 20 | byte_code = loop_end_ptr; 21 | break; 22 | case 3: 23 | return byte_code; 24 | case -1: 25 | return byte_code; 26 | } 27 | ++byte_code; 28 | } 29 | return byte_code; 30 | } 31 | 32 | int64_t tier0_interpret(int64_t * byte_code) 33 | { 34 | int64_t ret = 0; 35 | interpret(&ret, byte_code); 36 | return ret; 37 | } 38 | -------------------------------------------------------------------------------- /code_block.c: -------------------------------------------------------------------------------- 1 | #include "p1s.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void * make_block_executable(struct code_block * block) 8 | { 9 | size_t size = ROUND_TO_PAGE(block->length); // round up to nearest page size 10 | void * ptr = alloc_page(size); 11 | memcpy(ptr, block->code, block->length); 12 | protect_page(ptr, size); 13 | block->executable_code = ptr; 14 | return ptr; 15 | } 16 | 17 | struct code_block * create_code_block() 18 | { 19 | struct code_block * block = malloc(sizeof(struct code_block)); 20 | block->capacity = 1; 21 | block->length = 0; 22 | block->code = malloc(1); 23 | block->executable_code = NULL; 24 | return block; 25 | } 26 | 27 | void destroy_code_block(struct code_block * block) 28 | { 29 | if (block->code) 30 | { 31 | free(block->code); 32 | block->code = NULL; 33 | } 34 | if (block->executable_code) 35 | { 36 | free_page(block->executable_code, ROUND_TO_PAGE(block->length)); 37 | block->executable_code = NULL; 38 | } 39 | free(block); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /p1s.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "p1s.h" 9 | 10 | #include "tier0.h" 11 | #include "tier1.h" 12 | 13 | int64_t incr(int64_t x) 14 | { 15 | return x + 1; 16 | } 17 | 18 | int main(int argc, char ** argv) 19 | { 20 | struct code_block * block = create_code_block(); 21 | if (argc < 2) 22 | { 23 | fprintf(stderr, "Usage: %s INFILE\n", argv[0]); 24 | return EXIT_FAILURE; 25 | } 26 | FILE * text_source = fopen(argv[1], "r"); 27 | if (!text_source) 28 | { 29 | perror("fopen"); 30 | return EXIT_FAILURE; 31 | } 32 | int64_t * byte_code = compile_text_file(text_source); 33 | fclose(text_source); 34 | if (!byte_code) 35 | return EXIT_FAILURE; 36 | tier1_generate_function(block, byte_code); 37 | GeneratedFunction f = (GeneratedFunction) make_block_executable(block); 38 | printf("%" PRId64 " " "%" PRId64 "\n", f(0), tier0_interpret(byte_code)); 39 | destroy_code_block(block); 40 | return 0; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jared Feng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Project Specified ### 2 | 3 | # built executable 4 | p1s 5 | 6 | ### Linux ### 7 | *~ 8 | 9 | # temporary files which can be created if a process still has a handle open of a deleted file 10 | .fuse_hidden* 11 | 12 | # KDE directory preferences 13 | .directory 14 | 15 | # Linux trash folder which might appear on any partition or disk 16 | .Trash-* 17 | 18 | # .nfs files are created when an open file is removed but is still being accessed 19 | .nfs* 20 | 21 | 22 | ### OSX ### 23 | *.DS_Store 24 | .AppleDouble 25 | .LSOverride 26 | 27 | # Thumbnails 28 | ._* 29 | # Files that might appear in the root of a volume 30 | .DocumentRevisions-V100 31 | .fseventsd 32 | .Spotlight-V100 33 | .TemporaryItems 34 | .Trashes 35 | .VolumeIcon.icns 36 | .com.apple.timemachine.donotpresent 37 | # Directories potentially created on remote AFP share 38 | .AppleDB 39 | .AppleDesktop 40 | Network Trash Folder 41 | Temporary Items 42 | .apdisk 43 | 44 | 45 | ### C ### 46 | # Prerequisites 47 | *.d 48 | 49 | # Object files 50 | *.o 51 | *.ko 52 | *.obj 53 | *.elf 54 | 55 | # Linker output 56 | *.ilk 57 | *.map 58 | *.exp 59 | 60 | # Precompiled Headers 61 | *.gch 62 | *.pch 63 | 64 | # Libraries 65 | *.lib 66 | *.a 67 | *.la 68 | *.lo 69 | 70 | # Shared objects (inc. Windows DLLs) 71 | *.dll 72 | *.so 73 | *.so.* 74 | *.dylib 75 | 76 | # Executables 77 | *.exe 78 | *.out 79 | *.app 80 | *.i*86 81 | *.x86_64 82 | *.hex 83 | 84 | # Debug files 85 | *.dSYM/ 86 | *.su 87 | *.idb 88 | *.pdb 89 | 90 | # Kernel Module Compile Results 91 | *.mod* 92 | *.cmd 93 | modules.order 94 | Module.symvers 95 | Mkfile.old 96 | dkms.conf 97 | 98 | -------------------------------------------------------------------------------- /p1s.h: -------------------------------------------------------------------------------- 1 | #ifndef P1S_H_ 2 | #define P1S_H_ 3 | 4 | #if !defined(__unix__) && !defined(__APPLE__) 5 | #error This program support Unix or Unix-like OS only. 6 | #endif 7 | 8 | #if !defined(__x86_64__) && !defined(_M_AMD64) 9 | #error This program support amd64 architecture only 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | /* memory page related macros */ 17 | #define PAGE_SIZE 4096 18 | #define ROUND_TO_PAGE(X) ((((X) - 1) / PAGE_SIZE + 1 ) * PAGE_SIZE) 19 | 20 | /* memory page related functions */ 21 | void * alloc_page(size_t size); 22 | void protect_page(void * ptr, size_t size); 23 | void free_page(void * ptr, size_t size); 24 | 25 | /* code block type */ 26 | struct code_block 27 | { 28 | uint8_t * code; 29 | void * executable_code; 30 | size_t capacity; 31 | size_t length; 32 | }; 33 | 34 | /* code block related functions */ 35 | struct code_block * create_code_block(); 36 | void destroy_code_block(struct code_block * block); 37 | void * make_block_executable(struct code_block * block); 38 | 39 | /* code generation functions */ 40 | void emit(struct code_block * block, uint8_t * code, size_t length); 41 | 42 | /* code generation data */ 43 | extern uint8_t init_code[7]; 44 | extern uint8_t function_call_code[18]; 45 | extern uint8_t return_code[4]; 46 | extern uint8_t loop_begin_code[26]; 47 | extern uint8_t loop_end_code[6]; 48 | 49 | /* compilation */ 50 | int64_t * compile_text_file(FILE * f); 51 | 52 | /* common types */ 53 | typedef int64_t (*GeneratedFunction)(int64_t); 54 | typedef int64_t * (*code_generator)(struct code_block *, int64_t *); 55 | 56 | /* common functions */ 57 | int64_t incr(int64_t x); 58 | 59 | #endif // P1S_H_ 60 | 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JIT in 10 Minutes 2 | ============= 3 | 4 | Introduction 5 | ------------- 6 | This repository introduces a simple language called +1s and makes a JIT runtime for it. 7 | 8 | Prerequisites 9 | ------------- 10 | No knowledge in Compiler Design course is assumed. However you will need some basic awareness of assembly language to understand how machine code is generated. Some experience of programming in Unix like operating systems is required. 11 | 12 | You will need a POSIX-compatible x86-64 (a.k.a. amd64) operating system. All popular Linux distributions or macOS on a recent apple computer should work. All code in this repository depends on x86-64 architecture, especially the registers. Due to the significant difference in function call conventions between Windows and POSIX-compatible operating systems, it is going to be costly to make the same piece of code run under Windows. 13 | 14 | Executable Code in Memory 15 | ------------- 16 | To make our system secure, the operating system generally allow code execution only in certain memory pages. To change the access control flag, or canonically `protection flag` ( see `mprotect(2)` ). 17 | 18 | The +1s Language 19 | ------------- 20 | We need to create a simple language for this experiment. We want this language to be as simple as possible to avoid creating syntax or lexical analyzer, since they are not the point of this repository. Let's name our little funny language +1s, a language for the time accumulator. 21 | 22 | At the beginning of our program, we set the global accumulator to zero. For each `+1s` statement, we increase our accumulator by one second. For example `+1s +1s +1s +1s +1s` increases the accumulator by 5 seconds. 23 | 24 | To accumnulate a large amount of time, we need to do some crowd-funding of time. Or in other word, for-loop. The structure of for-loop looks like this. 25 | ``` 26 | N people do 27 | +1s 28 | done 29 | ``` 30 | For example if we want to repeat +1s for 5 times, another solution besides that metioned above is 31 | ``` 32 | 5 people do 33 | +1s 34 | done 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /code_generation.c: -------------------------------------------------------------------------------- 1 | #include "p1s.h" 2 | 3 | #include 4 | 5 | uint8_t init_code[7] = 6 | { 7 | 0x48, 0xc7, 0xc2, 0x00, 0x00, 0x00, 0x00, // mov $0x0, %rdx 8 | }; 9 | 10 | uint8_t function_call_code[18] = 11 | { 12 | 0x48, 0x89, 0xd7, // mov %rdx, %rdi 13 | 14 | 0x48, 0xb8, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // movabs $func, $rax 16 | // actual address of $func (8 bytes unsigned) 17 | // should be filled at offset +5 18 | 19 | 0xff, 0xd0, // callq *%rax 20 | 0x48, 0x89, 0xc2, // mov %rax, %rdx 21 | }; 22 | 23 | uint8_t return_code[4] = 24 | { 25 | 0x48, 0x89, 0xd0, // mov %rdx, %rax 26 | 0xc3 // ret 27 | }; 28 | 29 | uint8_t loop_begin_code[26] = 30 | { 31 | 0x48, 0xb9, 0x00, 0x00, 0x00, // movabsq $count,%rcx 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 33 | // actual value of $count (8 bytes signed) 34 | // should be filled at offset +2 35 | 36 | 0x51, // push %rcx 37 | 0x59, // START: pop %rcx 38 | 0x48, 0xff, 0xc9, // dec %rcx 39 | 0x48, 0x83, 0xf9, 0x00, // cmp %0x0, %rcx 40 | 41 | 0x0f, 0x8c, 0x00, 0x00, 0x00, 0x00, // jl <.END> 42 | // actual offset of .END (4 bytes signed) 43 | // should be filled at offset +21 44 | 45 | 0x51 // push %rcx 46 | }; 47 | 48 | uint8_t loop_end_code[6] = 49 | { 50 | 0xe9, 0x00, 0x00, 0x00, 0x00, // jmp <.START> 51 | // actual offset of .START (4 bytes signed) 52 | // should be filled at offset + 1 53 | 54 | 0x90 // END: nop 55 | }; 56 | 57 | 58 | void emit(struct code_block * block, uint8_t * code, size_t length) 59 | { 60 | while (block->capacity < block->length + length) 61 | { 62 | block->capacity *= 2; 63 | block->code = (uint8_t *) realloc(block->code, block->capacity); 64 | } 65 | uint8_t * head = block->code + block->length; 66 | memcpy(head, code, length); 67 | block->length += length; 68 | } 69 | -------------------------------------------------------------------------------- /compile.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "p1s.h" 7 | 8 | 9 | static inline void emit_code(int64_t ** byte_code, size_t* len, int64_t code) 10 | { 11 | *byte_code = realloc(*byte_code, (*len + 1) * sizeof(int64_t)); 12 | (*byte_code)[*len] = code; 13 | ++*len; 14 | } 15 | 16 | static inline void print_error(size_t index, const char * msg, int64_t * byte_code) 17 | { 18 | fprintf(stderr, "syntax error: %s at token %zu\n", msg, index); 19 | free(byte_code); 20 | } 21 | 22 | int64_t * compile_text_file(FILE* f) 23 | { 24 | char token[255]; 25 | char last_token[255]; 26 | int64_t * byte_code = malloc(0); 27 | size_t len = 0; 28 | size_t index = 0; 29 | int64_t nested_loop_depth = 0; 30 | while ( fscanf(f, "%255s", token) != EOF ) 31 | { 32 | ++index; 33 | if (strcmp("+1s", token) == 0) 34 | { 35 | //+1s 36 | emit_code(&byte_code, &len, 1); 37 | } 38 | else if (strcmp("people", token) == 0) 39 | { 40 | // repeat 41 | int64_t repeat_token = 0; 42 | sscanf(last_token, "%" SCNd64, &repeat_token); 43 | emit_code(&byte_code, &len, 2); 44 | emit_code(&byte_code, &len, repeat_token); 45 | 46 | ++nested_loop_depth; 47 | } 48 | else if (strcmp("do", token) == 0) 49 | { 50 | if (strcmp("people", last_token) != 0) 51 | { 52 | // no people before do 53 | print_error(index, "expect 'people' before 'do'", byte_code); 54 | return NULL; 55 | } 56 | } 57 | else if (strcmp("done", token) == 0) 58 | { 59 | // repeat end. 60 | --nested_loop_depth; 61 | if (nested_loop_depth < 0) 62 | { 63 | print_error(index, "no matching 'do' for 'done'", byte_code); 64 | return NULL; 65 | } 66 | emit_code(&byte_code, &len, 3); 67 | ++len; 68 | } 69 | strcpy(last_token, token); 70 | } 71 | if (nested_loop_depth != 0) 72 | { 73 | print_error(index, "no matching 'done' for 'do'", byte_code); 74 | return NULL; 75 | } 76 | emit_code(&byte_code, &len, -1); 77 | ++len; 78 | return byte_code; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /tier1.c: -------------------------------------------------------------------------------- 1 | #include "tier1.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "p1s.h" 10 | 11 | static code_generator loop_processor, instruction_generator; 12 | 13 | static int64_t * create_loop(struct code_block * block, int64_t * byte_code) 14 | { 15 | int start_index = block->length; 16 | emit(block, loop_begin_code, sizeof(loop_begin_code)); 17 | assert ( * (int32_t *) (block->code + start_index + 2) == 0); 18 | ++byte_code; 19 | * (int32_t *) (block->code + start_index + 2) = *byte_code; 20 | 21 | byte_code = instruction_generator(block, byte_code + 1); 22 | 23 | int end_index = block->length; 24 | emit(block, loop_end_code, sizeof(loop_end_code)); 25 | assert( *((uint8_t *) block->code + start_index + 20) == 0x8c); 26 | assert( *((uint8_t *) block->code + end_index) == 0xe9); 27 | * (int32_t *) (block->code + start_index + 21) = (int32_t) ( (end_index + 5) - (start_index + 25)); 28 | /* jmp offset = address of target instruction - address of next instruction */ 29 | * (int32_t *) (block->code + end_index + 1) = (int32_t) ((start_index + 11) - (end_index + 5) ); 30 | return byte_code; 31 | 32 | } 33 | 34 | static int64_t * generate_instructions(struct code_block * block, int64_t * byte_code) 35 | { 36 | while (1) 37 | { 38 | if (*byte_code == 1) 39 | { 40 | // +1s 41 | emit(block, function_call_code, sizeof(function_call_code)); 42 | } 43 | else if (*byte_code == 2) 44 | { 45 | // loop start 46 | byte_code = loop_processor(block, byte_code); 47 | } 48 | else if (*byte_code == 3) 49 | { 50 | // loop end 51 | return byte_code; 52 | } 53 | else if (*byte_code == -1) 54 | { 55 | // exit 56 | return byte_code; 57 | } 58 | ++byte_code; 59 | } 60 | return byte_code; 61 | } 62 | 63 | void tier1_generate_function(struct code_block * block, int64_t * byte_code) 64 | { 65 | loop_processor = &create_loop; 66 | instruction_generator = &generate_instructions; 67 | *((void **)(function_call_code + 5)) = (void *) &incr; 68 | emit(block, init_code, sizeof(init_code)); 69 | generate_instructions(block, byte_code); 70 | emit(block, return_code, sizeof(return_code)); 71 | } 72 | 73 | 74 | --------------------------------------------------------------------------------