From a612b68e4ca9b396258d22b950b04721c67e0eaf Mon Sep 17 00:00:00 2001 From: Happy Date: Sun, 1 Mar 2026 08:03:18 +0000 Subject: [PATCH] optimizer: add De Morgan's law transformations for bitwise and boolean ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add four new algebraic simplification rules based on De Morgan's laws: Bitwise: - bnot(a) & bnot(b) → bnot(a | b) (saves one i64.xor instruction) - bnot(a) | bnot(b) → bnot(a & b) (saves one i64.xor instruction) Boolean: - not(a) and not(b) → not(a or b) (saves one i32.eqz instruction) - not(a) or not(b) → not(a and b) (saves one i32.eqz instruction) These transformations reduce two negation operations to one while preserving semantic equivalence, producing smaller and faster WASM output for bitwise-heavy code patterns. Includes 10 new tests covering unit, complex sub-expression, negative (no-op), and end-to-end pipeline cases. --- lib/firebird/compiler/optimizer.ex | 24 ++++ test/compiler/optimizer_algebraic_test.exs | 130 +++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/lib/firebird/compiler/optimizer.ex b/lib/firebird/compiler/optimizer.ex index 5beef4b..22ab4f5 100644 --- a/lib/firebird/compiler/optimizer.ex +++ b/lib/firebird/compiler/optimizer.ex @@ -30,6 +30,8 @@ defmodule Firebird.Compiler.Optimizer do - Boolean short-circuit: `0 and x` → `0`, `1 or x` → `1` - Comparison self-reflexivity: `x == x` → `1`, `x < x` → `0` - Double negation: `not(not(x))` → `x` + - De Morgan's laws: `bnot(a) & bnot(b)` → `bnot(a | b)`, `bnot(a) | bnot(b)` → `bnot(a & b)`, + `not(a) and not(b)` → `not(a or b)`, `not(a) or not(b)` → `not(a and b)` - Comparison inversion: `not(x == y)` → `x != y`, `not(x < y)` → `x >= y`, etc. 8. **Arithmetic cancellation** - Cancel inverse operation pairs: - `(x + a) - a` → `x`, `(x - a) + a` → `x` (add/sub inverse) @@ -1179,6 +1181,28 @@ defmodule Firebird.Compiler.Optimizer do # not(not(x)) → x def algebraic_simplify({:unaryop, :not, {:unaryop, :not, expr}}), do: algebraic_simplify(expr) + # --- De Morgan's laws (bitwise) --- + # bnot(a) & bnot(b) → bnot(a | b) (saves one bnot/i64.xor instruction) + def algebraic_simplify({:binop, :band, {:unaryop, :bnot, a}, {:unaryop, :bnot, b}}) do + {:unaryop, :bnot, {:binop, :bor, algebraic_simplify(a), algebraic_simplify(b)}} + end + + # bnot(a) | bnot(b) → bnot(a & b) (saves one bnot/i64.xor instruction) + def algebraic_simplify({:binop, :bor, {:unaryop, :bnot, a}, {:unaryop, :bnot, b}}) do + {:unaryop, :bnot, {:binop, :band, algebraic_simplify(a), algebraic_simplify(b)}} + end + + # --- De Morgan's laws (boolean) --- + # not(a) and not(b) → not(a or b) (saves one i32.eqz instruction) + def algebraic_simplify({:binop, :and_, {:unaryop, :not, a}, {:unaryop, :not, b}}) do + {:unaryop, :not, {:binop, :or_, algebraic_simplify(a), algebraic_simplify(b)}} + end + + # not(a) or not(b) → not(a and b) (saves one i32.eqz instruction) + def algebraic_simplify({:binop, :or_, {:unaryop, :not, a}, {:unaryop, :not, b}}) do + {:unaryop, :not, {:binop, :and_, algebraic_simplify(a), algebraic_simplify(b)}} + end + # --- Comparison inversion --- # not(a == b) → a != b (eliminates i32.eqz instruction) def algebraic_simplify({:unaryop, :not, {:binop, :eq, left, right}}), diff --git a/test/compiler/optimizer_algebraic_test.exs b/test/compiler/optimizer_algebraic_test.exs index 6eb697e..f4851b7 100644 --- a/test/compiler/optimizer_algebraic_test.exs +++ b/test/compiler/optimizer_algebraic_test.exs @@ -323,6 +323,136 @@ defmodule Firebird.Compiler.OptimizerAlgebraicTest do end end + # ── De Morgan's Laws (Bitwise) ───────────────────────────────────── + + describe "algebraic_simplify/1 De Morgan's laws (bitwise)" do + test "bnot(a) & bnot(b) → bnot(a | b)" do + result = + Optimizer.algebraic_simplify( + {:binop, :band, {:unaryop, :bnot, {:var, :a}}, {:unaryop, :bnot, {:var, :b}}} + ) + + assert result == {:unaryop, :bnot, {:binop, :bor, {:var, :a}, {:var, :b}}} + end + + test "bnot(a) | bnot(b) → bnot(a & b)" do + result = + Optimizer.algebraic_simplify( + {:binop, :bor, {:unaryop, :bnot, {:var, :a}}, {:unaryop, :bnot, {:var, :b}}} + ) + + assert result == {:unaryop, :bnot, {:binop, :band, {:var, :a}, {:var, :b}}} + end + + test "De Morgan bitwise with complex sub-expressions" do + expr_a = {:binop, :add, {:var, :x}, {:literal, 1}} + expr_b = {:binop, :mul, {:var, :y}, {:literal, 2}} + + result = + Optimizer.algebraic_simplify( + {:binop, :band, {:unaryop, :bnot, expr_a}, {:unaryop, :bnot, expr_b}} + ) + + assert result == {:unaryop, :bnot, {:binop, :bor, expr_a, expr_b}} + end + + test "no De Morgan when only one side is bnot" do + result = + Optimizer.algebraic_simplify({:binop, :band, {:unaryop, :bnot, {:var, :a}}, {:var, :b}}) + + assert result == {:binop, :band, {:unaryop, :bnot, {:var, :a}}, {:var, :b}} + end + end + + # ── De Morgan's Laws (Boolean) ─────────────────────────────────── + + describe "algebraic_simplify/1 De Morgan's laws (boolean)" do + test "not(a) and not(b) → not(a or b)" do + result = + Optimizer.algebraic_simplify( + {:binop, :and_, {:unaryop, :not, {:var, :a}}, {:unaryop, :not, {:var, :b}}} + ) + + assert result == {:unaryop, :not, {:binop, :or_, {:var, :a}, {:var, :b}}} + end + + test "not(a) or not(b) → not(a and b)" do + result = + Optimizer.algebraic_simplify( + {:binop, :or_, {:unaryop, :not, {:var, :a}}, {:unaryop, :not, {:var, :b}}} + ) + + assert result == {:unaryop, :not, {:binop, :and_, {:var, :a}, {:var, :b}}} + end + + test "De Morgan boolean with comparison sub-expressions" do + cmp_a = {:binop, :gt_s, {:var, :x}, {:literal, 0}} + cmp_b = {:binop, :lt_s, {:var, :y}, {:literal, 10}} + + result = + Optimizer.algebraic_simplify( + {:binop, :and_, {:unaryop, :not, cmp_a}, {:unaryop, :not, cmp_b}} + ) + + assert result == {:unaryop, :not, {:binop, :or_, cmp_a, cmp_b}} + end + + test "no De Morgan when only one side has not" do + result = + Optimizer.algebraic_simplify({:binop, :and_, {:unaryop, :not, {:var, :a}}, {:var, :b}}) + + assert result == {:binop, :and_, {:unaryop, :not, {:var, :a}}, {:var, :b}} + end + end + + # ── De Morgan's Laws End-to-End ────────────────────────────────── + + describe "De Morgan optimization end-to-end" do + test "bnot(a) band bnot(b) optimizes through full pipeline" do + module = %IR.Module{ + name: :Test, + functions: [ + %IR.Function{ + name: :demorgan_band, + arity: 2, + params: [:p0, :p1], + body: {:binop, :band, {:unaryop, :bnot, {:var, :p0}}, {:unaryop, :bnot, {:var, :p1}}}, + clauses: [], + type: nil + } + ], + exports: [:demorgan_band] + } + + {:ok, optimized} = Optimizer.optimize(module) + [func] = optimized.functions + # Should become bnot(p0 | p1) + assert func.body == {:unaryop, :bnot, {:binop, :bor, {:var, :p0}, {:var, :p1}}} + end + + test "not(a) and not(b) optimizes through full pipeline" do + module = %IR.Module{ + name: :Test, + functions: [ + %IR.Function{ + name: :demorgan_and, + arity: 2, + params: [:p0, :p1], + body: {:binop, :and_, {:unaryop, :not, {:var, :p0}}, {:unaryop, :not, {:var, :p1}}}, + clauses: [], + type: nil + } + ], + exports: [:demorgan_and] + } + + {:ok, optimized} = Optimizer.optimize(module) + [func] = optimized.functions + # Should become not(p0 or p1) + assert func.body == {:unaryop, :not, {:binop, :or_, {:var, :p0}, {:var, :p1}}} + end + end + # ── End-to-End Pipeline ─────────────────────────────────────────── describe "end-to-end with algebraic simplification" do