Source code for m2isar.backends.etiss.instruction_transform

# SPDX-License-Identifier: Apache-2.0
#
# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R
#
# Copyright (C) 2022
# Chair of Electrical Design Automation
# Technical University of Munich

"""Recursive tree traversal methods to generate behavior code."""

import logging
from math import log2
from itertools import chain
from string import Template
from functools import singledispatchmethod

from ... import M2NameError, M2SyntaxError, M2ValueError, flatten
from ...metamodel import arch, behav
from ...metamodel.code_info import LineInfoPlacement
from ...metamodel.utils.ExprVisitor import ExprVisitor
from . import CodeInfoTracker, replacements
from .instruction_utils import (FN_VAL_REPL, MEM_VAL_REPL, CodePartsContainer,
                                CodeString, FnID, MemID, StaticType,
                                TransformerContext, data_type_map)

# pylint: disable=unused-argument

[docs] logger = logging.getLogger("instr_transform")
[docs] class InstructionTransformVisitor(ExprVisitor): """Visitor to transform M2-ISA-R model expressions to C code strings for ETISS.""" @singledispatchmethod
[docs] def generate(self, expr: behav.BaseNode, context: TransformerContext): raise NotImplementedError(f"No visit method implemented for type {type(expr).__name__} in {type(self).__name__}")
@generate.register
[docs] def _(self, expr: behav.Operation, context: TransformerContext): """Generate an `Operation` model object. Essentially generate all children, concatenate their code, and add exception behavior if needed. """ args: "list[CodeString]" = [] code_lines = [] for stmt in expr.statements: c = self.generate(stmt, context) if isinstance(c, list): args.extend(flatten(c)) else: args.append(c) if expr.line_info is not None and context.generate_coverage: CodeInfoTracker.insert(context.arch_name, expr.line_info) code_lines.append(context.wrap_codestring(f"etiss_coverage_count(1, {expr.line_info.id});")) for arg in args: if arg.is_mem_access: raise_fn_call = self.generate(behav.Conditional( [behav.CodeLiteral('cpu->exception')], [behav.ProcedureCall( context.mem_raise_fn, [behav.CodeLiteral("cpu->exception")] )] ), context) raise_fn_str = [context.wrap_codestring(c.code, c.static) for c in raise_fn_call] before_line_infos = [] after_line_infos = [] if context.generate_coverage: for l in flatten(arg.line_infos): if l is not None: CodeInfoTracker.insert(context.arch_name, l) if l.placement == LineInfoPlacement.BEFORE: before_line_infos.append(str(l.id)) elif l.placement == LineInfoPlacement.AFTER: after_line_infos.append(str(l.id)) if len(before_line_infos) > 0: code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(before_line_infos)}, {', '.join(before_line_infos)});")) for f_id in arg.function_calls: code_lines.append(context.wrap_codestring(f'{data_type_map[f_id.fn_call.data_type]}{f_id.fn_call.actual_size} {FN_VAL_REPL}{f_id.fn_id};', arg.static)) code_lines.append(context.wrap_codestring(f'{FN_VAL_REPL}{f_id.fn_id} = {f_id.args};', arg.static)) code_lines.append(context.wrap_codestring('if (cpu->return_pending) goto instr_exit_" + std::to_string(ic.current_address_) + ";', arg.static)) for m_id in arg.read_mem_ids: code_lines.append(context.wrap_codestring(f'etiss_uint{m_id.access_size} {MEM_VAL_REPL}{m_id.mem_id};')) code_lines.append(context.wrap_codestring(f'cpu->exception |= (*(system->dread))(system->handle, cpu, {m_id.index.code}, (etiss_uint8*)&{MEM_VAL_REPL}{m_id.mem_id}, {int(m_id.access_size / 8)});')) code_lines.extend(raise_fn_str) for m_id in arg.write_mem_ids: code_lines.append(context.wrap_codestring(f'etiss_uint{m_id.access_size} {MEM_VAL_REPL}{m_id.mem_id};')) code_lines.append(context.wrap_codestring(f'{arg.code}', arg.static)) if len(after_line_infos) > 0: code_lines.append(context.wrap_codestring(f"etiss_coverage_count({len(after_line_infos)}, {', '.join(after_line_infos)});")) for m_id in arg.write_mem_ids: code_lines.append(context.wrap_codestring(f'cpu->exception |= (*(system->dwrite))(system->handle, cpu, {m_id.index.code}, (etiss_uint8*)&{MEM_VAL_REPL}{m_id.mem_id}, {int(m_id.access_size / 8)});')) code_lines.extend(raise_fn_str) container = CodePartsContainer() container.initial_required = '\n'.join(code_lines) # only generate return statements if not in a function if not context.ignore_static: container.initial_required += '\ncp.code() += "instr_exit_" + std::to_string(ic.current_address_) + ":\\n";' container.initial_required += '\ncp.code() += "cpu->instructionPointer = cpu->nextPc;\\n";' return_conditions = [] return_needed = any(( context.generates_exception, arch.InstrAttribute.NO_CONT in context.attributes, arch.InstrAttribute.COND in context.attributes, arch.InstrAttribute.FLUSH in context.attributes )) if context.generates_exception: return_conditions.append("cpu->return_pending") return_conditions.append("cpu->exception") if arch.InstrAttribute.NO_CONT in context.attributes and arch.InstrAttribute.COND in context.attributes: return_conditions.append(f'cpu->nextPc != " + std::to_string(ic.current_address_ + {int(context.instr_size / 8)}) + "ULL') elif arch.InstrAttribute.NO_CONT in context.attributes: return_conditions.clear() if arch.InstrAttribute.FLUSH in context.attributes: container.initial_required = 'cp.code() += "cpu->exception = ETISS_RETURNCODE_RELOADBLOCKS;\\n";\n' + container.initial_required return_conditions.clear() if return_needed: cond_str = ("if (" + " || ".join(return_conditions) + ") ") if return_conditions else "" container.appended_returning_required = f'cp.code() += "{cond_str}return cpu->exception;\\n";' elif arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in context.attributes: container.initial_required = "cpu->return_pending = 1;\ncpu->exception = 0;\n" + container.initial_required return container
@generate.register def _(self, expr: behav.Block, context: TransformerContext): stmts = [self.generate(stmt, context) for stmt in expr.statements] pre = [CodeString("{ // block", StaticType.READ, None, None, line_infos=expr.line_info)] post = [CodeString("} // block", StaticType.READ, None, None)] if not context.ignore_static: pre.append(CodeString("{ // block", StaticType.NONE, None, None)) post.insert(0, CodeString("} // block", StaticType.NONE, None, None)) return pre + stmts + post @generate.register def _(self, expr: behav.Return, context: TransformerContext): if context.instr_size != 0: raise M2SyntaxError('Return statements are not allowed in instruction behavior!') if expr.expr is not None: c = self.generate(expr.expr, context) c.code = f'return {c.code};' c.line_infos.append(expr.line_info) else: c = CodeString("return;", StaticType.RW, None, None, line_infos=expr.line_info) return c @generate.register def _(self, expr: behav.Break, context: TransformerContext): return CodeString("break;", StaticType.RW, None, None, line_infos=expr.line_info) @generate.register def _(self, expr: behav.ScalarDefinition, context: TransformerContext): """Generate a scalar definition. Calculates the actual required data width and generates a variable instantiation.""" if context.static_scalars: if context.ignore_static: static = StaticType.RW else: static = expr.scalar.static else: static = StaticType.NONE actual_size = 1 << (expr.scalar.size - 1).bit_length() actual_size = max(actual_size, 8) return CodeString( f'{data_type_map[expr.scalar.data_type]}{actual_size} {expr.scalar.name}', static, expr.scalar.size, expr.scalar.data_type == arch.DataType.S, line_infos=expr.line_info, ) @generate.register def _(self, expr: behav.ProcedureCall, context: TransformerContext): """Generate a procedure call (Function call without usage of the return value).""" fn_args = [self.generate(arg, context) for arg in expr.args] # extract function object reference ref = expr.ref_or_name if isinstance(expr.ref_or_name, arch.Function) else None name = ref.name if isinstance(expr.ref_or_name, arch.Function) else expr.ref_or_name if ref is not None: # if there is a function object, use its information fn = ref # determine if procedure call is entirely static static = StaticType.READ if fn.static and all(arg.static != StaticType.NONE for arg in fn_args) else StaticType.NONE # convert singular static arguments if not static: context.used_arch_data = True for arg in fn_args: if arg.static and not arg.is_literal: arg.code = context.make_static(arg.code, arg.signed) # generate argument string, add ETISS arch data if required arch_args = ['cpu', 'system', 'plugin_pointers'] if arch.FunctionAttribute.ETISS_NEEDS_ARCH in fn.attributes or (not fn.static and not fn.extern) else [] arg_str = ', '.join(arch_args + [arg.code for arg in fn_args]) # check if any argument is a memory access mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) # update affected and dependent registers regs_affected = set(chain.from_iterable([arg.regs_affected for arg in fn_args])) context.dependent_regs.update(regs_affected) # add special behavior if this function is an exception entry point exc_code = "" if arch.FunctionAttribute.ETISS_TRAP_TRANSLATE_FN in fn.attributes: context.generates_exception = True if arch.FunctionAttribute.ETISS_TRAP_ENTRY_FN in fn.attributes: context.generates_exception = True if fn.size is not None: exc_code = "cpu->exception = " c = CodeString(f'{exc_code}{fn.name}({arg_str});', static, None, None, line_infos=[expr.line_info] + [x.line_infos for x in fn_args]) c.mem_ids = mem_ids if fn.throws and not context.ignore_static: c.check_trap = True cond = "if (cpu->return_pending) " if fn.throws == arch.FunctionThrows.MAYBE else "" c2 = CodeString(cond + 'goto instr_exit_" + std::to_string(ic.current_address_) + ";', static, None, None) pre = [CodeString("{ // procedure", StaticType.READ, None, None), CodeString("{ // procedure", StaticType.NONE, None, None)] post = [CodeString("} // procedure", StaticType.NONE, None, None), CodeString("} // procedure", StaticType.READ, None, None)] return pre + [c, c2] + post return c raise M2NameError(f'Function {name} not recognized!') @generate.register def _(self, expr: behav.FunctionCall, context: TransformerContext): """Generate a regular function call (with further use of return value).""" fn_args = [self.generate(arg, context) for arg in expr.args] # extract function object reference ref = expr.ref_or_name if isinstance(expr.ref_or_name, arch.Function) else None name = ref.name if isinstance(expr.ref_or_name, arch.Function) else expr.ref_or_name if ref is not None: # if there is a function object, use its information fn = ref # determine if function call is entirely static static = StaticType.READ if fn.static and all(arg.static != StaticType.NONE for arg in fn_args) else StaticType.NONE # convert singular static arguments if not static: context.used_arch_data = True for arg in fn_args: if arg.static and not arg.is_literal: arg.code = context.make_static(arg.code, arg.signed) # generate argument string, add ETISS arch data if required arch_args = ['cpu', 'system', 'plugin_pointers'] if arch.FunctionAttribute.ETISS_NEEDS_ARCH in fn.attributes or (not fn.static and not fn.extern) else [] arg_str = ', '.join(arch_args + [arg.code for arg in fn_args]) # keep track of signedness of function return value signed = fn.data_type == arch.DataType.S # keep track of affected registers regs_affected = set(chain.from_iterable([arg.regs_affected for arg in fn_args])) c = CodeString(f'{fn.name}({arg_str})', static, fn.size, signed, regs_affected, [expr.line_info] + [x.line_infos for x in fn_args]) c.mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) if fn.throws and not context.ignore_static: fn_id = FnID(fn, context.fn_var_count, c) repl_c = CodeString(f'{FN_VAL_REPL}{context.fn_var_count}', static, fn.size, signed, regs_affected) repl_c.mem_ids = list(chain.from_iterable([arg.mem_ids for arg in fn_args])) repl_c.function_calls.append(fn_id) context.fn_var_count += 1 return repl_c return c raise M2NameError(f'Function {name} not recognized!') @generate.register def _(self, expr: behav.Callable, context: TransformerContext): # Callable is the base class; dispatch to specific subclass handler if isinstance(expr, behav.FunctionCall): return self.generate(expr, context) elif isinstance(expr, behav.ProcedureCall): return self.generate(expr, context) raise M2SyntaxError(f"Unsupported callable node type {type(expr).__name__}") @generate.register def _(self, expr: behav.Assignment, context: TransformerContext): """Generate an assignment expression""" # generate target and value expressions target: CodeString = self.generate(expr.target, context) expr_str: CodeString = self.generate(expr.expr, context) # check staticness static = bool(target.static & StaticType.WRITE) and bool(expr_str.static) if not expr_str.static and bool(target.static & StaticType.WRITE) and not context.ignore_static: raise M2ValueError('Static target cannot be assigned to non-static expression!') # convert assignment value staticness if expr_str.static and not expr_str.is_literal: if bool(target.static & StaticType.WRITE): if context.ignore_static: expr_str.code = Template(f'{expr_str.code}').safe_substitute(**replacements.rename_dynamic) else: expr_str.code = Template(f'{expr_str.code}').safe_substitute(**replacements.rename_static) else: expr_str.code = context.make_static(expr_str.code, expr_str.signed) # convert target staticness if bool(target.static & StaticType.READ): target.code = Template(target.code).safe_substitute(replacements.rename_write) # keep track of affected and dependent registers context.affected_regs.update(target.regs_affected) context.dependent_regs.update(expr_str.regs_affected) if not target.is_mem_access and not expr_str.is_mem_access: if target.actual_size > target.size: if target.signed: shift = target.actual_size - target.size expr_str.code = f'(((etiss_int{target.actual_size})({expr_str.code})) << {shift}) >> {shift}' else: mask = (1 << target.size) - 1 mask_bits = log2(mask) if mask_bits > 64: mask64 = (1 << 64) - 1 low = mask & mask64 high = mask >> 64 mask_code = f"(((etiss_int128){hex(high)}ULL << 64) | {hex(low)}ULL)" else: mask_code = f"{hex(mask)}ULL" expr_str.code = f'({expr_str.code}) & {mask_code}' else: context.generates_exception = True for m_id in expr_str.mem_ids: m_id.write = False if target.is_mem_access: if len(target.mem_ids) != 1: raise M2SyntaxError('Only one memory access is allowed as assignment target!') target.mem_ids[0].write = True c = CodeString(f"{target.code} = {expr_str.code};", static, None, None, line_infos=[expr.line_info] + target.line_infos + expr_str.line_infos) c.function_calls.extend(target.function_calls) c.function_calls.extend(expr_str.function_calls) c.mem_ids.extend(target.mem_ids) c.mem_ids.extend(expr_str.mem_ids) return c @generate.register def _(self, expr: behav.BinaryOperation, context: TransformerContext): """Generate a binary expression""" # generate LHS and RHS of the expression left = self.generate(expr.left, context) op = expr.op right = self.generate(expr.right, context) # convert staticness if needed if not left.static and right.static and not right.is_literal: right.code = context.make_static(right.code, right.signed) if not right.static and left.static and not left.is_literal: left.code = context.make_static(left.code, left.signed) c = CodeString( f'{left.code} {op.value} {right.code}', left.static and right.static, left.size if left.size > right.size else right.size, left.signed or right.signed, set.union(left.regs_affected, right.regs_affected), [expr.line_info] + left.line_infos + right.line_infos, ) # keep track of any memory accesses c.mem_ids = left.mem_ids + right.mem_ids return c @generate.register def _(self, expr: behav.UnaryOperation, context: TransformerContext): op = expr.op right = self.generate(expr.right, context) c = CodeString(f'{op.value}({right.code})', right.static, right.size, right.signed, right.regs_affected, [expr.line_info] + right.line_infos) c.mem_ids = right.mem_ids return c @generate.register def _(self, expr: behav.Conditional, context: TransformerContext): """Generate a conditional ('if' with optional 'else if' and 'else' blocks)""" # generate conditions and statement blocks conds: "list[CodeString]" = [self.generate(x, context) for x in expr.conds] stmts: "list[list[CodeString]]" = [] for cond in conds[1:]: conds[0].mem_ids.extend(cond.mem_ids) cond.mem_ids.clear() for stmt in expr.stmts: if isinstance(stmt, list): ret = [] for stmt_ in stmt: ret_ = self.generate(stmt_, context) ret.append(ret_) else: ret = self.generate(stmt, context) if isinstance(ret, list): stmts.append(ret) else: stmts.append([ret]) # check if all conditions are static static = all(x.static for x in conds) outputs: "list[CodeString]" = [] for cond in conds: for m_id in cond.mem_ids: m_id.write = False if cond.static and not static: cond.code = context.make_static(cond.code) cond.static = False conds[0].code = f'if ({conds[0].code}) {{ // conditional' conds[0].line_infos.append(expr.line_info) outputs.append(conds[0]) if not static: context.dependent_regs.update(conds[0].regs_affected) # generate first statement block outputs.extend(flatten(stmts[0])) # generate closing brace outputs.append(CodeString("} // conditional", static, None, None)) for elif_cond, elif_stmts in zip(conds[1:], stmts[1:]): elif_cond.code = f' else if ({elif_cond.code}) {{ // conditional' outputs.append(elif_cond) if not static: context.dependent_regs.update(elif_cond.regs_affected) outputs.extend(flatten(elif_stmts)) outputs.append(CodeString("} // conditional", static, None, None)) if len(conds) < len(stmts): outputs.append(CodeString("else { // conditional", static, None, None)) outputs.extend(flatten(stmts[-1])) outputs.append(CodeString("} // conditional", static, None, None)) return outputs @generate.register def _(self, expr: behav.Loop, context: TransformerContext): """Generate 'while' and 'do .. while' loops.""" cond: CodeString = self.generate(expr.cond, context) stmts: "list[CodeString]" = [] for stmt in expr.stmts: if isinstance(stmt, list): for stmt2 in stmt: stmts.append(self.generate(stmt2, context)) else: stmts.append(self.generate(stmt, context)) if not cond.static: context.dependent_regs.update(cond.regs_affected) outputs: "list[CodeString]" = [] if expr.post_test: start_c = CodeString("do", cond.static, None, None) end_c = cond end_c.code = f'while ({end_c.code})' else: start_c = cond start_c.code = f'while ({start_c.code})' end_c = CodeString("", cond.static, None, None) outputs.append(start_c) outputs.extend(flatten(stmts)) outputs.append(end_c) return outputs @generate.register def _(self, expr: behav.Ternary, context: TransformerContext): """Generate a ternary expression.""" # generate condition and 'then' and 'else' statements cond = self.generate(expr.cond, context) then_expr = self.generate(expr.then_expr, context) else_expr = self.generate(expr.else_expr, context) static = StaticType.NONE not in [x.static for x in (cond, then_expr, else_expr)] # convert singular static sub-components if not static: if cond.static and not cond.is_literal: cond.code = context.make_static(cond.code, cond.signed) if then_expr.static and not then_expr.is_literal: then_expr.code = context.make_static(then_expr.code, then_expr.signed) if else_expr.static and not else_expr.is_literal: else_expr.code = context.make_static(else_expr.code, else_expr.signed) c = CodeString( f'({cond}) ? ({then_expr}) : ({else_expr})', static, then_expr.size if then_expr.size > else_expr.size else else_expr.size, then_expr.signed or else_expr.signed, set.union(cond.regs_affected, then_expr.regs_affected, else_expr.regs_affected), [expr.line_info] + cond.line_infos + then_expr.line_infos + else_expr.line_infos, ) c.mem_ids = cond.mem_ids + then_expr.mem_ids + else_expr.mem_ids return c @generate.register def _(self, expr: behav.TypeConv, context: TransformerContext): """Generate a type cast expression""" # generate the expression to be type-casted expr_str = self.generate(expr.expr, context) # if only width should be changed assume data type remains unchanged if expr.data_type is None: expr.data_type = arch.DataType.S if expr_str.signed else arch.DataType.U # if only data type should be changed assume width remains unchanged if expr.size is None: expr._size = expr_str.size expr._actual_size = expr_str.actual_size code_str = expr_str.code # sign extension for non-2^N datatypes if expr.data_type == arch.DataType.S and expr_str.actual_size != expr_str.size: target_size = expr.actual_size if isinstance(expr.size, int): code_str = f'((etiss_int{target_size})(((etiss_int{target_size}){expr_str.code}) << ({target_size - expr.size})) >> ({target_size - expr.size}))' else: code_str = f'((etiss_int{target_size})(({expr_str.code}) << ({target_size} - {expr.size})) >> ({target_size} - {expr.size}))' # normal type conversion # TODO: check if behavior adheres to CoreDSL 2 spec else: code_str = f'({data_type_map[expr.data_type]}{expr.actual_size})({code_str})' c = CodeString(code_str, expr_str.static, expr.size, expr.data_type == arch.DataType.S, expr_str.regs_affected, line_infos=[expr.line_info] + expr_str.line_infos) c.mem_ids = expr_str.mem_ids return c @generate.register def _(self, expr: behav.NamedReference, context: TransformerContext): """Generate a named reference""" # extract referred object referred_var = expr.reference static = StaticType.NONE name = referred_var.name # check if static name replacement is needed if name in replacements.rename_static: name = f'${{{name}}}' static = StaticType.READ # check which type of reference has to be generated if isinstance(referred_var, arch.Memory): # architecture constant if not static: ref = "*" if len(referred_var.children) > 0 else "" name = f"{ref}{replacements.default_prefix}{name}" signed = False size = referred_var.size context.used_arch_data = True elif isinstance(referred_var, arch.BitFieldDescr): # function argument signed = referred_var.data_type == arch.DataType.S size = referred_var.size static = StaticType.READ elif isinstance(referred_var, arch.Scalar): signed = referred_var.data_type == arch.DataType.S size = referred_var.size if context.static_scalars: static = referred_var.static elif isinstance(referred_var, arch.Constant): signed = referred_var.value < 0 size = context.native_size static = StaticType.READ name = f'{referred_var.value}' elif isinstance(referred_var, arch.FnParam): signed = referred_var.data_type == arch.DataType.S size = referred_var.size static = StaticType.RW elif isinstance(referred_var, arch.Intrinsic): if context.ignore_static: raise TypeError("intrinsic not allowed in function") signed = referred_var.data_type == arch.DataType.S size = referred_var.size static = StaticType.READ if referred_var == context.intrinsics["__encoding_size"]: name = str(context.instr_size // 8) else: raise TypeError("wrong type") if context.ignore_static: static = StaticType.RW return CodeString(name, static, size, signed, line_infos=expr.line_info) @generate.register def _(self, expr: behav.IndexedReference, context: TransformerContext): """Generate an indexed reference expression (for register banks or memory).""" name = expr.reference.name # generate index expression index = self.generate(expr.index, context) referred_mem = expr.reference if isinstance(referred_mem, arch.Memory): context.used_arch_data = True size = referred_mem.size # convert static index expression index_code = index.code if index.static and not context.ignore_static and not index.is_literal: index.code = context.make_static(index.code, index.signed) if context.ignore_static: static = StaticType.RW else: static = StaticType.NONE if arch.MemoryAttribute.IS_MAIN_MEM in referred_mem.attributes: # generate memory access if main memory is accessed size = expr.inferred_type._width c = CodeString(f'{MEM_VAL_REPL}{context.mem_var_count}', static, size, False, line_infos=[expr.line_info] + index.line_infos) if (expr.right != None): # Use a simple base address on one site atleast for ranged_mem access. # Index Codestring is forwarded into MemID class. right = self.generate(expr.right, context) if(type(expr.index) == behav.NamedReference): c.mem_ids.append(MemID(referred_mem, context.mem_var_count, index, size)) elif(type(expr.right) == behav.NamedReference): c.mem_ids.append(MemID(referred_mem, context.mem_var_count, right, size)) else: raise(f"IndexedExpr ist needs to be static on one side: But Type is Left: {type(expr.index)} and Right: {type(expr.index)}") else: c.mem_ids.append(MemID(referred_mem, context.mem_var_count, index, size)) context.mem_var_count += 1 return c # generate normal indexed access if not code_str = f'{replacements.prefixes.get(name, replacements.default_prefix)}{name}[{index.code}]' if len(referred_mem.children) > 0: code_str = '*' + code_str c = CodeString(code_str, static, size, False, line_infos=[expr.line_info] + index.line_infos) if arch.MemoryAttribute.IS_MAIN_REG in referred_mem.attributes: c.regs_affected.add(index_code) return c @generate.register def _(self, expr: behav.SliceOperation, context: TransformerContext): """Generate a slice expression""" # generate expression to be sliced and lower and upper slice bound expr_str = self.generate(expr.expr, context) left = self.generate(expr.left, context) right = self.generate(expr.right, context) static = StaticType.NONE not in [x.static for x in (expr_str, left, right)] if not static: if expr_str.static and not expr_str.is_literal: expr_str.code = context.make_static(expr_str.code, expr_str.signed) if left.static and not left.is_literal: left.code = context.make_static(left.code, left.signed) if right.static and not right.is_literal: right.code = context.make_static(right.code, right.signed) # slice with fixed integers if slice bounds are integers try: new_size = int(left.code.replace("U", "").replace("L", "")) - int(right.code.replace("U", "").replace("L", "")) + 1 mask = (1 << (int(left.code.replace("U", "").replace("L", "")) - int(right.code.replace("U", "").replace("L", "")) + 1)) - 1 mask_bits = log2(mask) if mask_bits > 64: mask64 = (1 << 64) - 1 low = mask & mask64 high = mask >> 64 mask_code = f"((((etiss_int128)){hex(high)}ULL << 64) | {hex(low)}ULL)" else: mask_code = f"{hex(mask)}ULL" mask = f"{mask_code}" simple_mask = True # slice with actual lower and upper bound code if not possible to slice with integers except ValueError: new_size = expr_str.size mask = f"((1 << (({left.code}) - ({right.code}) + 1)) - 1)" simple_mask = False if simple_mask and (int(right.code.replace("U", "").replace("L", "")) == 0): # no need to shift zeros steps code = f"(({expr_str.code}) & {mask})" else: code = f"((({expr_str.code}) >> ({right.code})) & {mask})" c = CodeString(code, static, new_size, expr_str.signed, set.union(expr_str.regs_affected, left.regs_affected, right.regs_affected), [expr.line_info] + expr_str.line_infos + left.line_infos + right.line_infos) c.mem_ids = expr_str.mem_ids + left.mem_ids + right.mem_ids return c @generate.register def _(self, expr: behav.ConcatOperation, context: TransformerContext): """Generate a concatenation expression""" # generate LHS and RHS operands left: CodeString = self.generate(expr.left, context) right: CodeString = self.generate(expr.right, context) if not left.static and right.static and not right.is_literal: right.code = context.make_static(right.code, right.signed) if not right.static and left.static and not left.is_literal: left.code = context.make_static(left.code, left.signed) new_size = left.size + right.size c = CodeString(f"((({left.code}) << {right.size}) | ({right.code}))", left.static and right.static, new_size, left.signed or right.signed, set.union(left.regs_affected, right.regs_affected), [expr.line_info] + left.line_infos + right.line_infos) c.mem_ids = left.mem_ids + right.mem_ids return c @generate.register def _(self, expr: behav.NumberLiteral, context: TransformerContext): """Generate generic number literal. Currently unused.""" lit = int(expr.value) size = min(lit.bit_length(), 64) sign = lit < 0 twocomp_lit = (lit + (1 << 64)) % (1 << 64) postfix = "U" if not sign else "" postfix += "LL" return CodeString(str(twocomp_lit) + postfix, True, size, sign, line_infos=expr.line_info) @generate.register def _(self, expr: behav.IntLiteral, context: TransformerContext): """Generate an integer literal.""" lit = int(expr.value) size = min(expr.bit_size, 128) sign = expr.signed minus = "" if lit > 0 and sign and (lit >> (size - 1)) & 1: minus = "-" _ = (lit + (1 << size)) % (1 << size) postfix = "U" if not sign else "" postfix += "LL" ret = CodeString(minus + str(lit) + postfix, True, size, sign, line_infos=expr.line_info) ret.is_literal = True return ret @generate.register def _(self, expr: behav.StringLiteral, context: TransformerContext): return CodeString(f'"{expr.value}"', StaticType.READ, None, False, line_infos=expr.line_info) @generate.register def _(self, expr: behav.CodeLiteral, context: TransformerContext): return CodeString(expr.val, False, context.native_size, False, line_infos=expr.line_info) @generate.register def _(self, expr: behav.Operator, context: TransformerContext): return expr.value @generate.register def _(self, expr: behav.Group, context: TransformerContext): """Generate a group of expressions.""" expr_str = self.generate(expr.expr, context) if isinstance(expr_str, CodeString): expr_str.code = f'({expr_str.code})' expr_str.line_infos.append(expr.line_info) else: expr_str = f'({expr_str})' return expr_str