def call_indirect_op(config: Configuration) -> None: """ Logic function for the CALL_INDIRECT opcode """ instruction = cast(CallIndirect, config.current_instruction) if config.enable_logic_fn_logging: logger.debug("%s()", instruction.opcode.text) table_address = config.frame_module.table_addrs[0] table = config.store.tables[table_address] function_type = config.frame_module.types[instruction.type_idx] element_idx = config.pop_u32() if len(table.elem) <= element_idx: raise Trap( "Element index out of table range: {element_idx} > {len(table.elem)}" ) function_address = table.elem[element_idx] if function_address is None: raise Trap("Table element at index {element_idx} is empty") function = config.store.funcs[int(function_address)] if function.type != function_type: raise Trap( "Function type mismatch. Expected {function_type}. Got {function.type}" ) _setup_call(config, function_address)
def iXX_divs_op(config: Configuration) -> None: """ Common logic function for the integer DIVS opcodes """ instruction = cast(BinOp, config.current_instruction) b, a = config.pop2_u32() b_s = instruction.valtype.to_signed(b) a_s = instruction.valtype.to_signed(a) if config.enable_logic_fn_logging: logger.debug("%s(%s, %s)", instruction.opcode.text, a_s, b_s) if b == 0: raise Trap('DIVISION BY ZERO') raw_result = abs(int(a_s)) // abs(int(b_s)) _, upper_bound = instruction.valtype.signed_bounds if raw_result > upper_bound: raise Trap('UNDEFINED') if (a_s < 0) is not (b_s < 0): signed_result = instruction.valtype.signed_type(-1 * raw_result) else: signed_result = instruction.valtype.signed_type(raw_result) result = instruction.valtype.from_signed(signed_result) config.push_operand(result)
def iXX_trunc_usX_fXX_op(config: Configuration) -> None: """ Common logic function for the TRUNC opcodes which convert a float to an integer """ instruction = cast(Truncate, config.current_instruction) value = config.pop_f32() if config.enable_logic_fn_logging: logger.debug("%s(%s)", instruction.opcode.text, value) if numpy.isnan(value) or numpy.isinf(value): raise Trap(f"Truncation is undefined for {value}") else: trunc_value = int(numpy.trunc(value)) if instruction.signed: s_lower_bound, s_upper_bound = instruction.valtype.signed_bounds if trunc_value < s_lower_bound or trunc_value > s_upper_bound: raise Trap( f"Truncation is undefined for {value}. Result outside of s32 " "range.") result = instruction.valtype.from_signed( instruction.valtype.signed_type(trunc_value)) else: u_lower_bound, u_upper_bound = instruction.valtype.bounds if trunc_value < u_lower_bound or trunc_value > u_upper_bound: raise Trap( f"Truncation is undefined for {value}. Result outside of s32 " "range.") result = instruction.valtype.value(trunc_value) config.push_operand(result)
def read(self, location: numpy.uint32, size: numpy.uint32) -> memoryview: with no_overflow(): try: end_location = location + size except FloatingPointError: raise Trap( f"Attempt to read from out of bounds memory location: {int(location) + size} " f"> {len(self.data)}") if end_location > self._length_cache: raise Trap( f"Attempt to read from out of bounds memory location: {end_location} " f"> {len(self.data)}") return memoryview(self.data)[location:end_location]
def unreachable_op(config: Configuration) -> None: """ Logic function for the UNREACHABLE opcode """ if config.enable_logic_fn_logging: logger.debug("%s()", config.current_instruction.opcode.text) raise Trap("TRAP")
def write(self, location: numpy.uint32, value: bytes) -> None: end_index = location + len(value) if end_index > self._length_cache: raise Trap(f"Attempt to write to out of bounds memory location: " f"{end_index} " f"> {len(self.data)}") self.data[location:end_index] = value
def idivu_op(config: Configuration) -> None: """ Common logic function for the integer DIVU opcodes """ b, a = config.pop2_u32() if config.enable_logic_fn_logging: logger.debug("%s(%s, %s)", config.current_instruction.opcode.text, a, b) if b == 0: raise Trap('DIVISION BY ZERO') config.push_operand(a // b)
def iXX_rems_op(config: Configuration) -> None: """ Common logic function for the integer REMS opcodes """ instruction = cast(BinOp, config.current_instruction) b, a = config.pop2_u64() if config.enable_logic_fn_logging: logger.debug("%s(%s, %s)", instruction.opcode.text, a, b) if b == 0: raise Trap('DIVISION BY ZERO') b_s = instruction.valtype.to_signed(b) a_s = instruction.valtype.to_signed(a) raw_result = numpy.abs(a_s) % numpy.abs(b_s) result = -1 * raw_result if a_s < 0 else raw_result config.push_operand(instruction.valtype.value(result))
def load_op(config: Configuration) -> None: """ Logic function for the various *LOAD* memory opcodes. """ instruction = cast(MemoryOp, config.current_instruction) if config.enable_logic_fn_logging: logger.debug("%s()", instruction.opcode.text) memarg = instruction.memarg memory_address = config.frame_module.memory_addrs[0] mem = config.store.mems[memory_address] base_offset = config.pop_u32() with no_overflow(): try: memory_location = base_offset + memarg.offset except FloatingPointError: raise Trap( "Memory locatin exceeds u32 bounds: {int(base_offset) + memarg.offset" ) value_bit_width = instruction.memory_bit_size.value value_byte_width = value_bit_width // constants.BYTE_SIZE raw_bytes = mem.read(memory_location, value_byte_width) if instruction.valtype.is_integer_type: raw_value = instruction.memory_bit_size.unpack_int_bytes( raw_bytes, bool(instruction.signed), ) if instruction.signed: config.push_operand(instruction.valtype.from_signed(raw_value)) else: config.push_operand(instruction.valtype.value(raw_value)) elif instruction.valtype.is_float_type: value = instruction.valtype.unpack_float_bytes(raw_bytes) config.push_operand(value) else: raise Exception("Invariant")