Skip to content

Commit ecdec96

Browse files
committed
Enhance SSA/peephole optimization separation
This adds constant folding optimizations at SSA level while moving self- operation patterns to peephole optimizer for better separation. SSA enhancements (eval_algebraic): - Identity elements: x+0, 0+x, x*1, 1*x, x/1, x&(-1), x|0, x^0, x<<0, x>>0 - Absorbing elements: x*0, 0*x, x&0, x|-1 - Strength reduction: x*2^n → x<<n, x/2^n → x>>n - Special cases: 0-x → -x Peephole additions: - Self-operations: x-x→0, x^x→0, x&x→x, x|x→x This separation ensures compile-time optimizations happen early at SSA level while register-based patterns are handled post-allocation in peephole.
1 parent d19f238 commit ecdec96

File tree

2 files changed

+242
-0
lines changed

2 files changed

+242
-0
lines changed

src/peephole.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,49 @@ bool insn_fusion(ph2_ir_t *ph2_ir)
223223
return true;
224224
}
225225

226+
/* Self-operation patterns: x op x → simplified form
227+
* These patterns are handled at peephole level where register allocation
228+
* makes it easy to detect when both operands are the same.
229+
*/
230+
if (next && next->src0 == next->src1) {
231+
switch (next->op) {
232+
case OP_sub:
233+
/* Pattern: {sub x, x} → {li 0} (x - x = 0) */
234+
ph2_ir->op = OP_load_constant;
235+
ph2_ir->src0 = 0;
236+
ph2_ir->dest = next->dest;
237+
ph2_ir->next = next->next;
238+
return true;
239+
240+
case OP_bit_xor:
241+
/* Pattern: {xor x, x} → {li 0} (x ^ x = 0) */
242+
ph2_ir->op = OP_load_constant;
243+
ph2_ir->src0 = 0;
244+
ph2_ir->dest = next->dest;
245+
ph2_ir->next = next->next;
246+
return true;
247+
248+
case OP_bit_and:
249+
/* Pattern: {and x, x} → {mov x} (x & x = x) */
250+
ph2_ir->op = OP_assign;
251+
ph2_ir->src0 = next->src0;
252+
ph2_ir->dest = next->dest;
253+
ph2_ir->next = next->next;
254+
return true;
255+
256+
case OP_bit_or:
257+
/* Pattern: {or x, x} → {mov x} (x | x = x) */
258+
ph2_ir->op = OP_assign;
259+
ph2_ir->src0 = next->src0;
260+
ph2_ir->dest = next->dest;
261+
ph2_ir->next = next->next;
262+
return true;
263+
264+
default:
265+
break;
266+
}
267+
}
268+
226269
/* Extended multiplicative identity (operand position variant)
227270
* Handles the case where constant 1 is in src0 position of multiplication
228271
*/

src/ssa.c

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,202 @@ bool eval_const_unary(insn_t *insn)
17951795
return true;
17961796
}
17971797

1798+
/* SSA-level constant folding with algebraic simplifications.
1799+
* Only handle cases where we have known constants.
1800+
* Non-constant patterns (x-x, x^x, etc.) are handled by peephole optimizer.
1801+
*/
1802+
bool eval_algebraic(insn_t *insn)
1803+
{
1804+
if (!insn || !insn->rd)
1805+
return false;
1806+
1807+
/* Only optimize when we have actual constants */
1808+
bool has_const = (insn->rs1 && insn->rs1->is_const) ||
1809+
(insn->rs2 && insn->rs2->is_const);
1810+
if (!has_const)
1811+
return false;
1812+
1813+
switch (insn->opcode) {
1814+
case OP_add:
1815+
/* x + 0 = x or 0 + x = x */
1816+
if ((insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 0)) {
1817+
insn->opcode = OP_assign;
1818+
insn->rs2 = NULL;
1819+
return true;
1820+
}
1821+
if ((insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == 0)) {
1822+
insn->opcode = OP_assign;
1823+
insn->rs1 = insn->rs2;
1824+
insn->rs2 = NULL;
1825+
return true;
1826+
}
1827+
break;
1828+
1829+
case OP_sub:
1830+
/* x - 0 = x */
1831+
if (insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 0) {
1832+
insn->opcode = OP_assign;
1833+
insn->rs2 = NULL;
1834+
return true;
1835+
}
1836+
/* 0 - x = -x */
1837+
if (insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == 0) {
1838+
insn->opcode = OP_negate;
1839+
insn->rs1 = insn->rs2;
1840+
insn->rs2 = NULL;
1841+
return true;
1842+
}
1843+
break;
1844+
1845+
case OP_mul:
1846+
/* x * 0 = 0 or 0 * x = 0 */
1847+
if ((insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == 0) ||
1848+
(insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 0)) {
1849+
insn->opcode = OP_load_constant;
1850+
insn->rd->is_const = true;
1851+
insn->rd->init_val = 0;
1852+
insn->rs1 = NULL;
1853+
insn->rs2 = NULL;
1854+
return true;
1855+
}
1856+
/* x * 1 = x */
1857+
if (insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 1) {
1858+
insn->opcode = OP_assign;
1859+
insn->rs2 = NULL;
1860+
return true;
1861+
}
1862+
/* 1 * x = x */
1863+
if (insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == 1) {
1864+
insn->opcode = OP_assign;
1865+
insn->rs1 = insn->rs2;
1866+
insn->rs2 = NULL;
1867+
return true;
1868+
}
1869+
/* Strength reduction: x * power-of-2 → x << shift */
1870+
if (insn->rs2 && insn->rs2->is_const) {
1871+
int val = insn->rs2->init_val;
1872+
if (val > 0 && (val & (val - 1)) == 0) {
1873+
int shift = 0;
1874+
while ((1 << shift) != val)
1875+
shift++;
1876+
insn->opcode = OP_lshift;
1877+
insn->rs2->init_val = shift;
1878+
return true;
1879+
}
1880+
}
1881+
break;
1882+
1883+
case OP_div:
1884+
/* x / 1 = x */
1885+
if (insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 1) {
1886+
insn->opcode = OP_assign;
1887+
insn->rs2 = NULL;
1888+
return true;
1889+
}
1890+
/* Strength reduction: x / power-of-2 → x >> shift (unsigned) */
1891+
if (insn->rs2 && insn->rs2->is_const) {
1892+
int val = insn->rs2->init_val;
1893+
if (val > 0 && (val & (val - 1)) == 0) {
1894+
int shift = 0;
1895+
while ((1 << shift) != val)
1896+
shift++;
1897+
insn->opcode = OP_rshift;
1898+
insn->rs2->init_val = shift;
1899+
return true;
1900+
}
1901+
}
1902+
break;
1903+
1904+
case OP_bit_and:
1905+
/* x & 0 = 0 */
1906+
if ((insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == 0) ||
1907+
(insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 0)) {
1908+
insn->opcode = OP_load_constant;
1909+
insn->rd->is_const = true;
1910+
insn->rd->init_val = 0;
1911+
insn->rs1 = NULL;
1912+
insn->rs2 = NULL;
1913+
return true;
1914+
}
1915+
/* x & -1 = x */
1916+
if (insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == -1) {
1917+
insn->opcode = OP_assign;
1918+
insn->rs2 = NULL;
1919+
return true;
1920+
}
1921+
/* -1 & x = x */
1922+
if (insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == -1) {
1923+
insn->opcode = OP_assign;
1924+
insn->rs1 = insn->rs2;
1925+
insn->rs2 = NULL;
1926+
return true;
1927+
}
1928+
break;
1929+
1930+
case OP_bit_or:
1931+
/* x | 0 = x */
1932+
if (insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 0) {
1933+
insn->opcode = OP_assign;
1934+
insn->rs2 = NULL;
1935+
return true;
1936+
}
1937+
/* 0 | x = x */
1938+
if (insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == 0) {
1939+
insn->opcode = OP_assign;
1940+
insn->rs1 = insn->rs2;
1941+
insn->rs2 = NULL;
1942+
return true;
1943+
}
1944+
/* x | -1 = -1 or -1 | x = -1 */
1945+
if ((insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == -1) ||
1946+
(insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == -1)) {
1947+
insn->opcode = OP_load_constant;
1948+
insn->rd->is_const = true;
1949+
insn->rd->init_val = -1;
1950+
insn->rs1 = NULL;
1951+
insn->rs2 = NULL;
1952+
return true;
1953+
}
1954+
break;
1955+
1956+
case OP_bit_xor:
1957+
/* x ^ 0 = x */
1958+
if (insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 0) {
1959+
insn->opcode = OP_assign;
1960+
insn->rs2 = NULL;
1961+
return true;
1962+
}
1963+
/* 0 ^ x = x */
1964+
if (insn->rs1 && insn->rs1->is_const && insn->rs1->init_val == 0) {
1965+
insn->opcode = OP_assign;
1966+
insn->rs1 = insn->rs2;
1967+
insn->rs2 = NULL;
1968+
return true;
1969+
}
1970+
break;
1971+
1972+
case OP_lshift:
1973+
case OP_rshift:
1974+
/* x << 0 = x or x >> 0 = x */
1975+
if (insn->rs2 && insn->rs2->is_const && insn->rs2->init_val == 0) {
1976+
insn->opcode = OP_assign;
1977+
insn->rs2 = NULL;
1978+
return true;
1979+
}
1980+
break;
1981+
1982+
default:
1983+
break;
1984+
}
1985+
1986+
return false;
1987+
}
1988+
1989+
/* NOTE: Self-operation patterns (x-x, x^x, x&x, x|x) are better handled
1990+
* at peephole level after register allocation, where we can see actual
1991+
* register usage patterns. Removed from SSA level.
1992+
*/
1993+
17981994
bool const_folding(insn_t *insn)
17991995
{
18001996
if (mark_const(insn))
@@ -1803,6 +1999,9 @@ bool const_folding(insn_t *insn)
18031999
return true;
18042000
if (eval_const_unary(insn))
18052001
return true;
2002+
if (eval_algebraic(insn))
2003+
return true;
2004+
18062005
return false;
18072006
}
18082007

0 commit comments

Comments
 (0)