import logging from ScEpTIC import tools from ScEpTIC.AST.elements.instructions.memory_operations import AllocaOperation, LoadOperation, StoreOperation, GetElementPointerOperation from ScEpTIC.exceptions import ParsingException from ScEpTIC.llvmir_parser.instructions_parser import binary_operations from ScEpTIC.llvmir_parser.sections_parser import global_vars def parse_memory_operation(text): """ Parses and returns a memory operation. - alloca - load - getelementptr - store """ logging.debug('Calling is_alloca_operation({})'.format(text)) if is_memory_operation_with_return(text, 'alloca'): instruction_type = 'alloca' instruction_value = parse_alloca_operation(text) elif is_memory_operation_with_return(text, 'load'): instruction_type = 'load' instruction_value = parse_load_operation(text) elif is_memory_operation_with_return(text, 'getelementptr'): instruction_type = 'getelementptr' instruction_value = parse_getelementptr_operation(text) elif is_memory_operation_without_return(text, 'store'): instruction_type = 'store' instruction_value = parse_store_operation(text) else: return None if logging.getLogger().isEnabledFor(logging.DEBUG): logging.debug('Parsed {} instruction:\n{}'.format(instruction_type, instruction_value)) return instruction_value def is_memory_operation(text): """ Return if an instruction is a memory operation. """ logging.debug('Calling is_alloca_operation({})'.format(text)) if len(text) < 4: return False # fence imposes instruction ordering # cmpoxchg compares modifies memory # atomicrmw atomically modifies memory not_implemented = ['fence', 'cmpxchg', 'atomicrmw'] if text[0] in not_implemented or text[3] in not_implemented: raise NotImplementedError('Memory Operation "{}" not supported!'.format(text[3])) return is_memory_operation_with_return(text) or is_memory_operation_without_return(text) def is_memory_operation_with_return(text, operation_code = None): """ Return if an instruction is a memory operation with a return. """ logging.debug('Calling is_memory_operation_with_return({}, {})'.format(text, operation_code)) if len(text) < 4: return False supported_mem_ops = [ 'alloca', 'load', 'getelementptr' ] # operation code is on position 3 # % ret_reg = CODE ... op_code = text[3] if operation_code is None: correct_op_code = op_code in supported_mem_ops else: correct_op_code = op_code == operation_code return correct_op_code and text[0] == '%' and text[2] == '=' def is_memory_operation_without_return(text, operation_code = None): """ Return if an instruction is a memory operation without a return. """ logging.debug('Calling is_memory_operation_without_return({}, {})'.format(text, operation_code)) if len(text) < 4: return False supported_mem_ops = [ 'store' ] # operation code is on position 0 # CODE ... op_code = text[0] if operation_code is None: correct_op_code = op_code in supported_mem_ops else: correct_op_code = op_code == operation_code return correct_op_code def parse_alloca_operation(text): """ Parses and returns an alloca operation. = alloca [inalloca] [, ] [, align ] [, addrspace()] """ logging.debug('Calling is_alloca_operation({})'.format(text)) if not is_memory_operation_with_return(text, 'alloca'): raise ParsingException('Memory operation "alloca" expected.\n{} given.'.format(text)) # retrieve where the result must be stored target, offset = binary_operations.parse_result_target(text) text = text[offset+1:] # split into groups text = tools.split_context_into_sublist(text, ',') # type is on the first group elem_type = global_vars.parse_type(text[0]) elem_num = '1' # remove parsed group text = text[1:] # if present, the second group is the number of elements to be allocated. # Is represented as type elements. Only num elements needed, which is on pos. 1 if text[0][0] not in ['align', 'addrspace']: elem_num = text[1][1] text = text[1:] # get the alignment align = None if text[0][0] == 'align': align = text[0][1] return AllocaOperation(target, elem_type, elem_num, align) def parse_load_operation(text): """ Parses and returns a load operation. = load [volatile] , * [, align ] = load atomic [volatile] , * [syncscope("")] , align """ logging.debug('Calling parse_load_operation({})'.format(text)) if not is_memory_operation_with_return(text, 'load'): raise ParsingException('Memory operation "load" expected.\n{} given.'.format(text)) # retrieve where the result must be stored target, offset = binary_operations.parse_result_target(text) text = text[offset+1:] volatile = False # get if is marked as volatile if 'volatile' in text: volatile = True text.remove('volatile') if text[0] == 'atomic': # not a C feature raise NotImplementedError('Atomic loading not implemented!') # text[0] -> load type # text[1] -> type and pointer # text[2] -> if present, alignment text = tools.split_context_into_sublist(text, ',') load_type = global_vars.parse_type(text[0]) element = binary_operations.parse_operand(text[1], True) if len(text) > 2: # align number align = text[2][1] else: align = None return LoadOperation(target, load_type, element, align, volatile) def parse_store_operation(text): """ Parses and returns a store operation. store [volatile] , * [, align ] store atomic [volatile] , * [syncscope("")] , align """ logging.debug('Calling parse_store_operation({})'.format(text)) if not is_memory_operation_without_return(text, 'store'): raise ParsingException('Memory operation "store" expected.\n{} given.'.format(text)) volatile = False # get if is marked as volatile if 'volatile' in text: volatile = True text.remove('volatile') if text[1] == 'atomic': # not a C feature raise NotImplementedError('Atomic loading not implemented!') text = tools.split_context_into_sublist(text[1:], ',') store_value = binary_operations.parse_operand(text[0], True) store_target = binary_operations.parse_operand(text[1], True) # has alignment if len(text) > 2: # align value align = text[2][1] else: align = None return StoreOperation(store_target, store_value, align, volatile) def parse_getelementptr_operation(text, has_target = True): """ Parses and returns a getelementptr operation. = getelementptr inbounds , * {, [inrange] }* """ logging.debug('Calling parse_getelementptr_operation({}, {})'.format(text, has_target)) if has_target and not is_memory_operation_with_return(text, 'getelementptr'): raise ParsingException('Memory operation "getelementptr" expected.\n{} given.'.format(text)) if not has_target and not is_memory_operation_without_return(text, 'getelementptr'): raise ParsingException('Memory operation "getelementptr" expected.\n{} given.'.format(text)) # retrieve where the result must be stored if has_target: target, offset = binary_operations.parse_result_target(text) text = text[offset+1:] else: text = tools.list_sanitize(text, '(') text = tools.list_sanitize(text, ')') text = text[1:] target = None inbounds = False # get if is marked as inbounds if 'inbounds' in text: inbounds = True text.remove('inbounds') # text[0] -> type # text[1] -> element # text[2:] -> indexes text = tools.split_context_into_sublist(text, ',') base_type = global_vars.parse_type(text[0]) element = binary_operations.parse_operand(text[1], True) indexes = [] # parse the indexes for index_pair in text[2:]: inrange = False if index_pair[0] == 'inrange': inrange = True index_pair = index_pair[1:] index = binary_operations.parse_operand(index_pair, True) # add inrange attribute index.inrange = inrange indexes.append(index) return GetElementPointerOperation(target, element, base_type, indexes, inbounds)