<commit_before>/*
 * This file is part of libFirm.
 * Copyright (C) 2012 University of Karlsruhe.
 */

/**
 * @file
 * @brief   Load/Store optimizations.
 * @author  <NAME>
 */
#include <string.h>

#include "array.h"
#include "be.h"
#include "dbginfo_t.h"
#include "debug.h"
#include "entity_t.h"
#include "panic.h"
#include "ircons_t.h"
#include "iredges.h"
#include "irflag_t.h"
#include "irgmod.h"
#include "irgopt.h"
#include "irgraph_t.h"
#include "irgwalk.h"
#include "irhooks.h"
#include "irmemory.h"
#include "irmode_t.h"
#include "irnode_t.h"
#include "irnodehashmap.h"
#include "iropt_dbg.h"
#include "iropt_t.h"
#include "iroptimize.h"
#include "irtools.h"
#include "set.h"
#include "tv_t.h"
#include "type_t.h"
#include "util.h"

/** The debug handle. */
DEBUG_ONLY(static firm_dbg_module_t *dbg;)

#define MAX_PROJ MAX(MAX((unsigned)pn_Load_max, (unsigned)pn_Store_max), (unsigned)pn_Call_max)

typedef enum changes_t {
	NO_CHANGES = 0,
	DF_CHANGED = (1 << 0), /**< data flow changed */
	CF_CHANGED = (1 << 1), /**< control flow changed */
	/** nodes have been created but are not reachable. This is a bad hack
	 * try hard to avoid it! */
	NODES_CREATED = (1 << 2),
} changes_t;

/**
 * walker environment
 */
typedef struct walk_env_t {
	struct obstack obst;    /**< list of all stores */
	changes_t      changes; /**< a bitmask of graph changes */
} walk_env_t;

/** A Load/Store info. */
typedef struct ldst_info_t {
	ir_node  *projs[MAX_PROJ+1]; /**< list of Proj's of this node */
	ir_node  *exc_block;         /**< the exception block if available */
	int      exc_idx;            /**< predecessor index in exception block */
	unsigned visited;            /**< visited counter for breaking loops */
} ldst_info_t;

typedef struct base_offset_t {
	ir_node *base;
	long     offset;
} base_offset_t;

typedef struct track_load_env_t {
	ir_node      *load;
	base_offset_t base_offset;
	ir_node      *ptr; /* deprecated: alternative representation of
	                      base_offset */
} track_load_env_t;

/**
 * flags for control flow.
 */
typedef enum block_flags_t {
	BLOCK_HAS_COND = (1 << 0), /**< Block has conditional control flow */
	BLOCK_HAS_EXC  = (1 << 1), /**< Block has exceptional control flow */
} block_flags_t;

/**
 * a Block info.
 */
typedef struct block_info_t {
	block_flags_t flags;  /**< flags for the block */
} block_info_t;

/** the master visited flag for loop detection. */
static unsigned master_visited;

#define INC_MASTER()       ++master_visited
#define MARK_NODE(info)    (info)->visited = master_visited
#define NODE_VISITED(info) (info)->visited >= master_visited

/**
 * get the Load/Store info of a node
 */
static ldst_info_t *get_ldst_info(ir_node *node, struct obstack *obst)
{
	ldst_info_t *info = (ldst_info_t*)get_irn_link(node);
	if (info == NULL) {
		info = OALLOCZ(obst, ldst_info_t);
		set_irn_link(node, info);
	}
	return info;
}

/**
 * get the Block info of a node
 */
static block_info_t *get_block_info(ir_node *node, struct obstack *obst)
{
	block_info_t *info = (block_info_t*)get_irn_link(node);
	if (info == NULL) {
		info = OALLOCZ(obst, block_info_t);
		set_irn_link(node, info);
	}
	return info;
}

/**
 * update the projection info for a Load/Store
 */
static changes_t update_projs(ldst_info_t *info, ir_node *proj)
{
	unsigned nr = get_Proj_num(proj);
	assert(nr <= MAX_PROJ);

	if (info->projs[nr] != NULL) {
		/* there is already one, do CSE */
		exchange(proj, info->projs[nr]);
		return DF_CHANGED;
	} else {
		info->projs[nr] = proj;
		return NO_CHANGES;
	}
}

/**
 * update the exception block info for a Load/Store node.
 *
 * @param info   the load/store info struct
 * @param block  the exception handler block for this load/store
 * @param pos    the control flow input of the block
 */
static void update_exc(ldst_info_t *info, ir_node *block, int pos)
{
	assert(info->exc_block == NULL);
	info->exc_block = block;
	info->exc_idx   = pos;
}

/**
 * walker, collects all Proj/Load/Store/Call/CopyB nodes
 *
 * walks from Start -> End
 */
static void collect_nodes(ir_node *node, void *env)
{
	walk_env_t *wenv   = (walk_env_t *)env;
	unsigned    opcode = get_irn_opcode(node);

	if (opcode == iro_Proj) {
		ir_node *pred = get_Proj_pred(node);
		opcode = get_irn_opcode(pred);

		if (opcode == iro_Load || opcode == iro_Store || opcode == iro_Call) {
			ldst_info_t *ldst_info = get_ldst_info(pred, &wenv->obst);

			wenv->changes |= update_projs(ldst_info, node);

			/*
			 * Place the Proj's to the same block as the
			 * predecessor Load. This is always ok and prevents
			 * "non-SSA" form after optimizations if the Proj
			 * is in a wrong block.
			 */
			ir_node *blk      = get_nodes_block(node);
			ir_node *pred_blk = get_nodes_block(pred);
			if (blk != pred_blk) {
				wenv->changes |= DF_CHANGED;
				set_nodes_block(node, pred_blk);
			}
		}
	} else if (opcode == iro_Block) {
		for (int i = get_Block_n_cfgpreds(node); i-- > 0; ) {
			bool     is_exc = false;
			ir_node *proj   = get_Block_cfgpred(node, i);
			ir_node *pred   = proj;

			/* ignore Bad predecessors, they will be removed later */
			if (is_Bad(pred))
				continue;

			if (is_Proj(proj)) {
				pred   = get_Proj_pred(proj);
				is_exc = is_x_except_Proj(proj);
			}
			ir_node      *pred_block = get_nodes_block(pred);
			block_info_t *bl_info    = get_block_info(pred_block, &wenv->obst);

			if (is_fragile_op(pred) && is_exc)
				bl_info->flags |= BLOCK_HAS_EXC;
			else if (is_irn_forking(pred))
				bl_info->flags |= BLOCK_HAS_COND;

			opcode = get_irn_opcode(pred);
			if (is_exc && (opcode == iro_Load || opcode == iro_Store
			               || opcode == iro_Call)) {
				ldst_info_t *ldst_info = get_ldst_info(pred, &wenv->obst);

				update_exc(ldst_info, node, i);
			}
		}
	} else if (is_memop(node)) {
		/* Just initialize a ldst_info */
		(void) get_ldst_info(node, &wenv->obst);
	}
}

/* forward */
static void reduce_node_usage(ir_node *ptr);

/**
 * Update a Load that may have lost its users.
 */
static void handle_load_update(ir_node *load)
{
	/* do NOT touch volatile loads for now */
	if (get_Load_volatility(load) == volatility_is_volatile)
		return;

	ldst_info_t *info = (ldst_info_t*)get_irn_link(load);
	if (!info->projs[pn_Load_res] && !info->projs[pn_Load_X_except]) {
		ir_node *ptr = get_Load_ptr(load);
		ir_node *mem = get_Load_mem(load);

		/* a Load whose value is neither used nor exception checked, remove it */
		exchange(info->projs[pn_Load_M], mem);
		if (info->projs[pn_Load_X_regular]) {
			ir_node *jmp = new_r_Jmp(get_nodes_block(load));
			exchange(info->projs[pn_Load_X_regular], jmp);
		}
		kill_node(load);
		reduce_node_usage(ptr);
	}
}

/**
 * A use of a node has vanished. Check if this was a Proj
 * node and update the counters.
 */
static void reduce_node_usage(ir_node *ptr)
{
	if (!is_Proj(ptr))
		return;
	if (get_irn_n_edges(ptr) > 0)
		return;

	/* this Proj is dead now */
	ir_node *pred = get_Proj_pred(ptr);
	if (is_Load(pred)) {
		ldst_info_t *info = (ldst_info_t*)get_irn_link(pred);
		info->projs[get_Proj_num(ptr)] = NULL;

		/* this node lost its result proj, handle that */
		handle_load_update(pred);
	}
}

/**
 * Kill a Load or Store and all other nodes which are not needed after
 * it has been killed.
 */
static void kill_and_reduce_usage(ir_node *node)
{
	ir_node *ptr;
	ir_node *value;
	switch (get_irn_opcode(node)) {
	case iro_Load:
		ptr   = get_Load_ptr(node);
		value = NULL;
		break;
	case iro_Store:
		ptr   = get_Store_ptr(node);
		value = get_Store_value(node);
		break;
	default:
		panic("Cannot handle node %+F", node);
	}

	kill_node(node);
	reduce_node_usage(ptr);
	if (value != NULL) {
		reduce_node_usage(value);
	}
}

static void get_base_and_offset(ir_node *ptr, base_offset_t *base_offset)
{
	/* TODO: long might not be enough, we should probably use some tarval
	 * thingy, or at least detect long overflows and abort */
	long     offset = 0;
	ir_mode *mode   = get_irn_mode(ptr);
	for (;;) {
		if (is_Add(ptr)) {
			ir_node *l = get_Add_left(ptr);
			ir_node *r = get_Add_right(ptr);

			if (get_irn_mode(l) != mode || !is_Const(r))
				break;

			offset += get_tarval_long(get_Const_tarval(r));
			ptr     = l;
		} else if (is_Sub(ptr)) {
			ir_node *l = get_Sub_left(ptr);
			ir_node *r = get_Sub_right(ptr);

			if (get_irn_mode(l) != mode || !is_Const(r))
				break;

			offset -= get_tarval_long(get_Const_tarval(r));
			ptr     = l;
		} else if (is_Sel(ptr)) {
			ir_node *index = get_Sel_index(ptr);
			if (!is_Const(index))
				break;

			ir_type *type         = get_Sel_type(ptr);
			ir_type *element_type = get_array_element_type(type);
			if (get_type_state(element_type) != layout_fixed)
				break;

			/* TODO: may overflow here */
			int size = get_type_size_bytes(element_type);
			offset += size * get_tarval_long(get_Const_tarval(index));
			ptr = get_Sel_ptr(ptr);
		} else if (is_Member(ptr)) {
			ir_entity *entity = get_Member_entity(ptr);
			ir_type   *owner  = get_entity_owner(entity);
			if (get_type_state(owner) != layout_fixed)
				break;
			offset += get_entity_offset(entity);
			ptr = get_Member_ptr(ptr);
		} else
			break;
	}

	base_offset->offset = offset;
	base_offset->base   = ptr;
}

/**
 * Checks whether the memory of the first access
 * is contained within the second one.
 */
static bool is_contained_in(
	ir_mode *const load_mode, const base_offset_t *const load_bo,
	ir_mode *const prev_mode, const base_offset_t *const prev_bo)
{
	if (load_bo->base != prev_bo->base)
		return false;

	/* ensure the load value is completely contained in the previous one */
	long delta = load_bo->offset - prev_bo->offset;
	if (delta < 0)
		return false;
	long load_mode_len = get_mode_size_bytes(load_mode);
	long prev_mode_len = get_mode_size_bytes(prev_mode);
	if (delta+load_mode_len > prev_mode_len)
		return false;

	/* simple case: previous value has the same mode */
	if (load_mode == prev_mode)
		return true;

	ir_mode_arithmetic prev_arithmetic = get_mode_arithmetic(prev_mode);
	ir_mode_arithmetic load_arithmetic = get_mode_arithmetic(load_mode);
	return (prev_arithmetic == irma_twos_complement &&
	        load_arithmetic == irma_twos_complement)
	       || (prev_arithmetic != load_arithmetic
	           && load_mode_len == prev_mode_len);
}


/**
 * This is called for load-after-load and load-after-store.
 * If possible the value of the previous load/store is transformed in a way
 * so the 2nd load can be left out/replaced by arithmetic on the previous
 * value.
 */
static ir_node *transform_previous_value(ir_mode *const load_mode,
	const long load_offset, ir_mode *const prev_mode,
	const long prev_offset, ir_node *const prev_value,
	ir_node *const block)
{
	/* simple case: previous value has the same mode */
	if (load_mode == prev_mode)
		return prev_value;

	/* two complement values can be transformed with bitops */
	long               load_mode_len   = get_mode_size_bytes(load_mode);
	long               prev_mode_len   = get_mode_size_bytes(prev_mode);
	ir_mode_arithmetic prev_arithmetic = get_mode_arithmetic(prev_mode);
	ir_mode_arithmetic load_arithmetic = get_mode_arithmetic(load_mode);
	if (prev_arithmetic == irma_twos_complement &&
	    load_arithmetic == irma_twos_complement) {
		/* produce a shift to adjust offset delta */
		long           delta = load_offset - prev_offset;
		unsigned const shift = be_get_backend_param()->byte_order_big_endian
			? prev_mode_len - load_mode_len - delta
			: delta;
		ir_node *new_value = prev_value;
		if (shift != 0) {
			ir_graph *const irg   = get_irn_irg(block);
			ir_node  *const cnst  = new_r_Const_long(irg, mode_Iu, shift * 8);
			new_value = new_r_Shr(block, new_value, cnst, prev_mode);
		}

		return new_r_Conv(block, new_value, load_mode);
	} else {
		assert(prev_arithmetic != load_arithmetic && load_mode_len == prev_mode_len);
		return new_r_Bitcast(block, prev_value, load_mode);
	}
}

static changes_t replace_load(ir_node *load, ir_node *new_value)
{
	const ldst_info_t *info = (ldst_info_t*)get_irn_link(load);
	if (info->projs[pn_Load_M])
		exchange(info->projs[pn_Load_M], get_Load_mem(load));

	changes_t res = NO_CHANGES;
	/* no exception */
	if (info->projs[pn_Load_X_except] != NULL) {
		ir_graph *irg = get_irn_irg(load);
		ir_node  *bad = new_r_Bad(irg, mode_X);
		exchange(info->projs[pn_Load_X_except], bad);
		res |= CF_CHANGED;

		assert(info->projs[pn_Load_X_regular] != NULL);
		ir_node *jmp = new_r_Jmp(get_nodes_block(load));
		exchange(info->projs[pn_Load_X_regular], jmp);
	}

	/* loads without user should already be optimized away */
	assert(info->projs[pn_Load_res] != NULL);
	exchange(info->projs[pn_Load_res], new_value);

	kill_and_reduce_usage(load);
	return res | DF_CHANGED;
}

/**
 * returns false if op cannot be reached through the X_regular proj of
 * @p prev_op or if there are no exceptions possible.
 */
static bool on_regular_path(ir_node *op, ir_node *prev_op)
{
	/* TODO: create a real test, for now we just make sure the previous node
	 * does not throw an exception. */
	(void)op;
	return !is_fragile_op(prev_op) || !ir_throws_exception(prev_op);
}

static changes_t try_load_after_store(track_load_env_t *env, ir_node *store)
{
	ir_node *const load = env->load;
	if (!on_regular_path(load, store))
		return NO_CHANGES;

	ir_node      *store_ptr = get_Store_ptr(store);
	base_offset_t prev_base_offset;
	get_base_and_offset(store_ptr, &prev_base_offset);
	ir_mode       *const load_mode   = get_Load_mode(load);
	ir_mode       *const store_mode  = get_irn_mode(get_Store_value(store));
	base_offset_t *const base_offset = &env->base_offset;

	/* load value completely contained in previous store? */
	if (is_contained_in(load_mode, base_offset, store_mode, &prev_base_offset)) {
		ir_node *const store_value = get_Store_value(store);
		ir_node *const block       = get_nodes_block(load);
		ir_node *const new_value
			= transform_previous_value(load_mode, base_offset->offset, store_mode,
			                           prev_base_offset.offset, store_value, block);
		DBG_OPT_RAW(load, new_value);
		return replace_load(load, new_value);
	}

	return NO_CHANGES;

}

static changes_t try_load_after_load(track_load_env_t *env, ir_node *prev_load)
{
	ir_node *const load = env->load;
	if (!on_regular_path(load, prev_load))
		return NO_CHANGES;

	ldst_info_t *const info       = (ldst_info_t*)get_irn_link(prev_load);
	ir_node     *const prev_value = info->projs[pn_Load_res];
	/* the other load is unused and will get removed later anyway */
	if (prev_value == NULL)
		return NO_CHANGES;

	ir_node *const prev_ptr = get_Load_ptr(prev_load);
	base_offset_t prev_base_offset;
	get_base_and_offset(prev_ptr, &prev_base_offset);
	ir_mode       *const load_mode   = get_Load_mode(load);
	ir_mode       *const prev_mode   = get_Load_mode(prev_load);
	base_offset_t *const base_offset = &env->base_offset;

	/* load value completely contained in previous load? */
	if (is_contained_in(load_mode, base_offset, prev_mode, &prev_base_offset)) {
		ir_node *const block     = get_nodes_block(load);
		ir_node *const new_value
			= transform_previous_value(load_mode, base_offset->offset, prev_mode,
			                           prev_base_offset.offset, prev_value, block);
		DBG_OPT_RAR(prev_load, load);
		return replace_load(load, new_value);
	}

	/* previous load value completely contained in current load? */
	if (is_contained_in(prev_mode, &prev_base_offset, load_mode, base_offset)) {
		ir_graph *const irg   = get_irn_irg(prev_load);
		ir_node  *const block = get_nodes_block(prev_load);

		/* Adapt pointer of previous load. */
		const long        delta         = base_offset->offset - prev_base_offset.offset;
		assert(delta <= 0);
		ir_mode    *const ptr_mode      = get_irn_mode(prev_ptr);
		ir_mode    *const ptr_mode_offs = get_reference_mode_unsigned_eq(ptr_mode);
		ir_node    *const c             = new_r_Const_long(irg, ptr_mode_offs, delta);
		ir_node    *const add           = new_r_Add(block, prev_ptr, c, ptr_mode);
		set_Load_ptr(prev_load, add);

		/* Change mode of previous load. */
		set_Load_mode(prev_load, load_mode);
		ir_node   *const original_proj = info->projs[pn_Load_res];
		ir_node   *const result_proj   = new_r_Proj(prev_load, load_mode, pn_Load_res);
		changes_t        changed       = replace_load(load, result_proj);
		ir_node   *const new_prev_value
			= transform_previous_value(prev_mode, prev_base_offset.offset, load_mode,
			                           base_offset->offset, result_proj, block);
		exchange(original_proj, new_prev_value);
		info->projs[pn_Load_res] = result_proj;
		changed |= DF_CHANGED;
		DBG_OPT_RAR(prev_load, load);
		return changed;
	}

	return NO_CHANGES;

}<commit_msg>Only optimize loads with same type.<commit_after>/*
 * This file is part of libFirm.
 * Copyright (C) 2012 University of Karlsruhe.
 */

/**
 * @file
 * @brief   Load/Store optimizations.
 * @author  <NAME>
 */
#include <string.h>

#include "array.h"
#include "be.h"
#include "dbginfo_t.h"
#include "debug.h"
#include "entity_t.h"
#include "panic.h"
#include "ircons_t.h"
#include "iredges.h"
#include "irflag_t.h"
#include "irgmod.h"
#include "irgopt.h"
#include "irgraph_t.h"
#include "irgwalk.h"
#include "irhooks.h"
#include "irmemory.h"
#include "irmode_t.h"
#include "irnode_t.h"
#include "irnodehashmap.h"
#include "iropt_dbg.h"
#include "iropt_t.h"
#include "iroptimize.h"
#include "irtools.h"
#include "set.h"
#include "tv_t.h"
#include "type_t.h"
#include "util.h"

/** The debug handle. */
DEBUG_ONLY(static firm_dbg_module_t *dbg;)

#define MAX_PROJ MAX(MAX((unsigned)pn_Load_max, (unsigned)pn_Store_max), (unsigned)pn_Call_max)

typedef enum changes_t {
	NO_CHANGES = 0,
	DF_CHANGED = (1 << 0), /**< data flow changed */
	CF_CHANGED = (1 << 1), /**< control flow changed */
	/** nodes have been created but are not reachable. This is a bad hack
	 * try hard to avoid it! */
	NODES_CREATED = (1 << 2),
} changes_t;

/**
 * walker environment
 */
typedef struct walk_env_t {
	struct obstack obst;    /**< list of all stores */
	changes_t      changes; /**< a bitmask of graph changes */
} walk_env_t;

/** A Load/Store info. */
typedef struct ldst_info_t {
	ir_node  *projs[MAX_PROJ+1]; /**< list of Proj's of this node */
	ir_node  *exc_block;         /**< the exception block if available */
	int      exc_idx;            /**< predecessor index in exception block */
	unsigned visited;            /**< visited counter for breaking loops */
} ldst_info_t;

typedef struct base_offset_t {
	ir_node *base;
	long     offset;
} base_offset_t;

typedef struct track_load_env_t {
	ir_node      *load;
	base_offset_t base_offset;
	ir_node      *ptr; /* deprecated: alternative representation of
	                      base_offset */
} track_load_env_t;

/**
 * flags for control flow.
 */
typedef enum block_flags_t {
	BLOCK_HAS_COND = (1 << 0), /**< Block has conditional control flow */
	BLOCK_HAS_EXC  = (1 << 1), /**< Block has exceptional control flow */
} block_flags_t;

/**
 * a Block info.
 */
typedef struct block_info_t {
	block_flags_t flags;  /**< flags for the block */
} block_info_t;

/** the master visited flag for loop detection. */
static unsigned master_visited;

#define INC_MASTER()       ++master_visited
#define MARK_NODE(info)    (info)->visited = master_visited
#define NODE_VISITED(info) (info)->visited >= master_visited

/**
 * get the Load/Store info of a node
 */
static ldst_info_t *get_ldst_info(ir_node *node, struct obstack *obst)
{
	ldst_info_t *info = (ldst_info_t*)get_irn_link(node);
	if (info == NULL) {
		info = OALLOCZ(obst, ldst_info_t);
		set_irn_link(node, info);
	}
	return info;
}

/**
 * get the Block info of a node
 */
static block_info_t *get_block_info(ir_node *node, struct obstack *obst)
{
	block_info_t *info = (block_info_t*)get_irn_link(node);
	if (info == NULL) {
		info = OALLOCZ(obst, block_info_t);
		set_irn_link(node, info);
	}
	return info;
}

/**
 * update the projection info for a Load/Store
 */
static changes_t update_projs(ldst_info_t *info, ir_node *proj)
{
	unsigned nr = get_Proj_num(proj);
	assert(nr <= MAX_PROJ);

	if (info->projs[nr] != NULL) {
		/* there is already one, do CSE */
		exchange(proj, info->projs[nr]);
		return DF_CHANGED;
	} else {
		info->projs[nr] = proj;
		return NO_CHANGES;
	}
}

/**
 * update the exception block info for a Load/Store node.
 *
 * @param info   the load/store info struct
 * @param block  the exception handler block for this load/store
 * @param pos    the control flow input of the block
 */
static void update_exc(ldst_info_t *info, ir_node *block, int pos)
{
	assert(info->exc_block == NULL);
	info->exc_block = block;
	info->exc_idx   = pos;
}

/**
 * walker, collects all Proj/Load/Store/Call/CopyB nodes
 *
 * walks from Start -> End
 */
static void collect_nodes(ir_node *node, void *env)
{
	walk_env_t *wenv   = (walk_env_t *)env;
	unsigned    opcode = get_irn_opcode(node);

	if (opcode == iro_Proj) {
		ir_node *pred = get_Proj_pred(node);
		opcode = get_irn_opcode(pred);

		if (opcode == iro_Load || opcode == iro_Store || opcode == iro_Call) {
			ldst_info_t *ldst_info = get_ldst_info(pred, &wenv->obst);

			wenv->changes |= update_projs(ldst_info, node);

			/*
			 * Place the Proj's to the same block as the
			 * predecessor Load. This is always ok and prevents
			 * "non-SSA" form after optimizations if the Proj
			 * is in a wrong block.
			 */
			ir_node *blk      = get_nodes_block(node);
			ir_node *pred_blk = get_nodes_block(pred);
			if (blk != pred_blk) {
				wenv->changes |= DF_CHANGED;
				set_nodes_block(node, pred_blk);
			}
		}
	} else if (opcode == iro_Block) {
		for (int i = get_Block_n_cfgpreds(node); i-- > 0; ) {
			bool     is_exc = false;
			ir_node *proj   = get_Block_cfgpred(node, i);
			ir_node *pred   = proj;

			/* ignore Bad predecessors, they will be removed later */
			if (is_Bad(pred))
				continue;

			if (is_Proj(proj)) {
				pred   = get_Proj_pred(proj);
				is_exc = is_x_except_Proj(proj);
			}
			ir_node      *pred_block = get_nodes_block(pred);
			block_info_t *bl_info    = get_block_info(pred_block, &wenv->obst);

			if (is_fragile_op(pred) && is_exc)
				bl_info->flags |= BLOCK_HAS_EXC;
			else if (is_irn_forking(pred))
				bl_info->flags |= BLOCK_HAS_COND;

			opcode = get_irn_opcode(pred);
			if (is_exc && (opcode == iro_Load || opcode == iro_Store
			               || opcode == iro_Call)) {
				ldst_info_t *ldst_info = get_ldst_info(pred, &wenv->obst);

				update_exc(ldst_info, node, i);
			}
		}
	} else if (is_memop(node)) {
		/* Just initialize a ldst_info */
		(void) get_ldst_info(node, &wenv->obst);
	}
}

/* forward */
static void reduce_node_usage(ir_node *ptr);

/**
 * Update a Load that may have lost its users.
 */
static void handle_load_update(ir_node *load)
{
	/* do NOT touch volatile loads for now */
	if (get_Load_volatility(load) == volatility_is_volatile)
		return;

	ldst_info_t *info = (ldst_info_t*)get_irn_link(load);
	if (!info->projs[pn_Load_res] && !info->projs[pn_Load_X_except]) {
		ir_node *ptr = get_Load_ptr(load);
		ir_node *mem = get_Load_mem(load);

		/* a Load whose value is neither used nor exception checked, remove it */
		exchange(info->projs[pn_Load_M], mem);
		if (info->projs[pn_Load_X_regular]) {
			ir_node *jmp = new_r_Jmp(get_nodes_block(load));
			exchange(info->projs[pn_Load_X_regular], jmp);
		}
		kill_node(load);
		reduce_node_usage(ptr);
	}
}

/**
 * A use of a node has vanished. Check if this was a Proj
 * node and update the counters.
 */
static void reduce_node_usage(ir_node *ptr)
{
	if (!is_Proj(ptr))
		return;
	if (get_irn_n_edges(ptr) > 0)
		return;

	/* this Proj is dead now */
	ir_node *pred = get_Proj_pred(ptr);
	if (is_Load(pred)) {
		ldst_info_t *info = (ldst_info_t*)get_irn_link(pred);
		info->projs[get_Proj_num(ptr)] = NULL;

		/* this node lost its result proj, handle that */
		handle_load_update(pred);
	}
}

/**
 * Kill a Load or Store and all other nodes which are not needed after
 * it has been killed.
 */
static void kill_and_reduce_usage(ir_node *node)
{
	ir_node *ptr;
	ir_node *value;
	switch (get_irn_opcode(node)) {
	case iro_Load:
		ptr   = get_Load_ptr(node);
		value = NULL;
		break;
	case iro_Store:
		ptr   = get_Store_ptr(node);
		value = get_Store_value(node);
		break;
	default:
		panic("Cannot handle node %+F", node);
	}

	kill_node(node);
	reduce_node_usage(ptr);
	if (value != NULL) {
		reduce_node_usage(value);
	}
}

static void get_base_and_offset(ir_node *ptr, base_offset_t *base_offset)
{
	/* TODO: long might not be enough, we should probably use some tarval
	 * thingy, or at least detect long overflows and abort */
	long     offset = 0;
	ir_mode *mode   = get_irn_mode(ptr);
	for (;;) {
		if (is_Add(ptr)) {
			ir_node *l = get_Add_left(ptr);
			ir_node *r = get_Add_right(ptr);

			if (get_irn_mode(l) != mode || !is_Const(r))
				break;

			offset += get_tarval_long(get_Const_tarval(r));
			ptr     = l;
		} else if (is_Sub(ptr)) {
			ir_node *l = get_Sub_left(ptr);
			ir_node *r = get_Sub_right(ptr);

			if (get_irn_mode(l) != mode || !is_Const(r))
				break;

			offset -= get_tarval_long(get_Const_tarval(r));
			ptr     = l;
		} else if (is_Sel(ptr)) {
			ir_node *index = get_Sel_index(ptr);
			if (!is_Const(index))
				break;

			ir_type *type         = get_Sel_type(ptr);
			ir_type *element_type = get_array_element_type(type);
			if (get_type_state(element_type) != layout_fixed)
				break;

			/* TODO: may overflow here */
			int size = get_type_size_bytes(element_type);
			offset += size * get_tarval_long(get_Const_tarval(index));
			ptr = get_Sel_ptr(ptr);
		} else if (is_Member(ptr)) {
			ir_entity *entity = get_Member_entity(ptr);
			ir_type   *owner  = get_entity_owner(entity);
			if (get_type_state(owner) != layout_fixed)
				break;
			offset += get_entity_offset(entity);
			ptr = get_Member_ptr(ptr);
		} else
			break;
	}

	base_offset->offset = offset;
	base_offset->base   = ptr;
}

/**
 * Checks whether the memory of the first access
 * is contained within the second one.
 */
static bool is_contained_in(
	ir_mode *const load_mode, const base_offset_t *const load_bo,
	ir_mode *const prev_mode, const base_offset_t *const prev_bo)
{
	if (load_bo->base != prev_bo->base)
		return false;

	/* ensure the load value is completely contained in the previous one */
	long delta = load_bo->offset - prev_bo->offset;
	if (delta < 0)
		return false;
	long load_mode_len = get_mode_size_bytes(load_mode);
	long prev_mode_len = get_mode_size_bytes(prev_mode);
	if (delta+load_mode_len > prev_mode_len)
		return false;

	/* simple case: previous value has the same mode */
	if (load_mode == prev_mode)
		return true;

	ir_mode_arithmetic prev_arithmetic = get_mode_arithmetic(prev_mode);
	ir_mode_arithmetic load_arithmetic = get_mode_arithmetic(load_mode);
	return (prev_arithmetic == irma_twos_complement &&
	        load_arithmetic == irma_twos_complement)
	       || (prev_arithmetic != load_arithmetic
	           && load_mode_len == prev_mode_len);
}


/**
 * This is called for load-after-load and load-after-store.
 * If possible the value of the previous load/store is transformed in a way
 * so the 2nd load can be left out/replaced by arithmetic on the previous
 * value.
 */
static ir_node *transform_previous_value(ir_mode *const load_mode,
	const long load_offset, ir_mode *const prev_mode,
	const long prev_offset, ir_node *const prev_value,
	ir_node *const block)
{
	/* simple case: previous value has the same mode */
	if (load_mode == prev_mode)
		return prev_value;

	/* two complement values can be transformed with bitops */
	long               load_mode_len   = get_mode_size_bytes(load_mode);
	long               prev_mode_len   = get_mode_size_bytes(prev_mode);
	ir_mode_arithmetic prev_arithmetic = get_mode_arithmetic(prev_mode);
	ir_mode_arithmetic load_arithmetic = get_mode_arithmetic(load_mode);
	if (prev_arithmetic == irma_twos_complement &&
	    load_arithmetic == irma_twos_complement) {
		/* produce a shift to adjust offset delta */
		long           delta = load_offset - prev_offset;
		unsigned const shift = be_get_backend_param()->byte_order_big_endian
			? prev_mode_len - load_mode_len - delta
			: delta;
		ir_node *new_value = prev_value;
		if (shift != 0) {
			ir_graph *const irg   = get_irn_irg(block);
			ir_node  *const cnst  = new_r_Const_long(irg, mode_Iu, shift * 8);
			new_value = new_r_Shr(block, new_value, cnst, prev_mode);
		}

		return new_r_Conv(block, new_value, load_mode);
	} else {
		assert(prev_arithmetic != load_arithmetic && load_mode_len == prev_mode_len);
		return new_r_Bitcast(block, prev_value, load_mode);
	}
}

static changes_t replace_load(ir_node *load, ir_node *new_value)
{
	const ldst_info_t *info = (ldst_info_t*)get_irn_link(load);
	if (info->projs[pn_Load_M])
		exchange(info->projs[pn_Load_M], get_Load_mem(load));

	changes_t res = NO_CHANGES;
	/* no exception */
	if (info->projs[pn_Load_X_except] != NULL) {
		ir_graph *irg = get_irn_irg(load);
		ir_node  *bad = new_r_Bad(irg, mode_X);
		exchange(info->projs[pn_Load_X_except], bad);
		res |= CF_CHANGED;

		assert(info->projs[pn_Load_X_regular] != NULL);
		ir_node *jmp = new_r_Jmp(get_nodes_block(load));
		exchange(info->projs[pn_Load_X_regular], jmp);
	}

	/* loads without user should already be optimized away */
	assert(info->projs[pn_Load_res] != NULL);
	exchange(info->projs[pn_Load_res], new_value);

	kill_and_reduce_usage(load);
	return res | DF_CHANGED;
}

/**
 * returns false if op cannot be reached through the X_regular proj of
 * @p prev_op or if there are no exceptions possible.
 */
static bool on_regular_path(ir_node *op, ir_node *prev_op)
{
	/* TODO: create a real test, for now we just make sure the previous node
	 * does not throw an exception. */
	(void)op;
	return !is_fragile_op(prev_op) || !ir_throws_exception(prev_op);
}

static changes_t try_load_after_store(track_load_env_t *env, ir_node *store)
{
	ir_node *const load = env->load;
	if (!on_regular_path(load, store))
		return NO_CHANGES;

	ir_node      *store_ptr = get_Store_ptr(store);
	base_offset_t prev_base_offset;
	get_base_and_offset(store_ptr, &prev_base_offset);
	ir_mode       *const load_mode   = get_Load_mode(load);
	ir_mode       *const store_mode  = get_irn_mode(get_Store_value(store));
	base_offset_t *const base_offset = &env->base_offset;

	/* load value completely contained in previous store? */
	if (is_contained_in(load_mode, base_offset, store_mode, &prev_base_offset)) {
		ir_node *const store_value = get_Store_value(store);
		ir_node *const block       = get_nodes_block(load);
		ir_node *const new_value
			= transform_previous_value(load_mode, base_offset->offset, store_mode,
			                           prev_base_offset.offset, store_value, block);
		DBG_OPT_RAW(load, new_value);
		return replace_load(load, new_value);
	}

	return NO_CHANGES;

}

static changes_t try_load_after_load(track_load_env_t *env, ir_node *prev_load)
{
	ir_node *const load = env->load;
	if (!on_regular_path(load, prev_load))
		return NO_CHANGES;

	ldst_info_t *const info       = (ldst_info_t*)get_irn_link(prev_load);
	ir_node     *const prev_value = info->projs[pn_Load_res];
	/* the other load is unused and will get removed later anyway */
	if (prev_value == NULL)
		return NO_CHANGES;

	ir_node *const prev_ptr = get_Load_ptr(prev_load);
	base_offset_t prev_base_offset;
	get_base_and_offset(prev_ptr, &prev_base_offset);
	ir_mode       *const load_mode   = get_Load_mode(load);
	ir_mode       *const prev_mode   = get_Load_mode(prev_load);
	base_offset_t *const base_offset = &env->base_offset;

	/* load value completely contained in previous load? */
	if (is_contained_in(load_mode, base_offset, prev_mode, &prev_base_offset)) {
		ir_node *const block     = get_nodes_block(load);
		ir_node *const new_value
			= transform_previous_value(load_mode, base_offset->offset, prev_mode,
			                           prev_base_offset.offset, prev_value, block);
		DBG_OPT_RAR(prev_load, load);
		return replace_load(load, new_value);
	}

	/* previous load value completely contained in current load? */
	if (get_Load_type(load) == get_Load_type(prev_load) &&
	    is_contained_in(prev_mode, &prev_base_offset, load_mode, base_offset)) {
		ir_graph *const irg   = get_irn_irg(prev_load);
		ir_node  *const block = get_nodes_block(prev_load);

		/* Adapt pointer of previous load. */
		const long        delta         = base_offset->offset - prev_base_offset.offset;
		assert(delta <= 0);
		ir_mode    *const ptr_mode      = get_irn_mode(prev_ptr);
		ir_mode    *const ptr_mode_offs = get_reference_mode_unsigned_eq(ptr_mode);
		ir_node    *const c             = new_r_Const_long(irg, ptr_mode_offs, delta);
		ir_node    *const add           = new_r_Add(block, prev_ptr, c, ptr_mode);
		set_Load_ptr(prev_load, add);

		/* Change mode of previous load. */
		set_Load_mode(prev_load, load_mode);
		ir_node   *const original_proj = info->projs[pn_Load_res];
		ir_node   *const result_proj   = new_r_Proj(prev_load, load_mode, pn_Load_res);
		changes_t        changed       = replace_load(load, result_proj);
		ir_node   *const new_prev_value
			= transform_previous_value(prev_mode, prev_base_offset.offset, load_mode,
			                           base_offset->offset, result_proj, block);
		exchange(original_proj, new_prev_value);
		info->projs[pn_Load_res] = result_proj;
		changed |= DF_CHANGED;
		DBG_OPT_RAR(prev_load, load);
		return changed;
	}

	return NO_CHANGES;

}
{'fraction_non_alphanumeric': 0.11736372883384191, 'fraction_numerical': 0.0014360078980434393, 'mean_word_length': 3.974255952380952, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 113, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1950633', 'n_tokens_mistral': 13180, 'n_tokens_neox': 12346, 'n_words': 2933}