2026-07-10 10:38:57 +02:00

388 lines
11 KiB
Python

import logging
from ScEpTIC import tools
from ScEpTIC.AST.elements.instructions.binary_operation import BinaryOperation
from ScEpTIC.AST.elements.value import Value
from ScEpTIC.exceptions import ParsingException
from ScEpTIC.llvmir_parser.instructions_parser import conversion_operations
from ScEpTIC.llvmir_parser.instructions_parser import memory_operations
from ScEpTIC.llvmir_parser.sections_parser import global_vars
def parse_binary_operation(text):
"""
Parses and returns a binary operation.
Binary Operations:
- add
- fadd
- sub
- fsub
- mul
- fmul
- udiv
- sdiv
- fdiv
- urem
- srem
- frem
Bitwise Binary Operations:
- shl
- lsrh
- asrh
- and
- or
- xor
%target = <opcode> [optional_attributes] <type> <operand_1>, <operand_2>
"""
logging.debug('Calling parse_binary_operation({})'.format(text))
if not is_binary_operation(text):
raise ParsingException('Binary operation (add, fadd, sub, fsub, mul, fmul, udiv, sdiv, fdiv, urem, srem, frem) expected.\n{} given'.format(text))
# get the target virtual register and the offset from which the actual operation start
target, offset = parse_result_target(text)
# get the operation code
op_code = text[offset]
# remove parsed stuff
text = text[offset+1:]
# parse specific attributes
specific_attr_parser = get_specific_attr_parser(op_code)
if specific_attr_parser is not None:
specific_attr, text = specific_attr_parser(text, op_code)
else:
specific_attr = []
operands = parse_type_and_operands(text)
bitwise = is_bitwise(op_code)
return BinaryOperation(op_code, operands['op1'], operands['op2'], target, bitwise, specific_attr)
def is_binary_operation(text):
"""
Returns if a given instruction is a binary operation.
"""
logging.debug('Calling is_binary_operation({})'.format(text))
if len(text) < 4:
return False
supported_bin_ops = [
'add',
'fadd',
'sub',
'fsub',
'mul',
'fmul',
'udiv',
'sdiv',
'fdiv',
'urem',
'srem',
'frem',
'shl',
'lshr',
'ashr',
'and',
'or',
'xor'
]
op_code = text[3]
correct_op_code = op_code in supported_bin_ops
return correct_op_code and text[0] == '%' and text[2] == '='
def is_bitwise(operation_code):
"""
Returns if a given istruction is a bitwise binary operation.
"""
logging.debug('Calling is_binary_operation({})'.format(operation_code))
bitwise_codes = ['shl', 'lshr', 'ashr', 'and', 'or', 'xor']
return operation_code in bitwise_codes
def get_specific_attr_parser(op_code):
"""
Returns the optional parser for specific attributes.
"""
logging.debug('Calling get_specific_attr_parser({})'.format(op_code))
if op_code in ['add', 'sub', 'mul', 'shl']:
return parse_int_specific_attr
elif op_code in ['fadd', 'fsub', 'fmul', 'fdiv', 'frem']:
return parse_fp_specific_attr
elif op_code in ['udiv', 'sdiv', 'lshr', 'ashr']:
return parse_int_div_specific_attr
def parse_result_target(text):
"""
Returns the target virtual register of the result and the offset of the text.
"""
logging.debug('Calling parse_result_target({})'.format(text))
if text[0] != '%' or text[2] != '=':
raise ParsingException('Invalid result target: %virtual_reg = expected. {} given.'.format(text))
# %vreg = ...
# 3 to remove % vreg =
offset = 3
target = parse_operand(text[0:2])
return target, offset
def parse_type_and_operands(text):
"""
Parses type and operands of a binary operation. It returns a dict with keys {'op1', 'op2'}
"""
logging.debug('Calling parse_type_and_operands({})'.format(text))
text = tools.split_context_into_sublist(text, ',')
if len(text) != 2:
raise ParsingException('Operands len must be 2! Parsed operands: {}'.format(text))
op1 = parse_operand(text[0], True)
op2 = parse_operand(text[1])
op2.type = op1.type
return {'op1': op1, 'op2': op2}
def extract_type(text, is_function_return_type = True, is_function_attr = True):
"""
Returns the type and the offset of the type in a given string.
"""
logging.debug('Calling extract_type({}, {}, {})'.format(text, is_function_return_type, is_function_attr))
metadata_offset = 0
# if is metadata, remove keyword only if it does not refer to a metadata element
if text[0] == 'metadata' and text[1] != '!':
text = text[1:]
metadata_offset = 1
if text[0] == '<':
# is vector
end = text.index('>')
element_type = text[:end+1]
offset = end + 1
elif text[0] == '%':
# is struct / union
element_type = text[:2]
offset = 2
elif text[0] == '[':
# is an array
end = tools.get_index_of_closed_par(text, '[', ']')
element_type = text[:end+1]
offset = end + 1
elif text[0] == '{':
# is a type composition
end = tools.get_index_in_context(text, '}')
element_type = text[:end+1]
offset = end + 1
else:
# is base type
element_type = [text[0]]
offset = 1
# pointer.
while offset < len(text) and text[offset] == '*':
element_type.append(text[offset])
offset += 1
element_type = global_vars.parse_type(element_type, is_function_return_type, is_function_attr)
# compensate for metadata offset
offset += metadata_offset
return element_type, offset
def parse_operand(text, is_typed=False):
"""
Parses an operand and returns a corresponding dict with keys {'type', 'value', 'vector_type'}
"""
logging.debug('Calling parse_operand({}, {})'.format(text, is_typed))
if is_typed:
operand_type, offset = extract_type(text)
text = text[offset:]
else:
operand_type = None
# Function pointer recognition -> starts with (...)**
if text[0] == '(' and ')' in text:
operand_type.is_pointer = True
text = text[text.index(')')+1:]
while text[0] == '*':
operand_type.pointer_level += 1
text = text[1:]
# Start parsing operand
if text[0] == '%':
# %val
value = text[0]+text[1]
retval = Value('virtual_reg', value, operand_type)
elif text[0] == '@':
# @name
value = text[0]+text[1]
retval = Value('global_var', value, operand_type)
elif len(text) == 1:
# 0.0 or 0.0e+00 or 0
value = text[0]
retval = Value('immediate', value, operand_type)
elif text[0] == '!':
# ! metadata
# Empty DIExpression
if text[1:4] == ['DIExpression', '(', ')']:
retval = Value('metadata', 'empty', operand_type)
elif text[1].isdigit():
value = text[0]+text[1]
retval = Value('metadata', value, operand_type)
else:
raise ParsingException('Metadata reference expected. {} given'.format(text))
elif text[0] == '<':
# vector type
text = tools.split_into_sublist(text[1:-1], ',')
# each pair is type value; they have the same type
# so is sufficient to parse the type of the first element.
# <i32 0, i32 1, ...>
# NB: i can have <i32 0, i32 %x>
value = []
for operand in text:
val = parse_operand(operand, True)
value.append(val)
retval = Value('vector', value, operand_type)
elif text[0] == 'getelementptr':
val = memory_operations.parse_getelementptr_operation(text, False)
retval = Value('address', val, operand_type)
elif text[0] in conversion_operations.conversion_operations:
val = conversion_operations.parse_conversion_operation(text, False)
retval = Value('conversion', val, operand_type)
else:
raise ParsingException('Operand "{}" not supported.\n{}'.format(text[0], text))
logging.debug("Parsed operand: {}".format(retval))
return retval
def parse_int_specific_attr(text, operation_code):
"""
Parses and removes integer specific attributes.
It returns the list of found attributes.
"""
logging.debug('Calling parse_int_specific_attr({}, {})'.format(text, operation_code))
supported_op_code = ['add', 'sub', 'mul', 'shl']
if operation_code not in supported_op_code:
raise ParsingException('Binary operation "{}" not expected. {} only supported for integer specific attributes.\n{}'.format(operation_code, supported_op_code, text))
# nuw: no unsigned wrap
# nsw: no signed wrap
specific_attributes = ['nuw', 'nsw']
ret_attr = []
for attr in specific_attributes:
if attr in text:
text.remove(attr)
ret_attr.append(attr)
return ret_attr, text
def parse_fp_specific_attr(text, operation_code):
"""
Parses and removes floating point "fast-math flags" specific attributes.
It returns the list of found attributes.
"""
logging.debug('Calling parse_fp_specific_attr({}, {})'.format(text, operation_code))
supported_op_code = ['fadd', 'fsub', 'fmul', 'fdiv', 'frem', 'fcmp', 'call']
if operation_code not in supported_op_code:
raise ParsingException('Binary operation "{}" not expected. {} only supported for floating point specific attributes.\n{}'.format(operation_code, supported_op_code, text))
# just for optimizations, can be safely skipped in simulation
fast_math_flags = ['nnan', 'ninf', 'nsz', 'arcp', 'contract', 'afn', 'reassoc', 'fast']
ret_attr = []
for attr in fast_math_flags:
if attr in text:
text.remove(attr)
ret_attr.append(attr)
return ret_attr, text
def parse_int_div_specific_attr(text, operation_code):
"""
Parses and removes integer_division specific attributes.
It returns the list of found attributes.
"""
logging.debug('Calling parse_int_div_specific_attr({}, {})'.format(text, operation_code))
supported_op_code = ['udiv', 'sdiv', 'lshr', 'ashr']
if operation_code not in supported_op_code:
raise ParsingException('Binary operation "{}" not expected. {} only supported for division specific attributes.\n{}'.format(operation_code, supported_op_code, text))
specific_attributes = ['exact']
ret_attr = []
for attr in specific_attributes:
if attr in text:
text.remove(attr)
ret_attr.append(attr)
return ret_attr, text