Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions lib/firebird/compiler/optimizer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}}),
Expand Down
130 changes: 130 additions & 0 deletions test/compiler/optimizer_algebraic_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading