Skip to main content
⚡ Calmops

Semantic Equivalence: Comparing Program Meanings

Introduction

Semantic equivalence addresses a fundamental question in programming: when do two programs have the same meaning? This question is crucial for compiler optimization, program transformation, refactoring, and formal verification. Two programs may have different syntax but identical behavior—understanding when this occurs is essential for reasoning about programs.

This article explores different notions of semantic equivalence, equivalence relations, and practical applications.

Historical Context

The study of semantic equivalence emerged from work in denotational semantics and process algebra in the 1970s and 1980s. Robin Milner’s work on bisimulation and observational equivalence provided powerful tools for comparing program behaviors. These concepts have become fundamental in programming language theory and formal verification.

Core Concepts

Semantic Equivalence

Definition: Two programs p₁ and p₂ are semantically equivalent if they have the same meaning according to some semantics.

Notation: p₁ ≡ p₂

Key Question: What does “same meaning” mean? This depends on the semantics and what we consider observable.

Observational Equivalence

Definition: Two programs are observationally equivalent if they produce the same observable results for all possible inputs.

Observable Results: Outputs, side effects, termination behavior, etc.

Intuition: If we can’t distinguish two programs by running them and observing their behavior, they’re observationally equivalent.

Example:

Program 1: x := 5; y := x + 1
Program 2: y := 6

These are observationally equivalent if we only observe the final value of y.
But they're not equivalent if we observe the value of x.

Equivalence Relations

Denotational Equivalence

Two programs are denotationally equivalent if they have the same denotation:

Definition: p₁ ≡_D p₂ iff ⟦p₁⟧ = ⟦p₂⟧

Advantages:

  • Mathematically clean
  • Compositional
  • Enables algebraic reasoning

Disadvantage:

  • Depends on the denotational semantics chosen

Example:

⟦x := 5; y := x + 1⟧ = λσ. σ[x ↦ 5, y ↦ 6]
⟦y := 6; x := 5⟧ = λσ. σ[x ↦ 5, y ↦ 6]
These are denotationally equivalent.

Operational Equivalence

Two programs are operationally equivalent if they have the same operational behavior:

Definition: p₁ ≡_O p₂ iff for all contexts C and inputs i:

  • C[p₁] with input i produces the same output as C[p₂] with input i

Context: A program with a “hole” where p can be inserted

Advantages:

  • Directly based on execution
  • Intuitive and practical
  • Doesn’t require denotational semantics

Disadvantage:

  • Difficult to verify (requires checking all contexts)

Logical Equivalence

Two programs are logically equivalent if they satisfy the same logical properties:

Definition: p₁ ≡_L p₂ iff for all formulas φ in some logic:

  • p₁ ⊨ φ iff p₂ ⊨ φ

Logic: Could be Hoare logic, temporal logic, etc.

Example (Hoare Logic):

{P} p₁ {Q} iff {P} p₂ {Q}

Bisimulation

Bisimulation is a powerful technique for proving equivalence of state-based systems.

Definition

Bisimulation: A relation R between states is a bisimulation if:

  • Whenever (s, t) ∈ R and s → s’, there exists t’ such that t → t’ and (s’, t’) ∈ R
  • Whenever (s, t) ∈ R and t → t’, there exists s’ such that s → s’ and (s’, t’) ∈ R
  • s and t have the same observable properties

Intuition: Two states are bisimilar if they can simulate each other’s transitions while maintaining the same observable properties.

Bisimilarity

Definition: States s and t are bisimilar (s ~ t) if there exists a bisimulation R with (s, t) ∈ R.

Properties:

  • Reflexive: s ~ s
  • Symmetric: s ~ t implies t ~ s
  • Transitive: s ~ t and t ~ u implies s ~ u

Weak Bisimulation

Allows internal (unobservable) transitions:

Definition: A weak bisimulation allows τ (internal) transitions to be skipped.

Notation: s =⟹ s’ means s can reach s’ through zero or more τ transitions

Weak Bisimulation Rule:

  • If s → s’ (observable), then t =⟹ t’ with s’ ~ t'
  • If s =⟹ s’ (internal), then t =⟹ t’ with s’ ~ t'

Advantage: Abstracts away internal implementation details

Practical Examples

Example 1: Loop Equivalence

Program 1:

i := 0
while i < n do (
  sum := sum + a[i]
  i := i + 1
)

Program 2:

i := n - 1
while i ≥ 0 do (
  sum := sum + a[i]
  i := i - 1
)

Equivalence: Both compute the same sum, but in different orders. They’re equivalent if addition is commutative and we only observe the final sum.

Example 2: Optimization Equivalence

Original:

x := 5
y := x + 1
z := y * 2

Optimized:

z := 12

Equivalence: If we only observe z, they’re equivalent. But if we observe x or y, they’re not.

Example 3: Concurrent Program Equivalence

Program 1:

x := 1 ∥ y := 2

Program 2:

y := 2 ∥ x := 1

Equivalence: Both result in x = 1, y = 2. They’re equivalent under interleaving semantics.

Equivalence Checking

Syntactic Comparison

Simplest Approach: Compare abstract syntax trees

Limitations:

  • Doesn’t account for semantic equivalences
  • Misses equivalent programs with different syntax

Semantic Comparison

Approach: Compare denotations or operational behavior

Methods:

  1. Denotational: Compute and compare denotations
  2. Operational: Simulate execution and compare traces
  3. Bisimulation: Check if states are bisimilar

Automated Equivalence Checking

Tools:

  • Model Checkers: SPIN, NuSMV can check equivalence
  • Theorem Provers: Coq, Isabelle can prove equivalence
  • Specialized Tools: Diff tools for programs

Algorithm (for finite-state systems):

1. Build transition systems for both programs
2. Compute bisimulation relation
3. Check if initial states are bisimilar

Congruence and Compositionality

Congruence

Definition: An equivalence relation ≡ is a congruence if:

  • Whenever p₁ ≡ p₂, then C[p₁] ≡ C[p₂] for all contexts C

Importance: Allows replacing equivalent programs in larger programs.

Example: If x := 5; y := x + 1y := 6, then:

{x := 5; y := x + 1; z := y * 2} ≡ {y := 6; z := y * 2}

Compositionality

Definition: A semantics is compositional if the meaning of a composite construct is determined by the meanings of its parts.

Relationship: Compositional semantics naturally support congruence.

Equivalence Transformations

Program Transformations

Transformations that preserve equivalence:

Dead Code Elimination:

x := 5; y := 10; z := y + 1
≡ y := 10; z := y + 1  (if x is never used)

Constant Folding:

x := 5 + 3
≡ x := 8

Loop Unrolling:

for i := 1 to 2 do s
≡ s; s

Common Subexpression Elimination:

x := a + b; y := a + b
≡ x := a + b; y := x

Algebraic Laws

Commutativity (for independent statements):

x := 5; y := 10 ≡ y := 10; x := 5

Associativity:

(s₁; s₂); s₃ ≡ s₁; (s₂; s₃)

Identity:

s; skip ≡ s
skip; s ≡ s

Idempotence:

x := 5; x := 5 ≡ x := 5

Advanced Topics

Contextual Equivalence

Definition: p₁ and p₂ are contextually equivalent if for all contexts C:

  • C[p₁] and C[p₂] have the same observable behavior

Importance: Captures the idea that equivalent programs are indistinguishable in any context.

Challenge: Difficult to verify (requires checking all contexts)

Logical Relations

Technique: Prove equivalence by establishing a logical relation between programs.

Approach:

  1. Define a relation on program values
  2. Show the relation is preserved by program operations
  3. Conclude programs are equivalent

Parametricity

Concept: Polymorphic programs have restricted behavior based on type parameters.

Application: Prove equivalences for polymorphic programs without knowing concrete types.

Glossary

Bisimulation: A relation showing two systems can simulate each other’s behavior

Bisimilarity: The equivalence relation induced by bisimulation

Congruence: An equivalence relation preserved by all contexts

Contextual Equivalence: Programs indistinguishable in any context

Denotational Equivalence: Programs with the same denotation

Logical Equivalence: Programs satisfying the same logical properties

Observational Equivalence: Programs producing the same observable results

Semantic Equivalence: Programs having the same meaning

Weak Bisimulation: Bisimulation allowing internal transitions

Practice Problems

Problem 1: Prove that x := 5; y := x + 1 and y := 6 are denotationally equivalent.

Solution:

⟦x := 5; y := x + 1⟧ σ = ⟦y := x + 1⟧(σ[x ↦ 5])
                        = (σ[x ↦ 5])[y ↦ 6]
                        = σ[x ↦ 5, y ↦ 6]

⟦y := 6⟧ σ = σ[y ↦ 6]

These are not equal (first has x = 5, second doesn't constrain x).
So they're not denotationally equivalent.

Problem 2: Show that while false do s is equivalent to skip.

Solution:

Operationally:
⟨while false do s, σ⟩ → σ  (loop condition false, exit immediately)
⟨skip, σ⟩ → σ

Both transition to the same state, so they're operationally equivalent.

Problem 3: Define a bisimulation between two traffic light systems.

Solution:

System 1: Red → Green → Yellow → Red
System 2: Red → Yellow → Green → Red

Bisimulation R = {(Red₁, Red₂), (Green₁, Yellow₂), (Yellow₁, Green₂)}

Check:
- Red₁ → Green₁, Red₂ → Yellow₂, (Green₁, Yellow₂) ∈ R ✓
- Green₁ → Yellow₁, Yellow₂ → Green₂, (Yellow₁, Green₂) ∈ R ✓
- Yellow₁ → Red₁, Green₂ → Red₂, (Red₁, Red₂) ∈ R ✓

Systems are bisimilar.

Conclusion

Semantic equivalence provides the theoretical foundation for understanding when two programs have the same meaning. Whether through denotational, operational, or logical approaches, equivalence relations enable rigorous reasoning about program transformations, optimizations, and refactoring.

Bisimulation and observational equivalence offer practical tools for proving equivalence, particularly for state-based systems. Understanding these concepts is essential for compiler developers, formal verification specialists, and anyone involved in program analysis and transformation.

As programming languages and systems become more complex, the ability to reason about semantic equivalence becomes increasingly important for ensuring correctness, enabling optimization, and facilitating program maintenance and evolution.

Comments