[PATCH v1 16/18] ssa: SSA conversion, the classical way

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch implement the SSA conversion in the classical way,
via the iterated dominance frontier.
It's a drop-in replacement for simplify_symbol_usage() which
has several problems with the placement of phi-nodes.

Signed-off-by: Luc Van Oostenryck <luc.vanoostenryck@xxxxxxxxx>
---
 Makefile                          |   1 +
 linearize.h                       |   4 +
 optimize.c                        |   3 +-
 ssa.c                             | 369 ++++++++++++++++++++++++++++++++++++++
 ssa.h                             |   6 +
 symbol.h                          |   1 +
 validation/mem2reg/broken-phi02.c |   1 -
 validation/mem2reg/broken-phi03.c |   1 -
 validation/mem2reg/cond-expr5.c   |   1 -
 validation/mem2reg/quadra00.c     |   1 -
 validation/mem2reg/quadra01.c     |   1 +
 validation/mem2reg/stray-phisrc.c |   1 -
 validation/mem2reg/undef00.c      |   1 -
 validation/optim/cse-size.c       |   3 +-
 validation/optim/missing-select.c |   1 -
 validation/var-undef-partial.c    |   1 -
 16 files changed, 385 insertions(+), 11 deletions(-)
 create mode 100644 ssa.c
 create mode 100644 ssa.h

diff --git a/Makefile b/Makefile
index 43bbaa9bc..7e0d4ad92 100644
--- a/Makefile
+++ b/Makefile
@@ -58,6 +58,7 @@ LIB_OBJS += scope.o
 LIB_OBJS += show-parse.o
 LIB_OBJS += simplify.o
 LIB_OBJS += sort.o
+LIB_OBJS += ssa.o
 LIB_OBJS += sset.o
 LIB_OBJS += stats.o
 LIB_OBJS += storage.o
diff --git a/linearize.h b/linearize.h
index 0aaf11da5..5592b2c52 100644
--- a/linearize.h
+++ b/linearize.h
@@ -7,6 +7,7 @@
 #include "opcode.h"
 #include "parse.h"
 #include "symbol.h"
+#include "ptrmap.h"
 
 struct instruction;
 
@@ -17,6 +18,7 @@ struct pseudo_user {
 
 DECLARE_ALLOCATOR(pseudo_user);
 DECLARE_PTR_LIST(pseudo_user_list, struct pseudo_user);
+DECLARE_PTRMAP(phi_map, struct symbol *, pseudo_t);
 
 
 enum pseudo_type {
@@ -100,6 +102,7 @@ struct instruction {
 			struct multijmp_list *multijmp_list;
 		};
 		struct /* phi_node */ {
+			pseudo_t phi_var;		// used for SSA conversion
 			struct pseudo_list *phi_list;
 		};
 		struct /* phi source */ {
@@ -269,6 +272,7 @@ struct basic_block {
 	struct instruction_list *insns;	/* Linear list of instructions */
 	struct basic_block *idom;	/* link to the immediate dominator */
 	struct basic_block_list *doms;	/* list of BB idominated by this one */
+	struct phi_map *phi_map;
 	struct pseudo_list *needs, *defines;
 	union {
 		unsigned int nr;	/* unique id for label's names */
diff --git a/optimize.c b/optimize.c
index 2f9c7910d..429d42433 100644
--- a/optimize.c
+++ b/optimize.c
@@ -12,6 +12,7 @@
 #include "liveness.h"
 #include "flow.h"
 #include "cse.h"
+#include "ssa.h"
 
 int repeat_phase;
 
@@ -58,7 +59,7 @@ void optimize(struct entrypoint *ep)
 	 * Turn symbols into pseudos
 	 */
 	if (fpasses & PASS_MEM2REG)
-		simplify_symbol_usage(ep);
+		ssa_convert(ep);
 	if (fdump_ir & PASS_MEM2REG)
 		show_entry(ep);
 
diff --git a/ssa.c b/ssa.c
new file mode 100644
index 000000000..85f8acde2
--- /dev/null
+++ b/ssa.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: MIT
+//
+// SSA conversion
+// Copyright (C) 2005 Luc Van Oostenryck
+//
+
+#include <assert.h>
+#include "lib.h"
+#include "sset.h"
+#include "dominate.h"
+#include "flowgraph.h"
+#include "linearize.h"
+#include "flow.h"			// for convert_load_instruction()
+
+
+// Is it possible and desirable for this to be promoted to a pseudo?
+static inline bool is_promotable(struct symbol *type)
+{
+	struct symbol *member;
+	int bf_seen;
+	int nbr;
+
+	if (type->type == SYM_NODE)
+		type = type->ctype.base_type;
+	switch (type->type) {
+	case SYM_ENUM:
+	case SYM_BITFIELD:
+	case SYM_PTR:
+	case SYM_RESTRICT:	// OK, always integer types
+		return 1;
+	case SYM_STRUCT:
+		if (type->bit_size > long_ctype.bit_size)
+			return 0;
+		// we allow a single scalar field
+		// but a run of bitfields count for 1
+		nbr = 0;
+		bf_seen = 0;
+		FOR_EACH_PTR(type->symbol_list, member) {
+			if (is_bitfield_type(member)) {
+				if (bf_seen)
+					continue;
+				bf_seen = 1;
+			} else {
+				bf_seen = 0;
+			}
+			if (!is_scalar_type(member))
+				return 0;
+			if (nbr++)
+				return 0;
+		} END_FOR_EACH_PTR(member);
+		return 1;
+	case SYM_UNION:
+		// FIXME: should be like struct but has problem
+		//        when used with/for type cohercion
+		// -----> OK if only same sized integral types
+		FOR_EACH_PTR(type->symbol_list, member) {
+			if (member->bit_size != type->bit_size)
+				return 0;
+			if (!is_integral_type(member))
+				return 0;
+		} END_FOR_EACH_PTR(member);
+		return 1;
+	default:
+		break;
+	}
+	if (type->ctype.base_type == &int_type)
+		return 1;
+	if (type->ctype.base_type == &fp_type)
+		return 1;
+	return 0;
+}
+
+static bool insn_before(struct instruction *a, struct instruction *b)
+{
+	struct basic_block *bb = a->bb;
+	struct instruction *insn;
+
+	assert(b->bb == bb);
+	FOR_EACH_PTR(bb->insns, insn) {
+		if (insn == a)
+			return true;
+		if (insn == b)
+			return false;
+	} END_FOR_EACH_PTR(insn);
+	assert(0);
+}
+
+static void kill_store(struct instruction *insn)
+{
+	remove_use(&insn->src);
+	remove_use(&insn->target);
+	insn->bb = NULL;
+}
+
+static void rewrite_local_var(struct basic_block *bb, pseudo_t addr, int nbr_stores, int nbr_uses)
+{
+	struct instruction *insn;
+	pseudo_t val = NULL;
+
+	if (!bb)
+		return;
+
+	FOR_EACH_PTR(bb->insns, insn) {
+
+		if (!insn->bb || insn->src != addr)
+			continue;
+		switch (insn->opcode) {
+		case OP_LOAD:
+			if (!val)
+				val = undef_pseudo();
+			convert_load_instruction(insn, val);
+			break;
+		case OP_STORE:
+			val = insn->target;
+			// can't use kill_instruction() unless
+			// we add a fake user to val
+			kill_store(insn);
+			break;
+		}
+	} END_FOR_EACH_PTR(insn);
+}
+
+static bool rewrite_single_store(struct instruction *store)
+{
+	pseudo_t addr = store->src;
+	struct pseudo_user *pu;
+
+	FOR_EACH_PTR(addr->users, pu) {
+		struct instruction *insn = pu->insn;
+
+		if (insn->opcode != OP_LOAD)
+			continue;
+
+		// Let's try to replace the value of the load
+		// by the value from the store. This is only valid
+		// if the store dominate the load.
+
+		if (insn->bb == store->bb) {
+			// the load and the store are in the same BB
+			// we can convert if the load is after the store.
+			if (!insn_before(store, insn))
+				continue;
+		} else if (!domtree_dominates(store->bb, insn->bb)) {
+			// we can't convert this load
+			continue;
+		}
+
+		// OK, we can rewrite this load
+
+		// undefs ?
+
+		convert_load_instruction(insn, store->target);
+	} END_FOR_EACH_PTR(pu);
+
+	// is there some unconverted loads?
+	if (pseudo_user_list_size(addr->users) > 1)
+		return false;
+
+	kill_store(store);
+	return true;
+}
+
+static struct sset *processed;
+
+// we would like to know:
+// is there one or more stores?
+// are all loads & stores local/done in a single block?
+static void ssa_convert_one_var(struct entrypoint *ep, struct symbol *var)
+{
+	struct basic_block_list *alpha = NULL;
+	struct basic_block_list *idf = NULL;
+	struct basic_block *samebb = NULL;
+	struct instruction *store = NULL;
+	struct basic_block *bb;
+	struct pseudo_user *pu;
+	unsigned long mod = var->ctype.modifiers;
+	bool local = true;
+	int nbr_stores = 0;
+	int nbr_uses   = 0;
+	pseudo_t addr;
+
+	/* Never used as a symbol? */
+	addr = var->pseudo;
+	if (!addr)
+		return;
+
+	/* We don't do coverage analysis of volatiles.. */
+	if (mod & MOD_VOLATILE)
+		return;
+
+	/* ..and symbols with external visibility need more care */
+	mod &= (MOD_NONLOCAL | MOD_STATIC | MOD_ADDRESSABLE);
+	if (mod)
+		goto external_visibility;
+
+	if (!is_promotable(var))
+		return;
+
+	// 1) insert in the worklist all BBs that may modify var
+	sset_reset(processed);
+	FOR_EACH_PTR(addr->users, pu) {
+		struct instruction *insn = pu->insn;
+		struct basic_block *bb = insn->bb;
+
+		switch (insn->opcode) {
+		case OP_STORE:
+			nbr_stores++;
+			store = insn;
+			if (!sset_testset(processed, bb->nr))
+				add_bb(&alpha, bb);
+			/* fall through */
+		case OP_LOAD:
+			if (local) {
+				if (!samebb)
+					samebb = bb;
+				else if (samebb != bb)
+					local = false;
+			}
+			nbr_uses++;
+			break;
+		case OP_SYMADDR:
+			mod |= MOD_ADDRESSABLE;
+			goto external_visibility;
+		default:
+			warning(var->pos, "symbol '%s' pseudo used in unexpected way",
+				show_ident(var->ident));
+		}
+	} END_FOR_EACH_PTR(pu);
+
+	if (nbr_stores == 1) {
+		if (rewrite_single_store(store))
+			return;
+	}
+
+	// if all uses are local to a single block
+	// they can easily be rewritten and doesn't need phi-nodes
+	// FIXME: could be done for extended BB too
+	if (local) {
+		rewrite_local_var(samebb, addr, nbr_stores, nbr_uses);
+		return;
+	}
+
+	idf_compute(ep, &idf, alpha);
+	FOR_EACH_PTR(idf, bb) {
+		struct instruction *node = insert_phi_node(bb, var);
+		node->phi_var = var->pseudo;
+	} END_FOR_EACH_PTR(bb);
+	var->torename = 1;
+
+external_visibility:
+	if (mod & (MOD_NONLOCAL | MOD_STATIC))
+		return;
+	kill_dead_stores(ep, addr, !mod);
+}
+
+static pseudo_t lookup_var(struct basic_block *bb, struct symbol *var)
+{
+	do {
+		pseudo_t val = phi_map_lookup(bb->phi_map, var);
+		if (val)
+			return val;
+	} while ((bb = bb->idom));
+	return undef_pseudo();
+}
+
+static void ssa_rename_insn(struct basic_block *bb, struct instruction *insn)
+{
+	struct symbol *var;
+	pseudo_t addr;
+	pseudo_t val;
+
+	switch (insn->opcode) {
+	case OP_STORE:
+		addr = insn->src;
+		if (addr->type != PSEUDO_SYM)
+			break;
+		var = addr->sym;
+		if (!var || !var->torename)
+			break;
+		phi_map_update(&bb->phi_map, var, insn->target);
+		kill_store(insn);
+		break;
+	case OP_LOAD:
+		addr = insn->src;
+		if (addr->type != PSEUDO_SYM)
+			break;
+		var = addr->sym;
+		if (!var || !var->torename)
+			break;
+		val = lookup_var(bb, var);
+		convert_load_instruction(insn, val);
+		break;
+	case OP_PHI:
+		var = insn->type;
+		if (!var || !var->torename)
+			break;
+		phi_map_update(&bb->phi_map, var, insn->target);
+		break;
+	}
+}
+
+static void ssa_rename_phi(struct basic_block *bb, struct instruction *insn)
+{
+	struct basic_block *par;
+	struct symbol *var;
+
+	if (!insn->phi_var)
+		return;
+	var = insn->phi_var->sym;
+	if (!var->torename)
+		return;
+	FOR_EACH_PTR(bb->parents, par) {
+		struct instruction *term = delete_last_instruction(&par->insns);
+		pseudo_t val = lookup_var(par, var);
+		pseudo_t phi = alloc_phi(par, val, var);
+		phi->ident = var->ident;
+		add_instruction(&par->insns, term);
+		use_pseudo(insn, phi, add_pseudo(&insn->phi_list, phi));
+	} END_FOR_EACH_PTR(par);
+}
+
+static void ssa_rename_vars(struct entrypoint *ep)
+{
+	struct basic_block *bb;
+
+	FOR_EACH_PTR(ep->bbs, bb) {
+		struct instruction *insn;
+		FOR_EACH_PTR(bb->insns, insn) {
+			if (!insn->bb)
+				continue;
+			ssa_rename_insn(bb, insn);
+		} END_FOR_EACH_PTR(insn);
+	} END_FOR_EACH_PTR(bb);
+
+	FOR_EACH_PTR(ep->bbs, bb) {
+		struct instruction *insn;
+		FOR_EACH_PTR(bb->insns, insn) {
+			if (!insn->bb || insn->opcode != OP_PHI)
+				continue;
+			ssa_rename_phi(bb, insn);
+		} END_FOR_EACH_PTR(insn);
+	} END_FOR_EACH_PTR(bb);
+}
+
+void ssa_convert(struct entrypoint *ep)
+{
+	struct basic_block *bb;
+	pseudo_t pseudo;
+	int first, last;
+
+	// calculate the number of BBs
+	first = ep->entry->bb->nr;
+	last = first;
+	FOR_EACH_PTR(ep->bbs, bb) {
+		int nr = bb->nr;
+		if (nr > last)
+			last = nr;
+	} END_FOR_EACH_PTR(bb);
+
+	processed = sset_init(first, last);
+
+	// try to promote memory accesses to pseudos
+	FOR_EACH_PTR(ep->accesses, pseudo) {
+		ssa_convert_one_var(ep, pseudo->sym);
+	} END_FOR_EACH_PTR(pseudo);
+
+	// rename the converted accesses
+	ssa_rename_vars(ep);
+}
diff --git a/ssa.h b/ssa.h
new file mode 100644
index 000000000..cbcbc3a41
--- /dev/null
+++ b/ssa.h
@@ -0,0 +1,6 @@
+#ifndef SSA_H
+#define SSA_H
+
+void ssa_convert(struct entrypoint *ep);
+
+#endif
diff --git a/symbol.h b/symbol.h
index 5901a85aa..a4fdbcfec 100644
--- a/symbol.h
+++ b/symbol.h
@@ -173,6 +173,7 @@ struct symbol {
 					designated_init:1,
 					forced_arg:1,
 					accessed:1,
+					torename:1,
 					transparent_union:1;
 			struct expression *array_size;
 			struct ctype ctype;
diff --git a/validation/mem2reg/broken-phi02.c b/validation/mem2reg/broken-phi02.c
index 69776e0f1..5aa650716 100644
--- a/validation/mem2reg/broken-phi02.c
+++ b/validation/mem2reg/broken-phi02.c
@@ -22,7 +22,6 @@ int foo(int a, int b)
  *	is used) causes a missed select-conversion at later stage.
  *
  * check-command: test-linearize -Wno-decl $file
- * check-known-to-fail
  * check-output-ignore
  * check-output-contains: select\\.
  */
diff --git a/validation/mem2reg/broken-phi03.c b/validation/mem2reg/broken-phi03.c
index 58b479791..eff1ff8a8 100644
--- a/validation/mem2reg/broken-phi03.c
+++ b/validation/mem2reg/broken-phi03.c
@@ -23,7 +23,6 @@ int foo(int a, int b)
  *	is used) causes a missed select-conversion at later stage.
  *
  * check-command: test-linearize -Wno-decl $file
- * check-known-to-fail
  * check-output-ignore
  * check-output-contains: select\\.
  */
diff --git a/validation/mem2reg/cond-expr5.c b/validation/mem2reg/cond-expr5.c
index 62ac6c15d..a3ce5e3a9 100644
--- a/validation/mem2reg/cond-expr5.c
+++ b/validation/mem2reg/cond-expr5.c
@@ -11,7 +11,6 @@ int foo(int p, int q, int a)
 /*
  * check-name: cond-expr5
  * check-command: test-linearize -Wno-decl -fdump-ir=mem2reg $file
- * check-known-to-fail
  *
  * check-output-ignore
  * check-output-excludes: load\\.
diff --git a/validation/mem2reg/quadra00.c b/validation/mem2reg/quadra00.c
index 63b489c98..69ddc9772 100644
--- a/validation/mem2reg/quadra00.c
+++ b/validation/mem2reg/quadra00.c
@@ -20,7 +20,6 @@ int foo(int *a, int b, int c)
 /*
  * check-name: quadratic phisrc
  * check-command: test-linearize -Wno-decl $file
- * check-known-to-fail
  * check-output-ignore
  * check-output-excludes: phi\\..*, .*, .*
  * check-output-excludes: phi\\..*, .*, .*, .*
diff --git a/validation/mem2reg/quadra01.c b/validation/mem2reg/quadra01.c
index b71f46969..bab337f2b 100644
--- a/validation/mem2reg/quadra01.c
+++ b/validation/mem2reg/quadra01.c
@@ -20,6 +20,7 @@ static void foo(void) {
  * check-name: quadratic @ liveness
  * check-command: test-linearize -I. $file
  * check-timeout:
+ * check-known-to-fail
  *
  * check-output-ignore
  * check-output-excludes: phi\\.
diff --git a/validation/mem2reg/stray-phisrc.c b/validation/mem2reg/stray-phisrc.c
index e9f35c89c..0da7df7a4 100644
--- a/validation/mem2reg/stray-phisrc.c
+++ b/validation/mem2reg/stray-phisrc.c
@@ -18,7 +18,6 @@ static int foo(int **g)
 /*
  * check-name: stray phisrc
  * check-command: test-linearize -Wno-decl $file
- * check-known-to-fail
  *
  * check-output-ignore
  * check-output-excludes: phisrc\\.
diff --git a/validation/mem2reg/undef00.c b/validation/mem2reg/undef00.c
index 27f0aaa82..20ea962ce 100644
--- a/validation/mem2reg/undef00.c
+++ b/validation/mem2reg/undef00.c
@@ -13,7 +13,6 @@ static void badw(int v)
 /*
  * check-name: undef00
  * check-command: test-linearize -fdump-ir=mem2reg $file
- * check-known-to-fail
  * check-output-ignore
  * check-output-pattern(1): load\\.
  * check-output-pattern(1): load\\..*\\[UNDEF\\]
diff --git a/validation/optim/cse-size.c b/validation/optim/cse-size.c
index b75e4bfe9..e98c3cde7 100644
--- a/validation/optim/cse-size.c
+++ b/validation/optim/cse-size.c
@@ -13,6 +13,5 @@ static void foo(void)
  * check-command: test-linearize -Wno-decl $file
  *
  * check-output-ignore
- * check-output-excludes: phi\\.
- * check-output-excludes: phisrc\\.
+ * check-output-pattern(2): phi\\.
  */
diff --git a/validation/optim/missing-select.c b/validation/optim/missing-select.c
index 4796e0586..07ea008f9 100644
--- a/validation/optim/missing-select.c
+++ b/validation/optim/missing-select.c
@@ -17,7 +17,6 @@ static int foo(int **g)
 /*
  * check-name: missing-select
  * check-command: test-linearize -Wno-decl $file
- * check-known-to-fail
  *
  * check-output-ignore
  * check-output-contains: select\\.
diff --git a/validation/var-undef-partial.c b/validation/var-undef-partial.c
index 2b6658342..f1a07bea7 100644
--- a/validation/var-undef-partial.c
+++ b/validation/var-undef-partial.c
@@ -16,6 +16,5 @@ int foo(int a, int b)
  * check-description: trigger a bug in symbol/memop simplification
  * check-description: sparse-llvm is used here as semantic checker of sparse's IR
  * check-command: sparse-llvm -Wno-decl $file
- * check-known-to-fail
  * check-output-ignore
  */
-- 
2.16.2

--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Newbies FAQ]     [LKML]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Trinity Fuzzer Tool]

  Powered by Linux