diff --git a/jit/allocator.py b/jit/allocator.py index 7e37c44..71d6962 100644 --- a/jit/allocator.py +++ b/jit/allocator.py @@ -267,19 +267,34 @@ def __init__(self, global_allocator): # The register where the allocation pointer is stored self.register_allocation = asm.r15 + # Last available address in the heap, we must launch a gc phase when reached + self.address_end = 0 + + # Hard limit for the allocated heap + self.heap_limit = 0 + # Compile a sequence of code to initialize the allocation pointer def init_allocation_pointer(self): # We need to put a value inside the designated register address_beginning = self.global_allocator.data_address + self.global_allocator.runtime_offset + self.heap_limit = self.global_allocator.data_address + self.global_allocator.data_size + + # Due to the copying strategy of the GC, the free space is divided by two to allow copies + self.address_end = self.global_allocator.data_address + self.global_allocator.data_size//2 + encoded = asm.MOV(self.register_allocation, address_beginning).encode() self.global_allocator.code_offset = self.global_allocator.write_instruction(encoded, self.global_allocator.code_offset) + # Call a C function to allocate GC structures + stub_handler.lib.create_gc(stub_handler.ffi.cast("char*", address_beginning), stub_handler.ffi.cast("char*", self.address_end)) + # Allocate an object with a given size and return the address of the header # instructions: array of instructions # nb_words: number of 64 words to allocate + # context: compilation context, to get the stack size at this point # register: if specified, returns the result in this register, by default r10 - def allocate_object_with_size(self, instructions, nb_words, register=asm.r10): + def allocate_object_with_size(self, instructions, nb_words, context, register=asm.r10): # Save the next free address instructions.append(asm.MOV(register, self.register_allocation)) @@ -290,6 +305,39 @@ def allocate_object_with_size(self, instructions, nb_words, register=asm.r10): instructions.append(asm.ADD(self.register_allocation, size)) + # Compare with the limit of the heap + instructions.append(asm.MOV(asm.r11, self.address_end)) + instructions.append(asm.CMP(self.register_allocation, asm.r11)) + + function_address = int( + stub_handler.ffi.cast("intptr_t", stub_handler.ffi.addressof(stub_handler.lib, "gc_phase"))) + + tmp = list() + + # Save rsp + tmp.append(asm.MOV(asm.rax, asm.registers.rsp)) + tmp.append(asm.PUSH(asm.registers.rsp)) + + tmp.append(asm.MOV(asm.rdi, asm.registers.rax)) + tmp.append(asm.MOV(asm.rsi, self.register_allocation)) + tmp.append(asm.MOV(asm.rdx, context.stack_size)) + tmp.append(asm.MOV(asm.r11, function_address)) + tmp.append(asm.CALL(asm.r11)) + + # Restore rsp + tmp.append(asm.POP(asm.registers.rsp)) + + # Compute the size of these instructions + size = 0 + for el in tmp: + size += len(el.encode()) + + # Launch a gc phase if we reached the limit + instructions.append(asm.JLE(asm.operand.RIPRelativeOffset(size))) + + # Add following instructions + instructions.extend(tmp) + return register # Allocate an Object and return its pointer diff --git a/jit/compiler.py b/jit/compiler.py index 9c239b5..d4dffad 100644 --- a/jit/compiler.py +++ b/jit/compiler.py @@ -398,7 +398,7 @@ def compile_instructions(self, mfunction, block, index=0): instructions.append(asm.ADDSD(asm.xmm0, asm.xmm1)) # Allocate the space for the result of the addition - result_register = self.runtime_allocator.allocate_object_with_size(instructions, 2, reg0) + result_register = self.runtime_allocator.allocate_object_with_size(instructions, 2, context, reg0) instructions.append(asm.MOVQ(asm.operand.MemoryOperand(result_register+8), asm.xmm0)) instructions.extend(self.tags.tag_float_asm(result_register)) @@ -473,7 +473,7 @@ def compile_instructions(self, mfunction, block, index=0): # Make the addition and move the result to one of the operand instructions.append(asm.SUBSD(asm.xmm0, asm.xmm1)) - result_register = self.runtime_allocator.allocate_object_with_size(instructions, 2, reg0) + result_register = self.runtime_allocator.allocate_object_with_size(instructions, 2, context, reg0) instructions.append(asm.MOVQ(asm.operand.MemoryOperand(result_register + 8), asm.xmm0)) instructions.extend(self.tags.tag_float_asm(result_register)) diff --git a/jit/ffi_definitions.py b/jit/ffi_definitions.py index 838b0d0..b4b1d4f 100644 --- a/jit/ffi_definitions.py +++ b/jit/ffi_definitions.py @@ -6,283 +6,31 @@ # Use the CFFI to define C functions which are callable from assembly ffi = cffi.FFI() -# Define the stub_function and the callback for python -ffi.cdef(""" - // The function called by the assembly jited code to compile a given basic block - void bb_stub(uint64_t* rsp); +# First add header declarations of all functions related to stubs +with open("jit/stub.h", 'r') as f: + stub_header = f.read() - // Stub for a function compilation - void function_stub(uint64_t* rsp); +ffi.cdef(stub_header) - // Stub for type-test - void type_test_stub(uint64_t* rsp); - - // Stub for generating a class - void class_stub(uint64_t* rsp); - - // Allocate the code section with mmap and return a pointer to it - char* allocate_code_section(int); +# Then add headers for the garbage collector module +with open("jit/gc.h", 'r') as f: + gc_header = f.read() - // Allocate data section and return a pointer to it - char* allocate_data_section(int); - - // Execute the allocated JIT code - void execute_code(char*); +ffi.cdef(gc_header) - // Python function callback - extern "Python+C" void python_callback_bb_stub(uint64_t rsp); +# Then we add the source code of our C modules, first the garbage collector +with open("jit/gc.c", 'r') as f: + gc_source = f.read() - // Callback to trigger the compilation of a function - extern "Python+C" void python_callback_function_stub(uint64_t, uint64_t, uint64_t, uint64_t); +c_code = gc_source - // Callback for type tests - extern "Python+C" void python_callback_type_stub(uint64_t, int, int); +# Then the sources for the stubs-related functions +with open("jit/stub.c", 'r') as f: + stub_source = f.read() - // Callback for class creation - extern "Python+C" void python_callback_class_stub(uint64_t, uint64_t); +c_code += "\n " + stub_source - // Print the stack from the stack pointer in parameter - void print_stack(uint64_t* rsp); - // Print the array from the pointer in parameter - void print_data_section(uint64_t* array, int size); - - // Get the address of an element in a bytearray - uint64_t get_address(char* bytearray, int index); - - // Twopy general print - long int twopy_print(long int); - - // twopy lib, print one integer - int twopy_library_print_integer(int); - - // twopy lib, print one boolean - int twopy_library_print_boolean(int); - - // twopy print, print a string encoded in unicode - uint64_t twopy_library_print_string(uint64_t); - - // twopy print, default print method for an object - uint64_t twopy_library_print_object(uint64_t); - - // twopy print, default print method for a float - uint64_t twopy_library_print_float(uint64_t); - - // Print an error and exit - void twopy_error(int); - """) - -c_code = """ - #include - #include - #include - - // Function called to handle the compilation of stubs for basic blocks - static void python_callback_bb_stub(uint64_t rsp); - - static void python_callback_function_stub(uint64_t, uint64_t, uint64_t, uint64_t); - - static void python_callback_type_stub(uint64_t, int, int); - - static void python_callback_class_stub(uint64_t, uint64_t); - - char* allocate_code_section(int size) - { - char* res = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - return res; - } - - char* allocate_data_section(int size) - { - char* res = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - return res; - } - - void execute_code(char* code_address) - { - ((void (*)())code_address)(); - } - - void bb_stub(uint64_t* rsp) - { - python_callback_bb_stub(rsp[-2]); - } - - // Handle the compilation of a function's stub - void function_stub(uint64_t* rsp) - { - uint64_t* code_address = (uint64_t*)rsp[-2]; - - // Get the two values after the stub - int nbargs = (int)code_address[0]; - uint64_t return_address = rsp[0]; - - // Read values on the stack - // For now consider we have just the name and code id - long int name_id = rsp[1]; - long int code_id = rsp[2]; - - //TODO: handle free variables list - if(nbargs > 2) - ; - - // Callback to python to trigger the compilation of the function - python_callback_function_stub(name_id, code_id, return_address, rsp[3]); - } - - // Handle compilation of a type-test stub - void type_test_stub(uint64_t* rsp) - { - // Get the return address - long int return_address = rsp[-2]; - - long int return_address_aligned = return_address & -16; - - uint64_t* code_address = (uint64_t*)rsp[-2]; - - int id_variable = (int)code_address[0]; - int type_value = (int)code_address[1]; - - python_callback_type_stub(return_address_aligned, id_variable, type_value); - } - - void class_stub(uint64_t* rsp) - { - python_callback_class_stub(rsp[-1], rsp[0]); - } - - void print_stack(uint64_t* rsp) - { - printf("Print the stack\\n"); - for(int i=-1; i!=7; i++) - printf("\\t 0x%lx stack[%d] = 0x%lx\\n", (long int)&rsp[i], i, rsp[i]); - } - - void print_data_section(uint64_t* array, int size) - { - printf("Print the array\\n"); - for(int i=0; i!=size; i++) - printf("\\t %ld array[%d] = %ld\\n", (long int)&array[i], i, array[i]); - } - - uint64_t get_address(char* bytearray, int index) - { - return (uint64_t)&bytearray[index]; - } - - // Print one integer on stdout - int twopy_library_print_integer(long int value) - { - // Remove the integer tag for the print - printf("%ld\\n", value>>3); - - return value; - } - - // Print the representation of a boolean - int twopy_library_print_boolean(int value) - { - // Remove the tag for the print - if(value == 2) - printf("False\\n"); - else - printf("True\\n"); - - return value; - } - - uint64_t twopy_library_print_string(uint64_t value) - { - // Remove the tag to get the address of the object - uint64_t untag_address = value >> 3; - - // Get the size in the header - int size = ((uint64_t*)untag_address)[0]; - - // Create the pointer on the value - char* chars_array = ((char*)untag_address + 8); - - // Print characters one by one, the UTF-8 encoding will be automatically displayed - for(int i=0; i> 3; - - // Get the class address in the object after the header - uint64_t* class_address = ((uint64_t*)untag_address)+1; - - uint64_t class_structure = *(class_address); - - // Get the strings containing the file and class name - uint64_t file_name = *(((uint64_t*)class_structure)+2); - uint64_t class_name = *(((uint64_t*)class_structure)+3); - - printf("<"); - twopy_library_print_string(file_name); - printf("."); - twopy_library_print_string(class_name); - - // then print the address - printf(" object at 0x%lx>\\n", untag_address); - return value; - } - - // twopy print, default print method for a float - uint64_t twopy_library_print_float(uint64_t value) - { - uint64_t untag_address = value >> 3; - - // Get the double value encoded with IEEE-754 on 64 bits - double double_value = ((double*)untag_address)[1]; - - // Print the double value - printf("%f\\n", double_value); - - return value; - } - - long int twopy_print(long int value) - { - // Test the tag of the object - int tag = (int)value & 7; - - if(tag == 2) - return twopy_library_print_boolean(value); - else if(tag == 0) - return twopy_library_print_integer(value); - else if(tag == 4) - return twopy_library_print_object(value); - else if(tag == 5) - return twopy_library_print_float(value); - else if(tag == 6) - { - uint64_t res = twopy_library_print_string(value); - // Print a newline as requested by python - printf("\\n"); - - return res; - } - else - printf("ERROR: unknown value %ld\\n", value); - - return value; - } - - void twopy_error(int error_code) - { - if(error_code == 1) - printf("ERROR: overflow \\n"); - else - printf("ERROR: %d\\n", error_code); - - exit(-1); - } - """ # C Sources ffi.set_source("stub_module", c_code) diff --git a/jit/gc.c b/jit/gc.c new file mode 100644 index 0000000..a284f09 --- /dev/null +++ b/jit/gc.c @@ -0,0 +1,127 @@ +#include "gc.h" + + +struct heap +{ + // Address of the beginning of the free space in the heap + char* fromspace; + + // Address for the GC algorithm to copy objects + char* tospace; +}; + +struct heap* myHeap; + +// Create and initialize a new heap structure +void create_gc(char* beginning_address, char* end_address) +{ + // Initialize GC structures with the heap beginning and end + if(myHeap == NULL) + { + printf("Begin %p\n", beginning_address); + printf("end %p\n", end_address); + + // Create a new heap structure + myHeap = (struct heap*) malloc(sizeof(struct heap*)); + myHeap->fromspace = beginning_address; + myHeap->tospace = end_address; + } +} + +// Copy this object in a new space +uint64_t copy_root(uint64_t object, char** allocPtr) +{ + // TODO: forwarding addresses + /*If o has no forwarding address + o' = allocPtr + allocPtr = allocPtr + size(o) + copy the contents of o to o' + forwarding-address(o) = o' + EndIf + return forwarding-address(o)*/ + + // If o has no forwarding address + char* new_object = *allocPtr; + + // Dereference the root to access its header and content + uint64_t* untag_address = object >> 3; + int size = untag_address[0] + 8; + + // Copy the header and the object to the new space + memcpy(new_object, untag_address, size); + *allocPtr = *allocPtr + size; + + // Put the forwarding pointer in the old position of this object + untag_address[0] = new_object; + + // Return the new position, with a tag + uint64_t new_address = ((uint64_t)new_object)<<3; + return new_address; +} + +// Launch a gc phase +void gc_phase(uint64_t* rsp, uint64_t* register_allocation, int stack_size) +{ + printf("fromspace %p\n", myHeap->fromspace); + printf("tospace %p\n", myHeap->tospace); + + printf("Current allocation register in the beginning %p\n", register_allocation); + + // Swap semi spaces + char *tmp = myHeap->fromspace; + myHeap->fromspace = myHeap->tospace; + myHeap->tospace = tmp; + + // Use these two pointers to scan the heap + char* allocPtr = myHeap->fromspace; + char* scanPtr = myHeap->fromspace; + + // Allocate some size for roots + uint64_t roots[stack_size]; + int index = 0; + + // Scan every root in the stack + for(int i=0; i +#include +#include + + +// Function called to handle the compilation of stubs for basic blocks +static void python_callback_bb_stub(uint64_t rsp); + +static void python_callback_function_stub(uint64_t, uint64_t, uint64_t, uint64_t); + +static void python_callback_type_stub(uint64_t, int, int); + +static void python_callback_class_stub(uint64_t, uint64_t); + +char* allocate_code_section(int size) +{ + char* res = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + return res; +} + +char* allocate_data_section(int size) +{ + char* res = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + return res; +} + +void execute_code(char* code_address) +{ + ((void (*)())code_address)(); +} + +void bb_stub(uint64_t* rsp) +{ + python_callback_bb_stub(rsp[-2]); +} + +// Handle the compilation of a function's stub +void function_stub(uint64_t* rsp) +{ + uint64_t* code_address = (uint64_t*)rsp[-2]; + + // Get the two values after the stub + int nbargs = (int)code_address[0]; + uint64_t return_address = rsp[0]; + + // Read values on the stack + // For now consider we have just the name and code id + long int name_id = rsp[1]; + long int code_id = rsp[2]; + + //TODO: handle free variables list + if(nbargs > 2) + ; + + // Callback to python to trigger the compilation of the function + python_callback_function_stub(name_id, code_id, return_address, rsp[3]); +} + +// Handle compilation of a type-test stub +void type_test_stub(uint64_t* rsp) +{ + // Get the return address + long int return_address = rsp[-2]; + + long int return_address_aligned = return_address & -16; + + uint64_t* code_address = (uint64_t*)rsp[-2]; + + int id_variable = (int)code_address[0]; + int type_value = (int)code_address[1]; + + python_callback_type_stub(return_address_aligned, id_variable, type_value); +} + +void class_stub(uint64_t* rsp) +{ + python_callback_class_stub(rsp[-1], rsp[0]); +} + +void print_stack(uint64_t* rsp) +{ + printf("Print the stack\n"); + for(int i=-1; i!=7; i++) + printf("\t 0x%lx stack[%d] = 0x%lx\n", (long int)&rsp[i], i, rsp[i]); +} + +void print_data_section(uint64_t* array, int size) +{ + printf("Print the array\n"); + for(int i=0; i!=size; i++) + printf("\t %ld array[%d] = %ld\n", (long int)&array[i], i, array[i]); +} + +uint64_t get_address(char* bytearray, int index) +{ + return (uint64_t)&bytearray[index]; +} + +// Print one integer on stdout +int twopy_library_print_integer(long int value) +{ + // Remove the integer tag for the print + printf("%ld\n", value>>3); + + return value; +} + +// Print the representation of a boolean +int twopy_library_print_boolean(int value) +{ + // Remove the tag for the print + if(value == 2) + printf("False\n"); + else + printf("True\n"); + + return value; +} + +uint64_t twopy_library_print_string(uint64_t value) +{ + // Remove the tag to get the address of the object + uint64_t untag_address = value >> 3; + + // Get the size in the header + int size = ((uint64_t*)untag_address)[0]; + + // Create the pointer on the value + char* chars_array = ((char*)untag_address + 8); + + // Print characters one by one, the UTF-8 encoding will be automatically displayed + for(int i=0; i> 3; + + // Get the class address in the object after the header + uint64_t* class_address = ((uint64_t*)untag_address)+1; + + uint64_t class_structure = *(class_address); + + // Get the strings containing the file and class name + uint64_t file_name = *(((uint64_t*)class_structure)+2); + uint64_t class_name = *(((uint64_t*)class_structure)+3); + + printf("<"); + twopy_library_print_string(file_name); + printf("."); + twopy_library_print_string(class_name); + + // then print the address + printf(" object at 0x%lx>\n", untag_address); + return value; +} + +// twopy print, default print method for a float +uint64_t twopy_library_print_float(uint64_t value) +{ + uint64_t untag_address = value >> 3; + + // Get the double value encoded with IEEE-754 on 64 bits + double double_value = ((double*)untag_address)[1]; + + // Print the double value + printf("%f\n", double_value); + + return value; +} + +long int twopy_print(long int value) +{ + // Test the tag of the object + int tag = (int)value & 7; + + if(tag == 2) + return twopy_library_print_boolean(value); + else if(tag == 0) + return twopy_library_print_integer(value); + else if(tag == 4) + return twopy_library_print_object(value); + else if(tag == 5) + return twopy_library_print_float(value); + else if(tag == 6) + { + uint64_t res = twopy_library_print_string(value); + // Print a newline as requested by python + printf("\n"); + + return res; + } + else + printf("ERROR: unknown value %ld\n", value); + + return value; +} + +void twopy_error(int error_code) +{ + if(error_code == 1) + printf("ERROR: overflow \n"); + else + printf("ERROR: %d\n", error_code); + + exit(-1); +} + diff --git a/jit/stub.h b/jit/stub.h new file mode 100644 index 0000000..d433467 --- /dev/null +++ b/jit/stub.h @@ -0,0 +1,64 @@ +/* This file contains all declarations for cffi.cdef */ + +// The function called by the assembly jited code to compile a given basic block +void bb_stub(uint64_t* rsp); + +// Stub for a function compilation +void function_stub(uint64_t* rsp); + +// Stub for type-test +void type_test_stub(uint64_t* rsp); + +// Stub for generating a class +void class_stub(uint64_t* rsp); + +// Allocate the code section with mmap and return a pointer to it +char* allocate_code_section(int); + +// Allocate data section and return a pointer to it +char* allocate_data_section(int); + +// Execute the allocated JIT code +void execute_code(char*); + +// Python function callback +extern "Python+C" void python_callback_bb_stub(uint64_t rsp); + +// Callback to trigger the compilation of a function +extern "Python+C" void python_callback_function_stub(uint64_t, uint64_t, uint64_t, uint64_t); + +// Callback for type tests +extern "Python+C" void python_callback_type_stub(uint64_t, int, int); + +// Callback for class creation +extern "Python+C" void python_callback_class_stub(uint64_t, uint64_t); + +// Print the stack from the stack pointer in parameter +void print_stack(uint64_t* rsp); + +// Print the array from the pointer in parameter +void print_data_section(uint64_t* array, int size); + +// Get the address of an element in a bytearray +uint64_t get_address(char* bytearray, int index); + +// Twopy general print +long int twopy_print(long int); + +// twopy lib, print one integer +int twopy_library_print_integer(int); + +// twopy lib, print one boolean +int twopy_library_print_boolean(int); + +// twopy print, print a string encoded in unicode +uint64_t twopy_library_print_string(uint64_t); + +// twopy print, default print method for an object +uint64_t twopy_library_print_object(uint64_t); + +// twopy print, default print method for a float +uint64_t twopy_library_print_float(uint64_t); + +// Print an error and exit +void twopy_error(int); diff --git a/jit/stub_handler.py b/jit/stub_handler.py index 3af3888..f36d2f0 100644 --- a/jit/stub_handler.py +++ b/jit/stub_handler.py @@ -697,8 +697,6 @@ def encode_instructions(self, instructions): self.dict_stubs[return_address] = instruction self.dict_stubs_position[return_address] = old_position - #TODO: False branch of the test - for ins in self.false_branch: self.mfunction.allocator.encode(ins) @@ -712,7 +710,6 @@ def encode_instructions(self, instructions): self.mfunction.allocator.encode(instruction) self.dict_stubs[return_address] = instruction self.dict_stubs_position[return_address] = old_position - #TODO: false branch, default case # Encode a stub to continue the test def encode_stub_test(self, branch, label, type_value): diff --git a/tests/fib_fp.py b/tests/fib_fp.py index 72c827d..a6e79ad 100644 --- a/tests/fib_fp.py +++ b/tests/fib_fp.py @@ -5,4 +5,4 @@ def fib_fp(n): return fib_fp(n-1.0) + fib_fp(n-2.0) -print(fib_fp(35.0)) +print(fib_fp(38.0))