def allocate_object_with_size(self, instructions, nb_words, register=asm.r10): # Save the next free address instructions.append(asm.MOV(register, self.register_allocation)) # Increment the dynamic allocator size = nb_words * 8 instructions.append(asm.MOV(asm.operand.MemoryOperand(self.register_allocation), size-8)) instructions.append(asm.ADD(self.register_allocation, size)) return register
def compile_function_stub(self, mfunction, nbargs, address_after): # Now encode the stub stub_label = asm.Label("Stub_label_" + str(mfunction.name)) # The call to that will be compiled after the stub compilation is over address = mfunction.allocator.encode_stub(asm.LABEL(stub_label)) # Save the association mfunction.allocator.jump_labels[address] = asm.LABEL(stub_label) # Save the rsp for the stub mfunction.allocator.encode_stub(asm.MOV(asm.rdi, asm.registers.rsp)) # Call the stub function in C function_address = int( ffi.cast("intptr_t", ffi.addressof(lib, "function_stub"))) # Align the stack on 16 bits mfunction.allocator.encode_stub(asm.MOV(asm.rax, asm.registers.rsp)) mfunction.allocator.encode_stub(asm.AND(asm.registers.rsp, -16)) mfunction.allocator.encode_stub(asm.PUSH(asm.registers.rsp)) mfunction.allocator.encode_stub(asm.MOV(asm.r10, function_address)) mfunction.allocator.encode_stub(asm.CALL(asm.r10)) # Now put additional informations for the stub # Force min 8 bits encoding for this value nbargs_bytes = encode_bytes(nbargs) stub_function = StubFunction() self.stub_dictionary[address_after] = stub_function self.data_addresses[ address_after] = jitcompiler_instance.global_allocator.stub_offset address_after_bytes = address_after.to_bytes( address_after.bit_length(), "little") # Write after the stub jitcompiler_instance.global_allocator.stub_offset = jitcompiler_instance.global_allocator.write_instruction( nbargs_bytes, jitcompiler_instance.global_allocator.stub_offset) jitcompiler_instance.global_allocator.stub_offset = jitcompiler_instance.global_allocator.write_instruction( address_after_bytes, jitcompiler_instance.global_allocator.stub_offset) for i in range(5): mfunction.allocator.encode_stub(asm.NOP()) return address
def compile_error_stub(self, error_code): if not self.stub_error: self.stub_error = StubError() address = jitcompiler_instance.global_allocator.encode_stub( asm.MOV(asm.rdi, error_code)) function_address = int( ffi.cast("intptr_t", ffi.addressof(lib, "twopy_error"))) jitcompiler_instance.global_allocator.encode_stub( asm.MOV(asm.r10, function_address)) jitcompiler_instance.global_allocator.encode_stub(asm.CALL(asm.r10)) return address
def encode_stub_test(self, branch, label, type_value): # Giving rsp to C function address = self.mfunction.allocator.encode_stub( asm.MOV(asm.rdi, asm.registers.rsp)) # Save the association self.mfunction.allocator.jump_labels[address] = label # Call to C to compile the block reg_id = asm.r10 function_address = int( ffi.cast("intptr_t", ffi.addressof(lib, "type_test_stub"))) # Align the stack on 16 bits self.mfunction.allocator.encode_stub( asm.MOV(asm.rax, asm.registers.rsp)) self.mfunction.allocator.encode_stub(asm.PUSH(asm.registers.rsp)) self.mfunction.allocator.encode_stub(asm.MOV(reg_id, function_address)) self.mfunction.allocator.encode_stub(asm.CALL(reg_id)) # Compute the return address to link this stub to self return_address = lib.get_address( ffi.from_buffer( jitcompiler_instance.global_allocator.code_section), jitcompiler_instance.global_allocator.stub_offset) return_address = return_address & -16 # Associate this return address to self in the stub_handler stubhandler_instance.stub_dictionary[return_address] = self variable_id = encode_bytes(self.variable) type_bytes = encode_bytes(type_value) offset = jitcompiler_instance.global_allocator.stub_offset stubhandler_instance.data_addresses[return_address] = offset jitcompiler_instance.global_allocator.stub_offset = jitcompiler_instance.global_allocator.write_instruction( variable_id, offset) jitcompiler_instance.global_allocator.stub_offset = jitcompiler_instance.global_allocator.write_instruction( type_bytes, jitcompiler_instance.global_allocator.stub_offset) # Saving some space for the cleaning for i in range(5): self.mfunction.allocator.encode_stub(asm.NOP()) return return_address
def compile_class_stub(self, mfunction): # Push on the stack the address of the class' stub c_buffer = ffi.from_buffer( jitcompiler_instance.global_allocator.code_section) address_stub = lib.get_address( c_buffer, jitcompiler_instance.global_allocator.stub_offset) mfunction.allocator.encode(asm.MOV(asm.r10, address_stub)) mfunction.allocator.encode(asm.PUSH(asm.r10)) # Save the address after the PUSH to be able to jump there later c_buffer = ffi.from_buffer( jitcompiler_instance.global_allocator.code_section) address_code = lib.get_address( c_buffer, jitcompiler_instance.global_allocator.code_offset) # Be able to find them in the collection during the later callback self.class_stub_addresses.append(address_stub) # Call the stub function mfunction.allocator.encode_stub(asm.MOV(asm.rdi, asm.registers.rsp)) function_address = int( ffi.cast("intptr_t", ffi.addressof(lib, "class_stub"))) mfunction.allocator.encode_stub(asm.MOV(asm.r10, function_address)) mfunction.allocator.encode_stub(asm.CALL(asm.r10)) offset = jitcompiler_instance.global_allocator.stub_offset return_address = lib.get_address( ffi.from_buffer( jitcompiler_instance.global_allocator.code_section), jitcompiler_instance.global_allocator.stub_offset) stub = StubClass() stub.data_address = offset stub.return_address = address_code # Indicate this offset correspond to the "return address" on the stub after the call to C returned self.data_addresses[return_address] = offset self.stub_dictionary[return_address] = stub # Reserve some space for the cleanup for i in range(5): mfunction.allocator.encode_stub(asm.NOP())
def compile_stub(self, mfunction, stub): # The call to that will be compiled after the stub compilation is over stub_label = "Stub_label_" + str(id(stub)) # Now we store the stack pointer to patch it later address = mfunction.allocator.encode_stub( asm.MOV(asm.rdi, asm.registers.rsp)) # Save the association mfunction.allocator.jump_labels[address] = stub_label reg_id = asm.r10 function_address = int( ffi.cast("intptr_t", ffi.addressof(lib, "bb_stub"))) # Align the stack on 16 bits mfunction.allocator.encode_stub(asm.MOV(asm.rax, asm.registers.rsp)) mfunction.allocator.encode_stub(asm.PUSH(asm.registers.rsp)) mfunction.allocator.encode_stub(asm.MOV(reg_id, function_address)) mfunction.allocator.encode_stub(asm.CALL(reg_id)) # Save the offset offset = jitcompiler_instance.global_allocator.stub_offset return_address = lib.get_address( ffi.from_buffer( jitcompiler_instance.global_allocator.code_section), jitcompiler_instance.global_allocator.stub_offset) # Indicate this offset correspond to the "return address" on the stub after the call to C returned self.data_addresses[return_address] = offset self.stub_dictionary[return_address] = stub # Save some space for cleaning instructions for i in range(15): mfunction.allocator.encode_stub(asm.NOP()) return address
def is_float_asm(self, register): instructions = list() instructions.append(asm.MOV(asm.r12, register)) instructions.append(asm.AND(asm.r12, Tags.Float)) instructions.append(asm.CMP(asm.r12, Tags.Float)) return instructions
def clean(self, return_address): instructions = [] instructions.append(asm.POP(asm.registers.rsp).encode()) instructions.append(asm.MOV(asm.rax, return_address).encode()) instructions.append(asm.JMP(asm.rax).encode()) offset = self.data_address for i in instructions: offset = jitcompiler_instance.global_allocator.write_instruction( i, offset)
def clean(self, class_address, address_class_function): instructions = [] # Now push the function address instructions.append(asm.MOV(asm.rax, class_address)) instructions.append(asm.PUSH(asm.rax)) instructions.append(asm.MOV(asm.r10, address_class_function)) instructions.append(asm.CALL(asm.r10)) # Clean the stack instructions.append(asm.ADD(asm.registers.rsp, 32)) # Finally, jump to the correct destination instructions.append(asm.MOV(asm.rax, class_address)) instructions.append(asm.MOV(asm.r11, self.return_address)) instructions.append(asm.JMP(asm.r11)) offset = self.data_address for i in instructions: offset = jitcompiler_instance.global_allocator.write_instruction( i.encode(), offset)
def clean(self, return_address, function_address, canary_value=None): instructions = [] # restore rsp instructions.append(asm.POP(asm.registers.rsp).encode()) if canary_value in stubhandler_instance.class_stub_addresses: instructions.append(asm.ADD(asm.registers.rsp, 32).encode()) else: # Discard the three top values on the stack instructions.append(asm.ADD(asm.registers.rsp, 24).encode()) # Now push the function address instructions.append(asm.MOV(asm.rax, function_address).encode()) instructions.append(asm.PUSH(asm.rax).encode()) # Finally, jump to the correct destination instructions.append(asm.MOV(asm.rax, return_address).encode()) instructions.append(asm.JMP(asm.rax).encode()) offset = self.data_address for i in instructions: offset = jitcompiler_instance.global_allocator.write_instruction( i, offset)
def compile_absolute_jump(self, mfunction, block): stub_label = "Stub_label_jump" + str(id(block)) old_code_offset = jitcompiler_instance.global_allocator.code_offset c_buffer = ffi.from_buffer( jitcompiler_instance.global_allocator.code_section) address = lib.get_address( c_buffer, jitcompiler_instance.global_allocator.stub_offset) jump_instruction = asm.MOV(asm.r10, address) mfunction.allocator.encode(jump_instruction) mfunction.allocator.encode(asm.JMP(asm.r10)) # Now create the stub stub = StubBB(block, jump_instruction, old_code_offset) self.compile_stub(mfunction, stub)
def compile_bb_stub(self, mfunction, true_block, false_block, test_instruction, true_instructions=None): # Save both offsets old_stub_offset = jitcompiler_instance.global_allocator.stub_offset old_code_offset = jitcompiler_instance.global_allocator.code_offset # And update the dictionary of ids and blocks # Compute the offset to the stub, by adding the size of the JL instruction offset = old_stub_offset - old_code_offset peachpy_instruction = test_instruction( asm.operand.RIPRelativeOffset(offset - 6)) mfunction.allocator.encode(peachpy_instruction) # Compile a stub for each branch jump_stub = StubBB(true_block, peachpy_instruction, old_code_offset) # If any, add some custom instructions for the true branch of the test jump_stub.instructions_before = true_instructions self.compile_stub(mfunction, jump_stub) # For now, jump to the newly compiled stub, # This code will be patched later old_code_offset = jitcompiler_instance.global_allocator.code_offset # Compute the address of the false block stub c_buffer = ffi.from_buffer( jitcompiler_instance.global_allocator.code_section) address_false = lib.get_address( c_buffer, jitcompiler_instance.global_allocator.stub_offset) peachpy_instruction = asm.MOV(asm.r10, address_false) mfunction.allocator.encode(peachpy_instruction) mfunction.allocator.encode(asm.JMP(asm.r10)) # We store the MOV into the register as the jumping instruction, we just need to patch this notjump_stub = StubBB(false_block, peachpy_instruction, old_code_offset) self.compile_stub(mfunction, notjump_stub)
def is_int_asm(self, register): # 7FFF FFFF FFFF FFFF max value for a 64 bits signed integer instructions = [] # Collect stats if needed if self.jit.interpreter.args.stats: # +1 for each type-check executed instructions.append(asm.ADD(self.jit.register_stats, 1)) # Copy the value inside a new register instructions.append(asm.MOV(asm.r12, register)) test_value = 0b111 # Now compare instructions.append(asm.AND(asm.r12, test_value)) # The result should be 0 if we have an int instructions.append(asm.CMP(asm.r12, 0)) # Make the jumps according to the result return instructions
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 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)
def allocate_instance(self, class_address, init_function, class_function): # Save the address of the initializer definition c_buffer = stub_handler.ffi.from_buffer(self.global_allocator.code_section) init_code_address = stub_handler.lib.get_address(c_buffer, self.global_allocator.code_offset) self.global_allocator.jitcompiler.initializer_addresses[class_function.name] = init_code_address instructions = list() # Move the next available address into rax to return it instructions.append(asm.MOV(asm.rax, self.register_allocation)) # Construct the header with the size of the object size = 2 instructions.append(asm.MOV(asm.operand.MemoryOperand(asm.r15), size)) # Put a pointer to the class address in the second field instructions.append(asm.MOV(asm.r10, class_address)) instructions.append(asm.MOV(asm.operand.MemoryOperand(self.register_allocation + 8), asm.r10)) # Increment the allocation pointer instructions.append(asm.ADD(self.register_allocation, 8 * 5)) # Finally, tag the address inside rax tag_instructions = self.global_allocator.jitcompiler.tags.tag_object_asm(asm.rax) instructions.extend(tag_instructions) # Now call the __init__() method of the class if any # TODO: handle __init__ with more than 1 parameters if init_function is not None: init_offset = 4 # Save the return address of the current call instructions.append(asm.POP(asm.r9)) # Saving parameter instructions.append(asm.POP(asm.r8)) # Depop the class address instructions.append(asm.ADD(asm.registers.rsp, 8)) # TODO: problem stack size instructions.append(asm.PUSH(asm.r9)) # Push back object and parameters instructions.append(asm.PUSH(asm.rax)) instructions.append(asm.PUSH(asm.r8)) # Make the call to init instructions.append(asm.ADD(asm.r10, 8 * init_offset)) instructions.append(asm.CALL(asm.operand.MemoryOperand(asm.r10))) # Saving return address in a register instructions.append(asm.POP(asm.r9)) instructions.append(asm.JMP(asm.r9)) offset = self.global_allocator.code_offset for i in instructions: offset = self.global_allocator.write_instruction(i.encode(), offset) self.global_allocator.code_offset = offset
def binary_type_check(self, mfunction, block, context): # Try to retrieve information on the context stack x_register = asm.r13 y_register = asm.r14 if self.jit.interpreter.args.maxvers == 0: # BBV is deactivated, replace type values with unknow in the virtual stack context.variable_types[0] = Types.Unknown context.variable_types[1] = Types.Unknown new_tuple0 = (context.stack[-1][0], context.variable_types[0]) new_tuple1 = (context.stack[-2][0], context.variable_types[1]) context.stack[-1] = new_tuple0 context.stack[-2] = new_tuple1 else: # Try to retrieve information on types in the context context.variable_types[0] = context.stack[-1][1] if context.variable_types[0] == Types.Unknown: # Try to see in context.variables_dict if context.stack[-1][0] in context.variable_dict: context.variable_types[0] = context.variable_dict[ context.stack[-1][0]] context.variable_types[1] = context.stack[-2][1] if context.variable_types[1] == Types.Unknown: if context.stack[-2][0] in context.variable_dict: context.variable_types[1] = context.variable_dict[ context.stack[-2][0]] context.variables_allocation[0] = x_register context.variables_allocation[1] = y_register # If we have static information on these types, we will compile a stub to the next block if context.variable_types[ 0] != Types.Unknown and context.variable_types[ 1] != Types.Unknown: # Update the stack and directly compile the next block # Construct two new tuples to fill the context with up to date information new_tuple0 = (context.stack[-1][0], context.variable_types[0]) new_tuple1 = (context.stack[-2][0], context.variable_types[1]) context.stack[-1] = new_tuple0 context.stack[-2] = new_tuple1 # We should have only one block after assert len(block.next) == 1 for el in block.next: self.jit.compile_instructions(mfunction, el) else: # Move values into registers and keep them on the stack until the end of the test instructions = [] instructions.append( asm.MOV(x_register, asm.operand.MemoryOperand(asm.registers.rsp))) instructions.append( asm.MOV(y_register, asm.operand.MemoryOperand(asm.registers.rsp + 8))) # Generate a test for the first variable test_instructions = self.is_int_asm(x_register) instructions.extend(test_instructions) # Code for true and false branches true_branch = self.is_int_asm(y_register) false_branch = self.is_float_asm(x_register) # Indicate which operand has to be tested id_var = 0 if context.variable_types[0] != Types.Unknown: id_var = 1 stub = stub_handler.StubType(mfunction, instructions, true_branch, false_branch, id_var, context) next_block = None for el in block.next: next_block = el # Indicate to the stub, which block must be compiled after the resolution of the test stub.following_block(next_block)
def patch_instruction(self, first_offset): if isinstance(self.instruction, asm.MOV): # Moving an address inside a register, we need to change the address here # If the MOVE + JUMP is supposed to go just after, put NOP instead diff = first_offset - self.position if diff > 0 and diff <= 13: nop = asm.NOP().encode() for i in range(diff): jitcompiler_instance.global_allocator.write_instruction( nop, self.position) self.position += 1 else: # It's a real jump, just compile it and patch the code new_address = lib.get_address( ffi.from_buffer( jitcompiler_instance.global_allocator.code_section), first_offset) new_instruction = asm.MOV(self.instruction.operands[0], new_address) # Create the new encoded instruction and replace the old one in the code section encoded = new_instruction.encode() jitcompiler_instance.global_allocator.write_instruction( encoded, self.position) elif isinstance(self.instruction, asm.JG): new_operand = first_offset - self.position - len( self.instruction.encode()) # Update to the new position new_instruction = asm.JG( asm.operand.RIPRelativeOffset(new_operand)) encoded = new_instruction.encode() # If the previous instruction was a 32 bits offset, force it to the new one if len(self.instruction.encode()) > 2: encoded = bytearray(len(self.instruction.encode())) # Force the 32 encoding of the JG instruction encoded[0] = 0x0F encoded[1] = 0x8F encoded[2] = 0 encoded[3] = 0 encoded[4] = 0 encoded[5] = 0 size = custom_ceil(new_operand / 255) bytes = new_operand.to_bytes(size, 'little') for i in range(0, len(bytes)): encoded[i + 2] = bytes[i] jitcompiler_instance.global_allocator.write_instruction( encoded, self.position) elif isinstance(self.instruction, asm.JGE): new_operand = first_offset - self.position - len( self.instruction.encode()) # Update to the new position new_instruction = asm.JGE( asm.operand.RIPRelativeOffset(new_operand)) encoded = new_instruction.encode() # If the previous instruction was a 32 bits offset, force it to the new one if len(encoded) != 6 and len(self.instruction.encode()) > 2: encoded = bytearray(6) # Force the 32 encoding of the JGE encoded[0] = 0x0F encoded[1] = 0x8D encoded[2] = 0 encoded[3] = 0 encoded[4] = 0 encoded[5] = 0 # Keep the same value for the jump size = custom_ceil(new_operand / 255) bytes = new_operand.to_bytes(size, 'little') for i in range(0, len(bytes)): encoded[i + 2] = bytes[i] jitcompiler_instance.global_allocator.write_instruction( encoded, self.position) elif isinstance(self.instruction, asm.JE): new_operand = first_offset - self.position - len( self.instruction.encode()) # Update to the new position new_instruction = asm.JE( asm.operand.RIPRelativeOffset(new_operand)) encoded = new_instruction.encode() # If the previous instruction was a 32 bits offset, force it to the new one if len(self.instruction.encode()) > 2: encoded = bytearray(len(self.instruction.encode())) # Force the 32 encoding of the JE instruction encoded[0] = 0x0F encoded[1] = 0x84 encoded[2] = 0 encoded[3] = 0 encoded[4] = 0 encoded[5] = 0 size = custom_ceil(new_operand / 255) bytes = new_operand.to_bytes(size, 'little') for i in range(0, len(bytes)): encoded[i + 2] = bytes[i] jitcompiler_instance.global_allocator.write_instruction( encoded, self.position) elif isinstance(self.instruction, asm.JL): new_operand = first_offset - self.position - len( self.instruction.encode()) new_instruction = asm.JL( asm.operand.RIPRelativeOffset(new_operand)) encoded = new_instruction.encode() # If the previous instruction was a 32 bits offset, force the same length for the new one if len(self.instruction.encode()) > 2 and len(encoded) != 6: encoded = bytearray(len(self.instruction.encode())) # Force the 32 encoding of the JL instruction encoded[0] = 0x0F encoded[1] = 0x8C encoded[2] = 0 encoded[3] = 0 encoded[4] = 0 encoded[5] = 0 size = custom_ceil(new_operand / 255) bytes = None if size < 0 or new_operand < 0: size = 4 bytes = new_operand.to_bytes(size, 'little', signed=True) else: bytes = new_operand.to_bytes(size, 'little') for i in range(0, len(bytes)): encoded[i + 2] = bytes[i] jitcompiler_instance.global_allocator.write_instruction( encoded, self.position) elif isinstance(self.instruction, asm.JLE): new_operand = first_offset - self.position - len( self.instruction.encode()) # Update to the new position new_instruction = asm.JLE( asm.operand.RIPRelativeOffset(new_operand)) encoded = new_instruction.encode() # If the previous instruction was a 32 bits offset, force it to the new one if len(encoded) != 6 and len(self.instruction.encode()) > 2: encoded = bytearray(6) # Force the 32 encoding of the JGE encoded[0] = 0x0F encoded[1] = 0x8E encoded[2] = 0 encoded[3] = 0 encoded[4] = 0 encoded[5] = 0 # Keep the same value for the jump size = custom_ceil(new_operand / 255) bytes = new_operand.to_bytes(size, 'little') for i in range(0, len(bytes)): encoded[i + 2] = bytes[i] jitcompiler_instance.global_allocator.write_instruction( encoded, self.position) elif isinstance(self.instruction, asm.JNE): new_operand = first_offset - self.position - len( self.instruction.encode()) if new_operand < 255: # We will encode the instruction new_operand = first_offset - self.position - 2 new_instruction = asm.JNE( asm.operand.RIPRelativeOffset(new_operand)) encoded = new_instruction.encode() # Need to add some NOP instruction to fill the space left from the previous longer instruction if len(encoded) < len(self.instruction.encode()): diff = len(self.instruction.encode()) - len(encoded) for i in range(diff): encoded += asm.NOP().encode() else: # If the previous instruction was a 32 bits offset, force the same length for the new one encoded = bytearray(len(self.instruction.encode())) # Force the 32 encoding of the JNE instruction encoded[0] = 0x0F encoded[1] = 0x85 encoded[2] = 0 encoded[3] = 0 encoded[4] = 0 encoded[5] = 0 size = custom_ceil(new_operand / 255) bytes = new_operand.to_bytes(size, 'little') for i in range(0, len(bytes)): encoded[i + 2] = bytes[i] jitcompiler_instance.global_allocator.write_instruction( encoded, self.position) elif isinstance(self.instruction, asm.JB): old_instruction_encoded = self.instruction.encode() new_operand = first_offset - self.position - len( old_instruction_encoded) if new_operand < 255: # We will encode the instruction new_operand = first_offset - self.position - len( old_instruction_encoded) new_instruction = asm.JB( asm.operand.RIPRelativeOffset(new_operand)) encoded = new_instruction.encode() # Need to add some NOP instruction to fill the space left from the previous longer instruction if len(old_instruction_encoded) != len(new_instruction.encode()): # If the previous instruction was a 32 bits offset, force the same length for the new one encoded = bytearray(len(old_instruction_encoded)) # Force the 32 encoding of the JNE instruction encoded[0] = 0x0F encoded[1] = 0x82 encoded[2] = 0 encoded[3] = 0 encoded[4] = 0 encoded[5] = 0 size = custom_ceil(new_operand / 255) bytes = new_operand.to_bytes(size, 'little') for i in range(0, len(bytes)): encoded[i + 2] = bytes[i] jitcompiler_instance.global_allocator.write_instruction( encoded, self.position) else: print("Not yet implemented patch " + str(self.instruction))