Skip to content
Draft
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
17 changes: 16 additions & 1 deletion src/analysis/lattices/flat.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,25 @@ struct Flat {
}
WASM_UNREACHABLE("unexpected comparison result");
}

bool meet(Element& meetee, const Element& meeter) const noexcept {
switch (compare(meetee, meeter)) {
case GREATER:
meetee = meeter;
return true;
case NO_RELATION:
meetee = Element{Bot{}};
return true;
case LESS:
case EQUAL:
return false;
}
WASM_UNREACHABLE("unexpected comparison result");
}
};

#if defined(__cpp_lib_concepts)
static_assert(Lattice<Flat<int>>);
static_assert(FullLattice<Flat<int>>);
#endif

} // namespace wasm::analysis
Expand Down
1 change: 1 addition & 0 deletions src/analysis/lattices/one-of.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#endif

#include "analysis/lattice.h"
#include "analysis/lattices/bool.h"
#include "support/utilities.h"

namespace wasm::analysis {
Expand Down
1 change: 1 addition & 0 deletions src/ir/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ FILE(GLOB ir_HEADERS *.h)
set(ir_SOURCES
ExpressionAnalyzer.cpp
ExpressionManipulator.cpp
constraint.cpp
drop.cpp
effects.cpp
eh-utils.cpp
Expand Down
119 changes: 119 additions & 0 deletions src/ir/constraint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright 2026 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "ir/constraint.h"

namespace wasm {

using namespace analysis;

// Defines a total order on Flat<Index>::Element that linearly extends the
// flat lattice partial order (where Bot < Index < Top, and different indices
// are unrelated).
// We order Bot < Index (ordered by index value) < Top.
std::strong_ordering orderFlatElements(const typename Flat<Index>::Element& a,
const typename Flat<Index>::Element& b) {
auto get_key = [](const typename Flat<Index>::Element& x) {
if (x.isBottom())
return 0u;
if (x.isTop())
return 2u;
return 1u;
};
auto k1 = get_key(a);
auto k2 = get_key(b);
if (k1 != k2)
return k1 <=> k2;
// If both are Index, compare their uint32_t values.
if (k1 == 1) {
return *a.getVal() <=> *b.getVal();
}
return std::strong_ordering::equal;
}

// BoundedConstraintsBase::orderElements implements the total order for the
// OneOf elements.
//
// It satisfies the requirement to be a linear extension of the OneOf lattice
// partial order, and additionally orders constant bounds (index 0) before
// variable bounds (index 1) so that variable bounds are dropped first when
// the size limit N is exceeded in BoundedConjunction.
std::strong_ordering
BoundedConstraintsBase::orderElements(const typename L::Element& a,
const typename L::Element& b) const {
// If they belong to different component lattices, order constants (index 0)
// before variables (index 1).
if (a.index() != b.index()) {
return a.index() <=> b.index();
}

// If they belong to the same component lattice, we define a total order
// within that component that extends the component's lattice order.

if (a.index() == 0) {
// Bound<Int64> component.
Bound<Int64> bound_int(Int64{});
const auto& va = std::get<0>(a);
const auto& vb = std::get<0>(b);

// If they are related in the lattice, use that order.
auto comp = bound_int.compare(va, vb);
if (comp == LESS)
return std::strong_ordering::less;
if (comp == GREATER)
return std::strong_ordering::greater;
if (comp == EQUAL)
return std::strong_ordering::equal;

// If they are unrelated (e.g. x < 5 and x >= 3), we use an arbitrary
// total order on their representation: first by relation, then by value.
const auto& c1 = std::get<typename Bound<Int64>::Constraint>(va);
const auto& c2 = std::get<typename Bound<Int64>::Constraint>(vb);
if (c1.rel != c2.rel) {
return c1.rel <=> c2.rel;
}
return c1.val <=> c2.val;
}

if (a.index() == 1) {
// Bound<Flat<Index>> component.
Bound<Flat<Index>> bound_flat(Flat<Index>{});
const auto& va = std::get<1>(a);
const auto& vb = std::get<1>(b);

// If they are related in the lattice, use that order.
auto comp = bound_flat.compare(va, vb);
if (comp == LESS)
return std::strong_ordering::less;
if (comp == GREATER)
return std::strong_ordering::greater;
if (comp == EQUAL)
return std::strong_ordering::equal;

// If they are unrelated, compare by relation, then by the total order
// on Flat<Index> elements.
const auto& c1 = std::get<typename Bound<Flat<Index>>::Constraint>(va);
const auto& c2 = std::get<typename Bound<Flat<Index>>::Constraint>(vb);
if (c1.rel != c2.rel) {
return c1.rel <=> c2.rel;
}
return orderFlatElements(c1.val, c2.val);
}

return std::strong_ordering::equal;
}

} // namespace wasm
78 changes: 78 additions & 0 deletions src/ir/constraint.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2026 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef wasm_ir_constraint_h
#define wasm_ir_constraint_h

#include "analysis/lattices/bound.h"
#include "analysis/lattices/bounded-conjunction.h"
#include "analysis/lattices/flat.h"
#include "analysis/lattices/int.h"
#include "analysis/lattices/one-of.h"
#include "support/index.h"
#include <compare>

namespace wasm {

// BoundedConstraintsBase defines the underlying lattice type and the total
// order on its elements.
//
// The underlying lattice is a OneOf lattice containing two types of bounds:
// 1. Bound<Int64>: Bounds against 64-bit integer constants (e.g. x < 5).
// The Int64 lattice is ordered by <.
// 2. Bound<Flat<Index>>: Bounds against other variables, represented by their
// local/parameter Index (e.g. x < $1). The Flat<Index> lattice has no
// order between different variable indices.
//
// An element of OneOf can be either a constant bound, a variable bound, Top,
// or Bottom.
struct BoundedConstraintsBase {
using L = analysis::OneOf<analysis::Bound<analysis::Int64>,
analysis::Bound<analysis::Flat<Index>>>;

// Defines a total order on OneOf elements to be used by BoundedConjunction.
// It orders constant bounds before variable bounds (so variable bounds are
// dropped first when the size limit N is exceeded), and otherwise defines
// a linear extension of the OneOf lattice partial order.
std::strong_ordering orderElements(const typename L::Element& a,
const typename L::Element& b) const;
};

// BoundedConstraints<N> represents a conjunction of up to N constraints on a
// variable. Each constraint is either a constant bound or a variable bound.
//
// It is implemented as a BoundedConjunction over the OneOf lattice defined
// in BoundedConstraintsBase.
template<std::size_t N>
struct BoundedConstraints
: BoundedConstraintsBase,
analysis::BoundedConjunction<BoundedConstraints<N>,
typename BoundedConstraintsBase::L,
N> {
using Super = analysis::BoundedConjunction<BoundedConstraints<N>,
typename BoundedConstraintsBase::L,
N>;

BoundedConstraints()
: BoundedConstraintsBase(),
Super(typename BoundedConstraintsBase::L(
analysis::Bound<analysis::Int64>(analysis::Int64{}),
analysis::Bound<analysis::Flat<Index>>(analysis::Flat<Index>{}))) {}
};

} // namespace wasm

#endif // wasm_ir_constraint_h
1 change: 1 addition & 0 deletions test/gtest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ set(unittest_SOURCES
arena.cpp
cast-check.cpp
cfg.cpp
constraint.cpp
dataflow.cpp
delta_debugging.cpp
dfa_minimization.cpp
Expand Down
94 changes: 94 additions & 0 deletions test/gtest/constraint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2026 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "ir/constraint.h"
#include "gtest/gtest.h"

using namespace wasm;
using namespace wasm::analysis;

TEST(ConstraintTest, Basic) {
BoundedConstraints<2> lattice;

auto bot = lattice.getBottom();
BoundedConstraints<2>::Element top{
inplace_vector<BoundedConstraintsBase::L::Element, 2>{}};

Bound<Int64> bound_int(Int64{});
Bound<Flat<Index>> bound_flat(Flat<Index>{});

auto make_int_bound = [&](BoundRelation rel, int64_t val) {
return BoundedConstraintsBase::L::Element(std::in_place_index<0>,
bound_int.makeBound(rel, val));
};

auto make_var_bound = [&](BoundRelation rel, Index var) {
return BoundedConstraintsBase::L::Element(
std::in_place_index<1>,
bound_flat.makeBound(rel, Flat<Index>{}.get(var)));
};

auto make_constraints =
[&](std::initializer_list<BoundedConstraintsBase::L::Element> elms) {
inplace_vector<BoundedConstraintsBase::L::Element, 2> vec;
for (const auto& e : elms) {
vec.push_back(e);
}
return BoundedConstraints<2>::Element{vec};
};

auto c_int1 = make_int_bound(BoundRelation::LT, 5); // x < 5
auto c_int2 = make_int_bound(BoundRelation::GE, 3); // x >= 3
auto c_var1 = make_var_bound(BoundRelation::LE, 1); // x <= $1
auto c_var2 = make_var_bound(BoundRelation::GT, 2); // x > $2

// Test compare
auto e_int1 = make_constraints({c_int1});
auto e_int2 = make_constraints({c_int2});
auto e_var1 = make_constraints({c_var1});

EXPECT_EQ(lattice.compare(bot, e_int1), LESS);
EXPECT_EQ(lattice.compare(top, e_int1), GREATER);

EXPECT_EQ(lattice.compare(e_int1, e_int2), NO_RELATION);
EXPECT_EQ(lattice.compare(e_int1, e_var1), NO_RELATION);

// Test boundedMeet (conjunction)
// Meet {x < 5} and {x >= 3} -> {x < 5, x >= 3}
{
auto meetee = e_int1;
EXPECT_TRUE(lattice.boundedMeet(meetee, e_int2));
EXPECT_EQ(meetee, make_constraints({c_int1, c_int2}));
}

// Meet {x < 5, x >= 3} and {x <= $1} -> exceeds N=2!
// It should keep constants (c_int1, c_int2) and drop variable (c_var1).
{
auto meetee = make_constraints({c_int1, c_int2});
EXPECT_FALSE(lattice.boundedMeet(meetee, e_var1));
EXPECT_EQ(meetee, make_constraints({c_int1, c_int2}));
}

// Meet {x < 5, x <= $1} and {x > $2} -> exceeds N=2!
// Sorted: c_int1 (const), c_var1 (LE), c_var2 (GT).
// LE < GT, so c_var1 is kept, c_var2 is dropped.
{
auto meetee = make_constraints({c_int1, c_var1});
auto meeter = make_constraints({c_var2});
EXPECT_FALSE(lattice.boundedMeet(meetee, meeter));
EXPECT_EQ(meetee, make_constraints({c_int1, c_var1}));
}
}
Loading