
#include <iostream>
#include <algorithm>
#include <unordered_map>

#include <gcc-plugin.h>
#include <tree.h>
#include <plugin-version.h>
#include <context.h>
#include <basic-block.h>
#include <coretypes.h>
#include <diagnostic.h>
#include <gimple.h>
#include <tree-pass.h>
#include <tree-iterator.h>
#include <input.h>
#include <assert.h>
#include <cgraph.h>
#include <cfgloop.h>
#include <gimple.h>
#include <gimple-pretty-print.h>
#include <gimple-iterator.h>
#include <gimple-walk.h>
#include <internal-fn.h>
#include <ssa.h>

// This plugin is GPL compatible
int plugin_is_GPL_compatible;
static struct plugin_info this_gcc_plugin_info = { "0.01a", "gimple dump plugin" };


#define DBG(x) {}
// #define DBG(x) (x)

namespace gimple_dump {

class gimple_dump_pass : public gimple_opt_pass {
private:
	static pass_data gimple_dump_pass_data() {
		return pass_data{
			GIMPLE_PASS,
			"gimple_dump_pass",
			OPTGROUP_NONE,
			TV_NONE,
			PROP_gimple_any,
			0,
			0,
			0,
			0
		};
	}

public:
	gimple_dump_pass(gcc::context* ctx) noexcept : gimple_opt_pass(gimple_dump_pass_data(), ctx) {}

private:
	void newline_and_indent(pretty_printer *pp, int spc) {
		pp_newline(pp);
		for (int t=0;t<spc;++t)
			pp_string(pp, "\t");
	}
	void pp_tree_identifier(pretty_printer *pp, tree id) {
		std::string s;
		if (pp_translate_identifiers(pp))
			s = std::string{ identifier_to_locale(IDENTIFIER_POINTER(id)) };
		else
			s = std::string{ IDENTIFIER_POINTER(id), IDENTIFIER_POINTER(id) + IDENTIFIER_LENGTH(id) };
		std::replace(s.begin(), s.end(), '.', '_');
		std::replace(s.begin(), s.end(), '$', '_');
		if (s == "complex double")
			s = "complex";
		pp_append_text(pp, s.data(), s.data() + s.length());
	}
	void pp_c_identifier(pretty_printer *pp, const char *id) {
		// pp_string(pp, " ");
		pp_identifier(pp, id);
	}
	void pp_c_tree_decl_identifier(tree t) {
		pretty_printer pp;
		pp_needs_newline(&pp) = false;
		pp.buffer->stream = stdout;
		pp_c_tree_decl_identifier(&pp, t);
		pp_flush(&pp);
	}
	std::unordered_map<std::string, int> visited_nodes;
	std::string tree_decl_node_name(tree t, bool unqualified = false) {
		std::string name;

		if (DECL_NAME(t))
			name = IDENTIFIER_POINTER(DECL_NAME(t));
		else {
			static char xname[8];
			sprintf(xname, "U%4hx", ((unsigned short)((uintptr_t)(t) & 0xffff)));
			name = xname;
			std::replace(name.begin(), name.end(), ' ', '_');
		}

		if (!unqualified) {
			const auto it = visited_nodes.find(name);
			if (it != visited_nodes.end())
				name += "v" + std::to_string(it->second);
		}

		return name;
	}
	void tree_decl_node_visit(std::string name) {
		if (name.length() && !visited_nodes.emplace(std::make_pair(name, 0)).second)
			visited_nodes[name]++;
	}
	void pp_c_tree_decl_identifier(pretty_printer *pp, tree t) {
		pp_c_identifier(pp, tree_decl_node_name(t).c_str());
	}
	void id_expression(pretty_printer *pp, tree t) {
		switch (TREE_CODE(t)) {
		case VAR_DECL:
		case PARM_DECL:
		case CONST_DECL:
		case TYPE_DECL:
		case FUNCTION_DECL:
		case FIELD_DECL:
		case LABEL_DECL:
			pp_c_tree_decl_identifier(pp, t);
			break;

		case IDENTIFIER_NODE:
			pp_c_identifier(pp, IDENTIFIER_POINTER(t));
			break;

		default:
			std::cerr << "Unsupported ID expression" << std::endl;
			break;
		}
	}
	void dump_decl_name(pretty_printer *pp, tree node, dump_flags_t flags) {
		const tree name = DECL_NAME(node);
		if (name)
			pp_tree_identifier(pp, name);
	}
	std::string get_function_name(tree node) {
		if (CONVERT_EXPR_P(node))
			node = TREE_OPERAND(node, 0);
		const tree name = DECL_NAME(node);
		return std::string{ identifier_to_locale(IDENTIFIER_POINTER(name)) };
	}
	void dump_function_declaration(pretty_printer *pp, tree node, int spc, dump_flags_t flags) {
		bool wrote_arg = false;

		pp_space (pp);
		pp_left_paren (pp);
		auto arg = TYPE_ARG_TYPES (node);
		while (arg && arg != void_list_node && arg != error_mark_node) {
			if (wrote_arg) { pp_comma (pp); pp_space (pp); }
			wrote_arg = true;
			write_generic_node(pp, TREE_VALUE(arg), false, spc);
			arg = TREE_CHAIN (arg);
		}

		pp_right_paren(pp);
	}
	void dump_array_domain(pretty_printer *pp, tree domain, int spc, dump_flags_t flags) {
		pp_left_bracket (pp);
		if (domain) {
			const auto max = TYPE_MAX_VALUE(domain);
			if (max && tree_fits_shwi_p(max))
				pp_wide_integer(pp, tree_to_shwi(max) + 1);
			else 
				std::cerr << "Invalid array domain" << std::endl;
		}
		else
			std::cerr << "Invalid array domain" << std::endl;
		pp_right_bracket(pp);
	}
	void pretty_print_string(pretty_printer *pp, const char *str, unsigned n) {
		if (!str) return;

		for (;n; --n, ++str) {
			switch (str[0]) {
			case '\b':
				pp_string(pp, "\\b");
				break;
			case '\f':
				pp_string(pp, "\\f");
				break;
			case '\n':
				pp_string(pp, "\\n");
				break;
			case '\r':
				pp_string(pp, "\\r");
				break;
			case '\t':
				pp_string(pp, "\\t");
				break;
			case '\v':
				pp_string(pp, "\\v");
				break;
			case '\\':
				pp_string(pp, "\\\\");
				break;
			case '\"':
				pp_string(pp, "\\\"");
				break;
			case '\'':
				pp_string(pp, "\\'");
				break;
			default:
				if (str[0] || n > 1) {
					if (!ISPRINT(str[0]))
					{
						char buf[5];
						sprintf (buf, "\\x%02x", (unsigned char)str[0]);
						pp_string(pp, buf);
					}
					else
						pp_character(pp, str[0]);
					break;
				}
			}
		}
	}
	const char* op_symbol(const_tree op) {
		return op_symbol_code(TREE_CODE(op));
	}

	void write_generic_node(tree node, bool is_statement, int spc=0) {
		pretty_printer pp;
		pp_needs_newline(&pp) = false;
		pp.buffer->stream = stdout;
		write_generic_node(&pp, node, is_statement, spc);
		pp_flush(&pp);
	}
	void write_generic_node(pretty_printer *pp, tree node, bool is_statement, int spc=0) {
		if (!node) return;
		
		bool is_expr = true;
		const dump_flags_t flags = TDF_NOUID;
		const auto code = TREE_CODE(node);
		switch (code) {
		case ERROR_MARK:
			pp_string(pp, " ((ERR)) ");
			break;

		case IDENTIFIER_NODE:
			DBG(std::cout << "id!");
			pp_tree_identifier(pp, node);
			break;

		case TREE_LIST:
			std::cerr << "TREE_LIST" << std::endl;
			break;
		case TREE_BINFO:
			std::cerr << "TREE_BINFO" << std::endl;
			break;
		case TREE_VEC:
			std::cerr << "TREE_VEC" << std::endl;
			break;

		case VOID_TYPE:
		case INTEGER_TYPE:
		case REAL_TYPE:
		case FIXED_POINT_TYPE:
		case COMPLEX_TYPE:
		case VECTOR_TYPE:
		case ENUMERAL_TYPE:
		case BOOLEAN_TYPE: {
			DBG(std::cout << "bt!");
			if (!ADDR_SPACE_GENERIC_P(TYPE_ADDR_SPACE(node)))
				std::cerr << "Non generic address space" << std::endl;

			const auto tclass = TREE_CODE_CLASS(TREE_CODE(node));
			if (tclass == tcc_declaration) {
				if (DECL_NAME(node))
					dump_decl_name(pp, node, flags);
				else
					std::cerr << "Unnamed type decl" << std::endl;
			}
			else if (tclass == tcc_type) {
				if (TYPE_NAME(node)) {
					if (TREE_CODE(TYPE_NAME(node)) == IDENTIFIER_NODE)
						pp_tree_identifier(pp, TYPE_NAME(node));
					else if (TREE_CODE(TYPE_NAME(node)) == TYPE_DECL && DECL_NAME(TYPE_NAME(node)))
						dump_decl_name(pp, TYPE_NAME(node), flags);
					else
						std::cerr << "Unnamed type" << std::endl;
				}
				else if (TREE_CODE (node) == VECTOR_TYPE) {
					pp_string(pp, "dvec");
					pp_wide_integer(pp, TYPE_VECTOR_SUBPARTS(node));
					pp_space(pp);
					// write_generic_node(pp, TREE_TYPE(node), false, spc);
				}
				else if (TREE_CODE(node) == INTEGER_TYPE) {
					if (TYPE_PRECISION(node) == CHAR_TYPE_SIZE)
						pp_string(pp, (TYPE_UNSIGNED(node) ? "uint8_t" : "int8_t"));
					else if (TYPE_PRECISION(node) == SHORT_TYPE_SIZE)
						pp_string(pp, (TYPE_UNSIGNED(node) ? "uint16_t" : "int16_t"));
					else if (TYPE_PRECISION(node) == INT_TYPE_SIZE || TYPE_PRECISION(node) == LONG_TYPE_SIZE)
						pp_string(pp, (TYPE_UNSIGNED(node) ? "uint" : "int"));
					else if (TYPE_PRECISION(node) == LONG_LONG_TYPE_SIZE)
						pp_string(pp, (TYPE_UNSIGNED(node) ? "uint64_t" : "int64_t"));
					else if (TYPE_PRECISION(node) >= CHAR_TYPE_SIZE && pow2p_hwi(TYPE_PRECISION(node))) {
						pp_string(pp, (TYPE_UNSIGNED(node) ? "uint" : "int"));
						pp_decimal_int (pp, TYPE_PRECISION(node));
						pp_string(pp, "_t");
					}
					else {
						std::cerr << "Unnamed integer" << std::endl;
					}
				}
				else if (TREE_CODE(node) == COMPLEX_TYPE) {
					pp_string(pp, "complex");
					// std::cerr << "Complex type" << std::endl;
				}
				else if (TREE_CODE(node) == REAL_TYPE) {
					if (TYPE_PRECISION(node) <= 32)
						pp_string(pp, "float");
					else
						pp_string(pp, "double");
				}
				else
					std::cerr << "Unnamed type" << std::endl;
			}
			break;
		}

		case POINTER_TYPE:
		case REFERENCE_TYPE:
			std::cerr << "Pointer/reference type" << std::endl;
			break;

		case MEM_REF: {
			DBG(std::cout << "m!");
			tree op0 = node;
			if (TREE_CODE(op0) == NON_LVALUE_EXPR)
				op0 = TREE_OPERAND(op0, 0);
			if (integer_zerop(TREE_OPERAND(op0, 1))) {
				op0 = TREE_OPERAND(op0, 0);
				write_generic_node(pp, op0, false, spc);
			}
			break;
		}

		case TARGET_MEM_REF:
			std::cerr << "TARGET_MEM_REF" << std::endl;
			break;

		case RECORD_TYPE:
		case UNION_TYPE:
		case QUAL_UNION_TYPE: {
			DBG(std::cout << "r!");
			if (TYPE_NAME(node))
				id_expression(pp, TYPE_NAME(node));
        	break;
		}

		case ARRAY_TYPE: {
			DBG(std::cout << "arr!");
			auto tmp = TREE_TYPE(node);
			for (; TREE_CODE(tmp) == ARRAY_TYPE; tmp = TREE_TYPE(tmp)) {}
			write_generic_node(pp, TREE_TYPE(tmp), false, spc);

			for (tmp = node; TREE_CODE(tmp) == ARRAY_TYPE; tmp = TREE_TYPE(tmp))
				dump_array_domain(pp, TYPE_DOMAIN(tmp), 0, flags);
			break;
		}

		case INTEGER_CST: {
			DBG(std::cout << "int!");
			if (tree_fits_shwi_p (node))
				pp_wide_integer(pp, tree_to_shwi(node));
			else if (tree_fits_uhwi_p (node))
				pp_unsigned_wide_integer(pp, tree_to_uhwi(node));
			else {
				auto val = wi::to_wide(node);
				if (wi::neg_p(val, TYPE_SIGN(TREE_TYPE(node)))) {
					pp_minus (pp);
					val = -val;
				}
				print_hex(val, pp_buffer(pp)->digit_buffer);
				pp_string(pp, pp_buffer(pp)->digit_buffer);
			}

			if (TREE_OVERFLOW(node))
				std::cerr << "INTEGER_CST: Overflow" << std::endl;
			break;
		}

		case REAL_CST: {
			DBG(std::cout << "fp!");
			REAL_VALUE_TYPE d;
			if (TREE_OVERFLOW (node))
				std::cerr << "REAL_CST: Overflow" << std::endl;

			d = TREE_REAL_CST(node);
			if (REAL_VALUE_ISINF(d))
				pp_string(pp, REAL_VALUE_NEGATIVE(d) ? " -Inf" : " Inf");
			else if (REAL_VALUE_ISNAN(d))
				pp_string(pp, " NaN");
			else {
				char string[255];
				real_to_decimal(string, &d, sizeof(string), 0, 1);
				pp_string(pp, string);
			}
			break;
		}

		case FIXED_CST: {
			DBG(std::cout << "real!");
			char string[255];
			fixed_to_decimal(string, TREE_FIXED_CST_PTR(node), sizeof(string));
			pp_string(pp, string);
			break;
		}

		case STRING_CST: {
			DBG(std::cout << "str!");
			pp_string(pp, "\"");
			if (unsigned nbytes = TREE_STRING_LENGTH(node))
				pretty_print_string(pp, TREE_STRING_POINTER(node), nbytes);
			pp_string(pp, "\"");
			break;
		}

		case VECTOR_CST: {
			DBG(std::cout << "vec!");
			unsigned HOST_WIDE_INT nunits;
			if (!VECTOR_CST_NELTS(node).is_constant(&nunits)) {
				std::cerr << "VECTOR_CST: Non const dimension" << std::endl;
				break;
			}

			pp_string(pp, "dvec");
			pp_wide_integer(pp, nunits);
			pp_left_paren(pp);
			for (decltype(nunits) i=0; i<nunits; ++i) {
				if (i != 0) pp_string(pp, ", ");
				write_generic_node(pp, VECTOR_CST_ELT(node, i), false, spc);
			}
			pp_right_paren(pp);
			break;
		}

		case POLY_INT_CST:
			std::cerr << "POLY_INT_CST" << std::endl;
			break;

		case COMPLEX_CST: {
			DBG(std::cout << "complex!");
			const auto real = TREE_REALPART(node);
			const auto imag = TREE_IMAGPART(node);
			pp_string(pp, "complex");
			pp_left_paren(pp);
			write_generic_node(pp, real, false, spc);
			pp_string(pp, ", ");
			write_generic_node(pp, imag, false, spc);
			pp_right_paren(pp);

			break;
		}

		case FUNCTION_TYPE:
		case METHOD_TYPE: {
			DBG(std::cout << "funt!");
			write_generic_node(pp, TREE_TYPE(node), false, spc);
			pp_space(pp);
			if (TREE_CODE (node) == METHOD_TYPE) {
				if (TYPE_METHOD_BASETYPE(node))
					write_generic_node(pp, TYPE_NAME(TYPE_METHOD_BASETYPE(node)), false, spc);
				else
					std::cerr << "METHOD_TYPE: null method basetype" << std::endl;
				pp_colon_colon(pp);
			}
			if (TYPE_IDENTIFIER (node))
				write_generic_node(pp, TYPE_NAME(node), false, spc);
			else if (TYPE_NAME(node) && DECL_NAME(TYPE_NAME(node)))
				dump_decl_name(pp, TYPE_NAME(node), flags);
			break;
		}

		case FUNCTION_DECL:
		case CONST_DECL: {
			DBG(std::cout << "fund!");
			dump_decl_name(pp, node, flags);
			break;
		}

		case VAR_DECL:
		case PARM_DECL:
		case FIELD_DECL:
		case DEBUG_EXPR_DECL:
		case NAMESPACE_DECL:
		case NAMELIST_DECL: {
			DBG(std::cout << "v!");
			pp_c_tree_decl_identifier(pp, node);
			break;
		}

		case TYPE_DECL: {
			DBG(std::cout << "t!");
			if (DECL_IS_BUILTIN(node)) {
				// Don't print the declaration of built-in types.
				break;
			}
			if (DECL_NAME(node))
				dump_decl_name(pp, node, flags);
			else if (TYPE_NAME(TREE_TYPE(node)) != node) {
				pp_string(pp, (TREE_CODE(TREE_TYPE(node)) == UNION_TYPE ? "union" : "struct "));
				write_generic_node(pp, TREE_TYPE(node), false, spc);
			}
			break;
		}

		case LABEL_DECL:
			std::cerr << "LABEL_DECL" << std::endl;
			break;
		case RESULT_DECL:
			std::cerr << "RESULT_DECL" << std::endl;
			break;
		case COMPONENT_REF:
			std::cerr << "COMPONENT_REF" << std::endl;
			break;
		case BIT_FIELD_REF:
		case BIT_INSERT_EXPR:
			std::cerr << "BIT_*" << std::endl;
			break;
		case ARRAY_REF:
		case ARRAY_RANGE_REF:
			std::cerr << "ARRAY_*" << std::endl;
			break;

		case CONSTRUCTOR: {
			if (TREE_CLOBBER_P(node)) {}
			else if (TREE_CODE(TREE_TYPE(node)) == RECORD_TYPE || TREE_CODE(TREE_TYPE(node)) == UNION_TYPE) {
				std::cerr << "Struct" << std::endl;
			}
			else if (TREE_CODE(TREE_TYPE(node)) == ARRAY_TYPE
					&& TYPE_DOMAIN(TREE_TYPE(node))
					&& TYPE_MIN_VALUE(TYPE_DOMAIN(TREE_TYPE(node)))
					&& TREE_CODE(TYPE_MIN_VALUE(TYPE_DOMAIN(TREE_TYPE(node))))
					== INTEGER_CST) {
				std::cerr << "Array" << std::endl;
			}

			if (CONSTRUCTOR_NELTS(node)) {
				pp_string(pp, "dvec");
				pp_decimal_int(pp, CONSTRUCTOR_NELTS(node));
				pp_left_paren(pp);

				unsigned HOST_WIDE_INT ix;
				[[maybe_unused]] tree val, field;
				FOR_EACH_CONSTRUCTOR_ELT(CONSTRUCTOR_ELTS(node), ix, field, val) {
					if (val && TREE_CODE(val) == ADDR_EXPR)
						if (TREE_CODE(TREE_OPERAND(val, 0)) == FUNCTION_DECL)
							val = TREE_OPERAND (val, 0);
					if (val && TREE_CODE(val) == FUNCTION_DECL)
						dump_decl_name(pp, val, flags);
					else
						write_generic_node(pp, val, false, spc);
					if (ix != CONSTRUCTOR_NELTS(node) - 1) {
						pp_comma(pp);
						pp_space(pp);
					}
				}

				pp_right_paren(pp);
			}
			
			break;
			}	

		case COMPOUND_EXPR:
			std::cerr << "COMPOUND_EXPR" << std::endl;
			break;

		case STATEMENT_LIST: {
			DBG(std::cout << "statement_list!");
			bool first = true;
			for (tree_stmt_iterator si = tsi_start(node); !tsi_end_p(si); tsi_next(&si)) {
				if (!first)
					newline_and_indent(pp, spc);
				else
					first = false;
				write_generic_node(pp, tsi_stmt(si), false, spc);
			}
			break;
		}

		case MODIFY_EXPR:
		case INIT_EXPR: {
			DBG(std::cout << "init!");
			write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
			pp_space(pp);
			pp_equal(pp);
			pp_space(pp);
			write_generic_node(pp, TREE_OPERAND(node, 1), false, spc);
			break;
		}

		case TARGET_EXPR:
			std::cerr << "TARGET_EXPR" << std::endl;
			break;

		case DECL_EXPR: {
			DBG(std::cout << "decl!");
			print_declaration(pp, DECL_EXPR_DECL(node), 0, flags);
			is_statement = false;
			break;
		}
		
		case COND_EXPR: {
			DBG(std::cout << "cond_expr!");
			if (!TREE_TYPE(node) || TREE_TYPE(node) == void_type_node) {
				pp_string(pp, "if");
				pp_space(pp);
				pp_left_paren(pp);
				write_generic_node(pp, COND_EXPR_COND(node), false, spc);
				pp_right_paren(pp);
				pp_space(pp);

				// The lowered cond_exprs should always be printed in full.
				if (COND_EXPR_THEN(node) &&
					(IS_EMPTY_STMT(COND_EXPR_THEN(node)) || TREE_CODE(COND_EXPR_THEN(node)) == GOTO_EXPR) &&
					COND_EXPR_ELSE(node) &&
					(IS_EMPTY_STMT(COND_EXPR_ELSE(node)) || TREE_CODE(COND_EXPR_ELSE(node)) == GOTO_EXPR)) {
					write_generic_node(pp, COND_EXPR_THEN(node), true, spc);

					if (!IS_EMPTY_STMT(COND_EXPR_ELSE(node))) {
						newline_and_indent(pp, spc);
						pp_string(pp, "else ");
						write_generic_node(pp, COND_EXPR_ELSE(node), true, spc);
					}
				}
				else {
					// Output COND_EXPR_THEN.
					if (COND_EXPR_THEN(node)) {
						pp_left_brace(pp);
						newline_and_indent(pp, spc+1);
						write_generic_node(pp, COND_EXPR_THEN(node), true, spc+1);
						newline_and_indent(pp, spc);
						pp_right_brace(pp);
					}

					// Output COND_EXPR_ELSE.
					if (COND_EXPR_ELSE (node) && !IS_EMPTY_STMT(COND_EXPR_ELSE(node))) {
						newline_and_indent(pp, spc);
						pp_string(pp, "else ");
						pp_left_brace(pp);
						newline_and_indent(pp, spc+1);
						write_generic_node(pp, COND_EXPR_ELSE(node), true, spc+1);
						newline_and_indent(pp, spc);
						pp_right_brace(pp);
					}
				}
			}
			else {
				// ?: statement
				write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
				pp_space(pp);
				pp_question(pp);
				pp_space(pp);
				write_generic_node(pp, TREE_OPERAND(node, 1), false, spc);
				pp_space(pp);
				pp_colon(pp);
				pp_space(pp);
				write_generic_node(pp, TREE_OPERAND(node, 2), false, spc);
			}

			is_expr = false;
			break;
		}

		case BIND_EXPR: {
			DBG(std::cout << "bind!");
			pp_left_brace(pp);
			if (BIND_EXPR_VARS (node)) {
				pp_newline(pp);
				for (auto op0 = BIND_EXPR_VARS(node); op0; op0 = DECL_CHAIN(op0)) {
					print_declaration(pp, op0, spc+1, flags);
					pp_newline (pp);
				}
			}

			newline_and_indent(pp, spc+1);
			write_generic_node(pp, BIND_EXPR_BODY(node), true, spc+1);
			newline_and_indent(pp, spc);
			pp_right_brace (pp);
			
			is_expr = false;
			break;
		}

		case CALL_EXPR: {
			DBG(std::cout << "call_expr!");
			if (CALL_EXPR_FN (node) != NULL_TREE)
				print_call_name(pp, CALL_EXPR_FN(node), flags);
			else {
				pp_dot(pp);
				pp_string(pp, internal_fn_name(CALL_EXPR_IFN(node)));
			}

			// Print parameters.
			pp_space(pp);
			pp_left_paren(pp);
			{
				tree arg;
				call_expr_arg_iterator iter;
				FOR_EACH_CALL_EXPR_ARG(arg, iter, node) {
					write_generic_node(pp, arg, false, spc);
					if (more_call_expr_args_p(&iter)) { pp_comma(pp); pp_space(pp); }
				}
			}
			pp_right_paren(pp);
			break;
		}

		case WITH_CLEANUP_EXPR:
			std::cerr << "WITH_CLEANUP_EXPR" << std::endl;
			break;
		case CLEANUP_POINT_EXPR:
			std::cerr << "CLEANUP_POINT_EXPR" << std::endl;
			break;
		case PLACEHOLDER_EXPR:
			std::cerr << "PLACEHOLDER_EXPR" << std::endl;
			break;

		case WIDEN_SUM_EXPR:
		case WIDEN_MULT_EXPR:
		case MULT_EXPR:
		case MULT_HIGHPART_EXPR:
		case PLUS_EXPR:
		case POINTER_PLUS_EXPR:
		case POINTER_DIFF_EXPR:
		case MINUS_EXPR:
		case TRUNC_DIV_EXPR:
		case CEIL_DIV_EXPR:
		case FLOOR_DIV_EXPR:
		case ROUND_DIV_EXPR:
		case TRUNC_MOD_EXPR:
		case CEIL_MOD_EXPR:
		case FLOOR_MOD_EXPR:
		case ROUND_MOD_EXPR:
		case RDIV_EXPR:
		case EXACT_DIV_EXPR:
		case LSHIFT_EXPR:
		case RSHIFT_EXPR:
		case LROTATE_EXPR:
		case RROTATE_EXPR:
		case WIDEN_LSHIFT_EXPR:
		case BIT_IOR_EXPR:
		case BIT_XOR_EXPR:
		case BIT_AND_EXPR:
		case TRUTH_ANDIF_EXPR:
		case TRUTH_ORIF_EXPR:
		case TRUTH_AND_EXPR:
		case TRUTH_OR_EXPR:
		case TRUTH_XOR_EXPR:
		case LT_EXPR:
		case LE_EXPR:
		case GT_EXPR:
		case GE_EXPR:
		case EQ_EXPR:
		case NE_EXPR:
		case UNLT_EXPR:
		case UNLE_EXPR:
		case UNGT_EXPR:
		case UNGE_EXPR:
		case UNEQ_EXPR:
		case LTGT_EXPR:
		case ORDERED_EXPR:
		case UNORDERED_EXPR: {
			DBG(std::cout << "binaryop!");
			const char *op = op_symbol(node);
			auto op0 = TREE_OPERAND(node, 0);
			auto op1 = TREE_OPERAND(node, 1);

			// When the operands are expressions with less priority, keep semantics of the tree representation.
			if (op_prio(op0) <= op_prio(node)) {
				pp_left_paren(pp);
				write_generic_node(pp, op0, false, spc);
				pp_right_paren(pp);
			}
			else
				write_generic_node(pp, op0, false, spc);

			pp_space(pp);
			pp_string(pp, op);
			pp_space(pp);

			// When the operands are expressions with less priority, keep semantics of the tree representation.
			if (op_prio(op1) <= op_prio(node)) {
				pp_left_paren(pp);
				write_generic_node(pp, op1, false, spc);
				pp_right_paren(pp);
			}
			else
				write_generic_node(pp, op1, false, spc);
			break;
		}

		case ADDR_EXPR:
		case INDIRECT_REF: {
			DBG(std::cout << "indir!");
			// if (TREE_CODE(node) == ADDR_EXPR && 
			// 	(TREE_CODE(TREE_OPERAND(node, 0)) == STRING_CST || TREE_CODE(TREE_OPERAND(node, 0)) == FUNCTION_DECL))
			// ;	// Do not output '&' for strings and function pointers.
			// else
			// 	pp_string(pp, op_symbol(node));
			if (op_prio(TREE_OPERAND(node, 0)) < op_prio(node)) {
				pp_left_paren(pp);
				write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
				pp_right_paren(pp);
			}
			else
				write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
			break;
		}

		case NEGATE_EXPR:
		case BIT_NOT_EXPR:
		case TRUTH_NOT_EXPR:
		case PREDECREMENT_EXPR:
		case PREINCREMENT_EXPR: {
			DBG(std::cout << "unaryop!");
			pp_string(pp, op_symbol(node));
			if (op_prio(TREE_OPERAND(node, 0)) < op_prio(node)) {
				pp_left_paren(pp);
				write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
				pp_right_paren(pp);
			}
			else
				write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
			break;
		}

		case POSTDECREMENT_EXPR:
		case POSTINCREMENT_EXPR: {
			DBG(std::cout << "postop!");
			if (op_prio(TREE_OPERAND(node, 0)) < op_prio(node)) {
				pp_left_paren(pp);
				write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
				pp_right_paren(pp);
			}
			else
				write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
			pp_string(pp, op_symbol(node));
			break;
		}

		case MIN_EXPR:
		case MAX_EXPR: {
			DBG(std::cout << "minmax!");
			pp_string(pp, code == MIN_EXPR ? "min" : "max");
			pp_left_paren(pp);
			write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
			pp_string(pp, ", ");
			write_generic_node(pp, TREE_OPERAND(node, 1), false, spc);
			pp_right_paren(pp);
			break;
		}

		case ABS_EXPR: {
			DBG(std::cout << "abs!");
			pp_string(pp, "abs");
			pp_left_paren(pp);
			write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
			pp_right_paren(pp);
			break;
		}

		case RANGE_EXPR:
			std::cerr << "RANGE_EXPR" << std::endl;
			break;
		case ADDR_SPACE_CONVERT_EXPR:
			std::cerr << "ADDR_SPACE_CONVERT_EXPR" << std::endl;
			break;

		case FIXED_CONVERT_EXPR:
		case FIX_TRUNC_EXPR:
		case FLOAT_EXPR:
		CASE_CONVERT: {
			DBG(std::cout << "convert!");
			auto type = TREE_TYPE(node);
			auto op0 = TREE_OPERAND(node, 0);
			if (type != TREE_TYPE(op0)) {
				pp_left_paren(pp);
				write_generic_node(pp, type, false, spc);
				pp_right_paren(pp);
				pp_space(pp);
			}
			if (op_prio(op0) < op_prio(node))
				pp_left_paren(pp);
			write_generic_node(pp, op0, false, spc);
			if (op_prio(op0) < op_prio(node))
				pp_right_paren(pp);
			break;
		}

		case VIEW_CONVERT_EXPR:
			std::cerr << "VIEW_CONVERT_EXPR" << std::endl;
			break;
		case PAREN_EXPR:
			std::cerr << "PAREN_EXPR" << std::endl;
			break;
		case NON_LVALUE_EXPR:
			std::cerr << "NON_LVALUE_EXPR" << std::endl;
			break;
		case SAVE_EXPR:
			std::cerr << "SAVE_EXPR" << std::endl;
			break;
		case COMPLEX_EXPR:
			std::cerr << "COMPLEX_EXPR" << std::endl;
			break;
		case CONJ_EXPR:
			std::cerr << "CONJ_EXPR" << std::endl;
			break;
		case REALPART_EXPR: {
			DBG(std::cout << "real_part!");
			auto op0 = TREE_OPERAND(node, 0);
			if (op0) {
				pp_string(pp, "real");
				pp_left_paren(pp);
				write_generic_node(pp, op0, false, spc);
				pp_right_paren(pp);
			}
			break;
		}
		case IMAGPART_EXPR: {
			DBG(std::cout << "imag_part!");
			auto op0 = TREE_OPERAND(node, 0);
			if (op0) {
				pp_string(pp, "imag");
				pp_left_paren(pp);
				write_generic_node(pp, op0, false, spc);
				pp_right_paren(pp);
			}
			break;
		}
		case VA_ARG_EXPR:
			std::cerr << "VA_ARG_EXPR" << std::endl;
			break;
		case TRY_FINALLY_EXPR:
		case TRY_CATCH_EXPR:
		case CATCH_EXPR:
			std::cerr << "try/catch/finally expr" << std::endl;
			break;
		case EH_FILTER_EXPR:
			std::cerr << "EH_FILTER_EXPR" << std::endl;
			break;
		case PREDICT_EXPR:
			std::cerr << "PREDICT_EXPR" << std::endl;
			break;
		case ANNOTATE_EXPR:
			std::cerr << "ANNOTATE_EXPR" << std::endl;
			break;

		case LABEL_EXPR: {
			DBG(std::cout << "lbl!");
			auto op0 = TREE_OPERAND(node, 0);
			// if (DECL_NAME(op0)) {
			// const char *name = IDENTIFIER_POINTER(DECL_NAME(op0));
			// if (strcmp (name, "break") == 0
			// || strcmp (name, "continue") == 0)
			// break;
			// }
			write_generic_node(pp, op0, false, spc);
			pp_colon(pp);
			break;
		}

		case LOOP_EXPR: {
			DBG(std::cout << "loop!");
			pp_string(pp, "while (1) ");
			pp_left_brace(pp);
			newline_and_indent(pp, spc+1);
			write_generic_node(pp, LOOP_EXPR_BODY(node), true, spc+1);
			newline_and_indent(pp, spc+1);
			pp_right_brace (pp);

			is_expr = false;
			break;
		}

		case RETURN_EXPR: {
			DBG(std::cout << "ret!");
			pp_string(pp, "return");
			auto op0 = TREE_OPERAND(node, 0);
			if (op0) {
				pp_space(pp);
				if (TREE_CODE(op0) == MODIFY_EXPR)
					write_generic_node(pp, TREE_OPERAND(op0, 1), false, spc);
				else
					write_generic_node(pp, op0, false, spc);
			}
			break;
		}

		case EXIT_EXPR: {
			DBG(std::cout << "exit!");
			pp_string(pp, "if (");
			write_generic_node(pp, TREE_OPERAND(node, 0), false, spc);
			pp_string(pp, ") break");
			break;
		}

		case SWITCH_EXPR: {
			DBG(std::cout << "switch!");
			pp_string(pp, "switch (");
			write_generic_node(pp, SWITCH_COND(node), false, spc);
			pp_right_paren(pp);
			pp_space(pp);
			pp_left_brace(pp);
			if (SWITCH_BODY(node)) {
				newline_and_indent(pp, spc+1);
				write_generic_node(pp, SWITCH_BODY(node), true, spc+1);
			}
			newline_and_indent(pp, spc);
			pp_right_brace(pp);

			is_expr = false;
			break;
		}

		case CASE_LABEL_EXPR: {
			DBG(std::cout << "case!");
			if (CASE_LOW(node) && CASE_HIGH(node)) {
				std::cerr << "Unsupported 'case' expression" << std::endl;
			}
			else if (CASE_LOW(node)) {
				pp_string(pp, "case ");
				write_generic_node(pp, CASE_LOW(node), false, spc);
			}
			else
				pp_string(pp, "default");
			pp_colon(pp);
			break;
		}

		case OBJ_TYPE_REF:
			std::cerr << "OBJ_TYPE_REF" << std::endl;
			break;

		case SSA_NAME: {
			DBG(std::cout << "ssa!");

			if (SSA_NAME_VAR(node) || SSA_NAME_IDENTIFIER(node)) {
				const auto id = SSA_NAME_IDENTIFIER(node);
				write_generic_node(pp, id, false, spc);
				if (TREE_CODE(id) == IDENTIFIER_NODE) {
					std::string s = std::string{ IDENTIFIER_POINTER(id), IDENTIFIER_POINTER(id) + IDENTIFIER_LENGTH(id) };
					if (s.length() && (s[0]=='K' || s[0]=='h' || s[0]=='c'))
						break;
				}
			}
			// else if (SSA_NAME_IDENTIFIER(node)) {
			// 	DBG(std::cout << "ssa_id!");
			// 	pp_tree_identifier(pp, node);
			// }
			// else {
				pp_string(pp, "var");
				pp_decimal_int(pp, SSA_NAME_VERSION(node));
			// }
			break;
		}

		case WITH_SIZE_EXPR:
			std::cerr << "WITH_SIZE_EXPR" << std::endl;
			break;
		case ASSERT_EXPR:
			std::cerr << "ASSERT_EXPR" << std::endl;
			break;
		case SCEV_KNOWN:
		case SCEV_NOT_KNOWN:
			std::cerr << "SCEV*" << std::endl;
			break;
		case POLYNOMIAL_CHREC:
			std::cerr << "POLYNOMIAL_CHREC" << std::endl;
			break;
		case REALIGN_LOAD_EXPR:
			std::cerr << "REALIGN_LOAD_EXPR" << std::endl;
			break;
		case DOT_PROD_EXPR:
			std::cerr << "DOT_PROD_EXPR" << std::endl;
			break;
		case WIDEN_MULT_PLUS_EXPR:
		case WIDEN_MULT_MINUS_EXPR:
			std::cerr << "WIDEN*" << std::endl;
			break;
		case OACC_PARALLEL:
		case OACC_KERNELS:
		case OACC_DATA:
		case OACC_HOST_DATA:
		case OACC_DECLARE:
		case OACC_UPDATE:
		case OACC_ENTER_DATA:
		case OACC_EXIT_DATA:
		case OACC_CACHE:
		case OMP_PARALLEL:
		case OMP_TASK:
		case OMP_FOR:
		case OMP_SIMD:
		case OMP_DISTRIBUTE:
		case OMP_TASKLOOP:
		case OACC_LOOP:
		case OMP_TEAMS:
		case OMP_TARGET_DATA:
		case OMP_TARGET_ENTER_DATA:
		case OMP_TARGET_EXIT_DATA:
		case OMP_TARGET:
		case OMP_TARGET_UPDATE:
		case OMP_SECTIONS:
		case OMP_SECTION:
		case OMP_MASTER:
		case OMP_TASKGROUP:
		case OMP_ORDERED:
		case OMP_CRITICAL:
		case OMP_ATOMIC:
		case OMP_ATOMIC_READ:
		case OMP_ATOMIC_CAPTURE_OLD:
		case OMP_ATOMIC_CAPTURE_NEW:
		case OMP_SINGLE:
		case OMP_CLAUSE:
			std::cerr << "OMP/OACC*" << std::endl;
			break;
		case TRANSACTION_EXPR:
			std::cerr << "TRANSACTION_EXPR" << std::endl;
			break;

		case VEC_COND_EXPR:
		case VEC_PERM_EXPR:
		case VEC_SERIES_EXPR:
		case VEC_WIDEN_MULT_HI_EXPR:
		case VEC_WIDEN_MULT_LO_EXPR:
		case VEC_WIDEN_MULT_EVEN_EXPR:
		case VEC_WIDEN_MULT_ODD_EXPR:
		case VEC_WIDEN_LSHIFT_HI_EXPR:
		case VEC_WIDEN_LSHIFT_LO_EXPR:
		case VEC_DUPLICATE_EXPR:
		case VEC_UNPACK_HI_EXPR:
		case VEC_UNPACK_LO_EXPR:
		case VEC_UNPACK_FLOAT_HI_EXPR:
		case VEC_UNPACK_FLOAT_LO_EXPR:
		case VEC_PACK_TRUNC_EXPR:
		case VEC_PACK_SAT_EXPR:
		case VEC_PACK_FIX_TRUNC_EXPR:
			std::cerr << "VEC_*_EXPR" << std::endl;
			break;

		case GOTO_EXPR:
			std::cerr << "GOTO_EXPR" << std::endl;
			break;
		case ASM_EXPR:
			std::cerr << "ASM_EXPR" << std::endl;
			break;
		case BLOCK:
			std::cerr << "BLOCK" << std::endl;
			break;
		case DEBUG_BEGIN_STMT:
			std::cerr << "DEBUG_BEGIN_STMT" << std::endl;
			break;

		default:
			std::cerr << "Unknown expr: " << std::to_string(code) << std::endl;
			break;
		}

		if (is_statement && is_expr)
			pp_semicolon(pp);
	}

	int write_unary_rhs(gassign *gs, int spc=0) {
		const auto rhs_code = gimple_assign_rhs_code(gs);
		tree lhs = gimple_assign_lhs(gs);
		tree rhs = gimple_assign_rhs1(gs);

		switch (rhs_code) {
		case VIEW_CONVERT_EXPR:
		case ASSERT_EXPR:
			write_generic_node(rhs, false, spc);
			break;

		case FIXED_CONVERT_EXPR:
		case ADDR_SPACE_CONVERT_EXPR:
		case FIX_TRUNC_EXPR:
		case FLOAT_EXPR:
		CASE_CONVERT:
			std::cout << "(";
			write_generic_node(TREE_TYPE(lhs), false, spc);
			std::cout << ")";
			if (op_prio(rhs) < op_code_prio(rhs_code)) {
				std::cout << "(";
				write_generic_node(rhs, false, spc);
				std::cout << ")";
			}
			else
				write_generic_node(rhs, false, spc);
			break;

		case PAREN_EXPR:
			std::cout << "(";
			write_generic_node(rhs, false, spc);
			std::cout << ")";
			break;

		case ABS_EXPR:
			std::cout << "abs(";
			write_generic_node(rhs, false, spc);
			std::cout << ")";
			break;

		default:
			if (TREE_CODE_CLASS(rhs_code) == tcc_declaration ||
				TREE_CODE_CLASS(rhs_code) == tcc_constant ||
				TREE_CODE_CLASS(rhs_code) == tcc_reference ||
				rhs_code == SSA_NAME ||
				rhs_code == ADDR_EXPR ||
				rhs_code == CONSTRUCTOR) {
				write_generic_node(rhs, false, spc);
				break;
			}
			else if (rhs_code == BIT_NOT_EXPR)
				std::cout << "~";
			else if (rhs_code == TRUTH_NOT_EXPR)
				std::cout << "!";
			else if (rhs_code == NEGATE_EXPR)
				std::cout << "-";
			else
				std::cout << "[" << get_tree_code_name (rhs_code) << "]";

			if (op_prio(rhs) < op_code_prio(rhs_code)) {
				std::cout << "(";
				write_generic_node(rhs, false, spc);
				std::cout << ")";
			}
			else
				write_generic_node(rhs, false, spc);
			break;
		}

		return 0;
	}
	int write_binary_rhs(gassign *gs, int spc=0) {
		const auto code = gimple_assign_rhs_code(gs);

		switch (code) {
		case VEC_WIDEN_MULT_HI_EXPR:
		case VEC_WIDEN_MULT_LO_EXPR:
		case VEC_WIDEN_MULT_EVEN_EXPR:
		case VEC_WIDEN_MULT_ODD_EXPR:
		case VEC_PACK_TRUNC_EXPR:
		case VEC_PACK_SAT_EXPR:
		case VEC_PACK_FIX_TRUNC_EXPR:
		case VEC_WIDEN_LSHIFT_HI_EXPR:
		case VEC_WIDEN_LSHIFT_LO_EXPR:
		case VEC_SERIES_EXPR:
			std::cerr << "Unsuported VEC expression" << std::endl;
			return 1;

		case COMPLEX_EXPR:
		case MIN_EXPR:
		case MAX_EXPR:
			if (code != COMPLEX_EXPR) {
				for (auto p = get_tree_code_name(code); *p; p++)
					std::cout << TOLOWER(*p);
			}
			else {
				std::cout << "complex";
			}
			std::cout << "(";
			write_generic_node(gimple_assign_rhs1(gs), false, spc);
			std::cout << ", ";
			write_generic_node(gimple_assign_rhs2(gs), false, spc);
			std::cout << ")";
			break;

		default:
			if (op_prio(gimple_assign_rhs1(gs)) <= op_code_prio(code)) {
				std::cout << "(";
				write_generic_node(gimple_assign_rhs1(gs), false, spc);
				std::cout << ")";
			}
			else
				write_generic_node(gimple_assign_rhs1(gs), false, spc);
			std::cout << " " << op_symbol_code(gimple_assign_rhs_code(gs)) << " ";
			if (op_prio (gimple_assign_rhs2(gs)) <= op_code_prio (code)) {
				std::cout << "(";
				write_generic_node(gimple_assign_rhs2(gs), false, spc);
				std::cout << ")";
			}
			else
				write_generic_node(gimple_assign_rhs2(gs), false, spc);
		}

		return 0;
	}
	int write_ternary_rhs(gassign *gs, int spc=0) {
		std::cerr << "ternary assign" << std::endl;
		return 1;
	}
	int write_assign(gassign *gs, int spc=0) {
		DBG(std::cout << "assign!");

		const auto lhs = gimple_assign_lhs(gs);
		const auto rhs = gimple_assign_rhs1(gs);
		const auto lhs_code = TREE_CODE(lhs);
		const auto rhs_code = TREE_CODE(rhs);

		if (rhs_code == CONSTRUCTOR && CONSTRUCTOR_NELTS(rhs) == 0) {
			DBG(std::cout << "assign_empty_ctor!");
			return 1;
		}

		if (lhs_code == REALPART_EXPR || lhs_code == IMAGPART_EXPR) {
			DBG(std::cout << "assign_complex!");
			auto op0 = TREE_OPERAND(lhs, 0);
			if (op0) {
				if (TREE_CODE(op0) == NON_LVALUE_EXPR)
					op0 = TREE_OPERAND(op0, 0);
				if (integer_zerop(TREE_OPERAND(op0, 1)))
					op0 = TREE_OPERAND(op0, 0);
				op0 = TREE_OPERAND(op0, 0);
				
				std::string name;
				if (lhs_code == REALPART_EXPR) {
					// Declare
					name = tree_decl_node_name(op0, true);
					tree_decl_node_visit(name);
					name = tree_decl_node_name(op0);

					std::cout << "complex " << name << ";" << std::endl;
				}
				else
					name = tree_decl_node_name(op0);

				std::cout << name;
				std::cout << (TREE_CODE(lhs) == REALPART_EXPR ? ".x" : ".y");
			}
		}
		else if (lhs_code == VAR_DECL ||
				 lhs_code == PARM_DECL ||
				 lhs_code == FIELD_DECL ||
				 lhs_code == DEBUG_EXPR_DECL ||
				 lhs_code == NAMESPACE_DECL ||
				 lhs_code == NAMELIST_DECL) {
			DBG(std::cout << "assign_decl!");
			auto name = tree_decl_node_name(lhs, true);
			tree_decl_node_visit(name);
			name = tree_decl_node_name(lhs);

			write_generic_node(TREE_TYPE(lhs), false, spc);
			std::cout << " " << name << ";" << std::endl;

			std::cout << name;
		}
		else {
			DBG(std::cout << "assign_other!");

			write_generic_node(TREE_TYPE(lhs), false, spc);
			std::cout << " ";
			
			auto op0 = lhs;
			if (lhs_code == MEM_REF) {
				if (TREE_CODE(op0) == NON_LVALUE_EXPR)
					op0 = TREE_OPERAND(op0, 0);
				if (integer_zerop(TREE_OPERAND(op0, 1)))
					op0 = TREE_OPERAND(op0, 0);
				if (TREE_CODE(op0) == ADDR_EXPR || TREE_CODE(op0) == INDIRECT_REF) {
					op0 = TREE_OPERAND(op0, 0);

					auto name = tree_decl_node_name(op0, true);
					tree_decl_node_visit(name);
					name = tree_decl_node_name(op0);

					std::cout << name << ";" << std::endl;
				}
			}

			write_generic_node(op0, false, spc);
		}
		std::cout << " = ";

		int ret = 0;
		if (gimple_num_ops(gs) == 2)
			ret = write_unary_rhs(gs, spc);
		else if (gimple_num_ops(gs) == 3)
			ret = write_binary_rhs(gs, spc);
		else if (gimple_num_ops(gs) == 4)
			ret = write_ternary_rhs(gs, spc);
		else
			gcc_unreachable();
		std::cout << ";" << std::endl;

		return ret;
	}

	void write_call_args(pretty_printer *buffer, gcall *gs, dump_flags_t flags) {
		size_t i = 0;

		// Pretty print first arg to certain internal fns.
		if (gimple_call_internal_p(gs)) {
			const char *const *enums = nullptr;
			unsigned limit = 0;

			switch (gimple_call_internal_fn(gs)) {
				case IFN_UNIQUE:
			#define DEF(X) #X
				static const char *const unique_args[] = {IFN_UNIQUE_CODES};
			#undef DEF
				enums = unique_args;
				
				limit = ARRAY_SIZE(unique_args);
				break;
				
				case IFN_GOACC_LOOP:
			#define DEF(X) #X
				static const char *const loop_args[] = {IFN_GOACC_LOOP_CODES};
			#undef DEF
				enums = loop_args;
				limit = ARRAY_SIZE(loop_args);
				break;

				case IFN_GOACC_REDUCTION:
			#define DEF(X) #X
				static const char *const reduction_args[]
					= {IFN_GOACC_REDUCTION_CODES};
			#undef DEF
				enums = reduction_args;
				limit = ARRAY_SIZE(reduction_args);
				break;

				default:
				break;
			}
			if (limit) {
				tree arg0 = gimple_call_arg (gs, 0);
				HOST_WIDE_INT v;

				if (TREE_CODE(arg0) == INTEGER_CST && 
					tree_fits_shwi_p(arg0) && 
					(v = tree_to_shwi(arg0)) >= 0 && v < limit) {
					++i;
					pp_string(buffer, enums[v]);
				}
			}
		}

		for (; i<gimple_call_num_args(gs); ++i) {
			if (i)
				pp_string(buffer, ", ");
			write_generic_node(buffer, gimple_call_arg(gs, i), false, 0);
		}
	}

	std::string call_lhs_name = {};
	int write_call(gcall *gs, int spc=0) {
		DBG(std::cout << "call!");

		pretty_printer pp;
		pp_needs_newline(&pp) = false;
		pp.buffer->stream = stdout;

		const dump_flags_t flags = TDF_NOUID;
		tree lhs = gimple_call_lhs(gs);
		tree fn = gimple_call_fn(gs);

		if (lhs) {
			DBG(std::cout << "call_lhs!");

			auto name = tree_decl_node_name(lhs, true);
			tree_decl_node_visit(name);
			name = tree_decl_node_name(lhs);

			write_generic_node(&pp, TREE_TYPE(lhs), false, spc);
			pp_space(&pp);
			
			write_generic_node(&pp, lhs, false, spc);
			if (fn) 
				pp_string(&pp, " = ");
			else
				call_lhs_name = "var" + std::to_string(SSA_NAME_VERSION(lhs));
		}

		if (fn) {		
			DBG(std::cout << "call_fn!");

			print_call_name(&pp, fn, flags);
			pp_left_paren(&pp);
			write_call_args(&pp, gs, flags);
			pp_right_paren(&pp);
		}

		pp_semicolon(&pp);
		pp_newline(&pp);

		pp_flush(&pp);

		return 0;
	}

	int write_cond(gcond *gs, int spc=0) {
		DBG(std::cout << "cond!");

		pretty_printer pp;
		pp_needs_newline(&pp) = false;
		pp.buffer->stream = stdout;

		pp_string(&pp, "if (");
		write_generic_node(&pp, gimple_cond_lhs(gs), false, spc);
		pp_space(&pp);
		pp_string(&pp, op_symbol_code(gimple_cond_code(gs)));
		pp_space(&pp);
		write_generic_node(&pp, gimple_cond_rhs(gs), false, spc);
		pp_right_paren(&pp);
		pp_newline(&pp);

		if (call_lhs_name.length()) {
			pp_string(&pp, "\t");
			pp_string(&pp, call_lhs_name.c_str());
			pp_string(&pp, " = ");
		}

		pp_flush(&pp);

		if (gimple_cond_true_label(gs)) {
			std::cerr << "cond: goto statement" << std::endl;
			return 1;
		}
		if (gimple_cond_false_label(gs)) {
			std::cerr << "cond: goto statement" << std::endl;
			return 1;
		}

		return 0;
	}

	int write_return(greturn *gs, int spc=0) {
		tree t;

		t = gimple_return_retval(gs);
		if (t) {
			std::cout << "return ";
			write_generic_node(t, false, spc);
			std::cout << ";" << std::endl;
		}
		else
			std::cout << "return;" << std::endl;

		return 0;
	}

	int write_gimple(gimple* gs) {
		switch (gimple_code(gs)) {
		case GIMPLE_ASSIGN: {
			call_lhs_name = {};
			return write_assign(as_a<gassign*>(gs));
		}
		case GIMPLE_CALL:{
			call_lhs_name = {};
			return write_call(as_a<gcall*>(gs));
		}
		case GIMPLE_COND:{
			return write_cond(as_a<gcond*>(gs));
		}
		case GIMPLE_RETURN:{
			call_lhs_name = {};
			return write_return(as_a<greturn*>(gs));
		}
		
		// Unsuported GIMPLEs
		case GIMPLE_ASM:
			std::cerr << "GIMPLE_ASM"<< std::endl;
			return 1;
		case GIMPLE_BIND:
			std::cerr << "GIMPLE_BIND"<< std::endl;
			return 1;
		case GIMPLE_LABEL:
			std::cerr << "GIMPLE_LABEL"<< std::endl;
			return 1;
		case GIMPLE_GOTO:
			std::cerr << "GIMPLE_GOTO"<< std::endl;
			return 1;
		case GIMPLE_NOP:
			std::cerr << "GIMPLE_NOP"<< std::endl;
			return 1;
		case GIMPLE_SWITCH:
			std::cerr << "GIMPLE_SWITCH"<< std::endl;
			return 1;
		case GIMPLE_TRY:
			std::cerr << "GIMPLE_TRY"<< std::endl;
			return 1;
		case GIMPLE_PHI:
			std::cerr << "GIMPLE_PHI"<< std::endl;
			return 1;
		case GIMPLE_OMP_PARALLEL:
			std::cerr << "GIMPLE_OMP_PARALLEL"<< std::endl;
			return 1;
		case GIMPLE_OMP_TASK:
			std::cerr << "GIMPLE_OMP_TASK"<< std::endl;
			return 1;
		case GIMPLE_OMP_ATOMIC_LOAD:
			std::cerr << "GIMPLE_OMP_ATOMIC_LOAD"<< std::endl;
			return 1;
		case GIMPLE_OMP_ATOMIC_STORE:
			std::cerr << "GIMPLE_OMP_ATOMIC_STORE"<< std::endl;
			return 1;
		case GIMPLE_OMP_FOR:
			std::cerr << "GIMPLE_OMP_FOR"<< std::endl;
			return 1;
		case GIMPLE_OMP_CONTINUE:
			std::cerr << "GIMPLE_OMP_CONTINUE"<< std::endl;
			return 1;
		case GIMPLE_OMP_SINGLE:
			std::cerr << "GIMPLE_OMP_SINGLE"<< std::endl;
			return 1;
		case GIMPLE_OMP_TARGET:
			std::cerr << "GIMPLE_OMP_TARGET"<< std::endl;
			return 1;
		case GIMPLE_OMP_TEAMS:
			std::cerr << "GIMPLE_OMP_TEAMS"<< std::endl;
			return 1;
		case GIMPLE_OMP_RETURN:
			std::cerr << "GIMPLE_OMP_RETURN"<< std::endl;
			return 1;
		case GIMPLE_OMP_SECTIONS:
			std::cerr << "GIMPLE_OMP_SECTIONS"<< std::endl;
			return 1;
		case GIMPLE_OMP_SECTIONS_SWITCH:
			std::cerr << "GIMPLE_OMP_SECTIONS_SWITCH"<< std::endl;
			return 1;
		case GIMPLE_OMP_TASKGROUP:
			std::cerr << "GIMPLE_OMP_TASKGROUP"<< std::endl;
			return 1;
		case GIMPLE_OMP_MASTER:
			std::cerr << "GIMPLE_OMP_MASTER"<< std::endl;
			return 1;
		case GIMPLE_OMP_GRID_BODY:
			std::cerr << "GIMPLE_OMP_GRID_BODY"<< std::endl;
			return 1;
		case GIMPLE_OMP_ORDERED:
			std::cerr << "GIMPLE_OMP_ORDERED"<< std::endl;
			return 1;
		case GIMPLE_OMP_CRITICAL:
			std::cerr << "GIMPLE_OMP_CRITICAL"<< std::endl;
			return 1;
		case GIMPLE_CATCH:
			std::cerr << "GIMPLE_CATCH"<< std::endl;
			return 1;
		case GIMPLE_EH_FILTER:
			std::cerr << "GIMPLE_EH_FILTER"<< std::endl;
			return 1;
		case GIMPLE_EH_MUST_NOT_THROW:
			std::cerr << "GIMPLE_EH_MUST_NOT_THROW"<< std::endl;
			return 1;
		case GIMPLE_EH_ELSE:
			std::cerr << "GIMPLE_EH_ELSE"<< std::endl;
			return 1;
		case GIMPLE_RESX:
			std::cerr << "GIMPLE_RESX"<< std::endl;
			return 1;
		case GIMPLE_EH_DISPATCH:
			std::cerr << "GIMPLE_EH_DISPATCH"<< std::endl;
			return 1;
		case GIMPLE_DEBUG:
			std::cerr << "GIMPLE_DEBUG"<< std::endl;
			return 1;
		case GIMPLE_PREDICT:
			std::cerr << "GIMPLE_PREDICT"<< std::endl;
			return 1;
		case GIMPLE_TRANSACTION:
			std::cerr << "GIMPLE_TRANSACTION"<< std::endl;
			return 1;
		default:
			std::cerr << "Unknown GIMPLE!" << std::endl;
			return 1;
		}
	}

public:
	static void finish(void *gcc_data, void *user_data) {}

	virtual unsigned int execute(function* fun) override {
		const auto name = get_function_name(fun->decl);
		if (name == "pow" || name == "erf" || name == "exp" || name == "pow.constprop")
			return 0;

		{
			pretty_printer pp;
			pp_needs_newline(&pp) = false;
			pp.buffer->stream = stdout;

			
			pp_string(&pp, "auto ");
			pp_append_text(&pp, name.data(), name.data() + name.length());
			pp_string(&pp, "(const double K, const complex Ga, const complex Gb, const complex erf0, const double h, const double c0, const double c1, const double c2, const double c3, const double c4) {\n");

			pp_flush(&pp);
		}

		basic_block bb;
		FOR_ALL_BB_FN(bb, fun) {
			auto seq = bb_seq(bb);
			for (gimple_stmt_iterator gsi = gsi_start(seq); !gsi_end_p(gsi); gsi_next(&gsi)) {
				gimple* g = gsi_stmt(gsi);
				write_gimple(g);
			}
		}

		std::cout << "}" << std::endl;

		return 0;
	}
	virtual gimple_dump_pass *clone() override { return this; }
};

}


void print_info(plugin_name_args *plugin_info, plugin_gcc_version *version) {
	std::cout << "Plugin info" << std::endl
		<< "===========" << std::endl << std::endl
		<< "Base name: " << plugin_info->base_name << std::endl
		<< "Full name: " << plugin_info->full_name << std::endl
		<< "Number of arguments of this plugin:" << plugin_info->argc << std::endl;

	for (int i = 0; i < plugin_info->argc; ++i)
		std::cout << "Argument " << i << ": Key: " << plugin_info->argv[i].key << ". Value: " << plugin_info->argv[i].value << std::endl;
	if (plugin_info->version)
		std::cout << "Version string of the plugin: " << plugin_info->version << std::endl;
	if (plugin_info->help)
		std::cout << "Help string of the plugin: " << plugin_info->help << std::endl;

	std::cout << std::endl
		<< "Version info" << std::endl
		<< "============" << std::endl << std::endl
		<< "Base version: " << version->basever << std::endl
		<< "Date stamp: " << version->datestamp << std::endl
		<< "Dev phase: " << version->devphase << std::endl
		<< "Revision: " << version->devphase << std::endl
		<< "Configuration arguments: " << version->configuration_arguments << std::endl << std::endl;
}
 
int plugin_init (plugin_name_args *plugin_info, plugin_gcc_version *version) {
	if (!plugin_default_version_check(version, &gcc_version)) {
		std::cerr << "This GCC plugin is for version " << GCCPLUGIN_VERSION_MAJOR << "." << GCCPLUGIN_VERSION_MINOR << std::endl;
		return 1;
	}

	register_callback(plugin_info->base_name, PLUGIN_INFO, nullptr, &this_gcc_plugin_info);

	// Register the phase right after cfg
	struct register_pass_info pass_info;

	pass_info.pass = new gimple_dump::gimple_dump_pass(g); // "g" is a global gcc::context pointer
	pass_info.reference_pass_name = "*all_optimizations_g";
	pass_info.ref_pass_instance_number = 1;
	pass_info.pos_op = PASS_POS_INSERT_AFTER;
	register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &pass_info);
	register_callback(plugin_info->base_name, PLUGIN_FINISH, gimple_dump::gimple_dump_pass::finish, nullptr);

	return 0;
}

