Skip to content

Commit

Permalink
[analysis] Add a generic powerset lattice
Browse files Browse the repository at this point in the history
Add `Powerset2<Set>` that implements the lattice of sets concretely represented
as `Set` ordered by subset as well as the full lattice `FinitePowerset2<Set>`
that is additionally configurted with the universe of set elements so it can
produce a top element, which is the set of everything in the universe.

The "2" in the name serves to temporarily differentiate these new powerset
lattices from the existing powerset lattices, which are less flexible and will
be replaced with the new powerset lattices in a future PR.

Also add a new `BitSet` utility that implements a set interface matching e.g.
`std::set<size_t>` or `std::unordered_set<size_t>` in terms of a
`std::vector<bool>` bit vector. Use `BitSet` as the concrete set implementation
used to fuzz `Powerset2` and `FinitePowerset2`.
  • Loading branch information
tlively committed Oct 27, 2023
1 parent c5a6478 commit a34cf69
Show file tree
Hide file tree
Showing 4 changed files with 538 additions and 17 deletions.
106 changes: 106 additions & 0 deletions src/analysis/lattices/powerset2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2023 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 <unordered_set>

#include "../lattice.h"
#include "support/bitset.h"

#ifndef wasm_analysis_lattices_powerset2_h
#define wasm_analysis_lattices_powerset2_h

namespace wasm::analysis {

// A powerset lattice whose elements are sets (represented concretely with type
// `Set`) ordered by subset.
template<typename Set> struct Powerset2 {
using Element = Set;

Element getBottom() const noexcept { return Set{}; }

LatticeComparison compare(const Set& a, const Set& b) const noexcept {
auto sizeA = a.size();
auto sizeB = b.size();
if (sizeA <= sizeB) {
for (const auto& val : a) {
if (!b.count(val)) {
// At least one member differs between A and B.
return NO_RELATION;
}
}
// All elements in A were also in B.
return sizeA == sizeB ? EQUAL : LESS;
}
for (const auto& val : b) {
if (!a.count(val)) {
// At least one member differs between A and B.
return NO_RELATION;
}
}
// A was larger and contained all the elements of B.
return GREATER;
}

bool join(Set& joinee, const Set& joiner) const noexcept {
bool result = false;
for (const auto& val : joiner) {
result |= joinee.insert(val).second;
}
return result;
}
};

// A powerset lattice initialized with a list of all elements in the universe,
// making it possible to produce a top elements that contains all of them.
template<typename Set> struct FinitePowerset2 : Powerset2<Set> {
private:
const Set top;

public:
using Element = Set;

FinitePowerset2(std::initializer_list<typename Set::value_type>&& vals)
: top(std::move(vals)) {}

template<typename Vals>
FinitePowerset2(const Vals& vals) : top(vals.begin(), vals.end()) {}

Element getTop() const noexcept { return top; }

bool meet(Set& meetee, const Set& meeter) const noexcept {
bool result = false;
for (auto it = meetee.begin(); it != meetee.end();) {
if (!meeter.count(*it)) {
it = meetee.erase(it);
result = true;
} else {
++it;
}
}
return result;
}
};

#if __cplusplus >= 202002L
static_assert(Lattice<Powerset2<BitSet>>);
static_assert(Lattice<Powerset2<std::unordered_set<int>>>);
static_assert(FullLattice<FinitePowerset2<BitSet>>);
static_assert(FullLattice<FinitePowerset2<std::unordered_set<int>>>);
#endif

} // namespace wasm::analysis

#endif // wasm_analysis_lattices_powerset2_h
216 changes: 216 additions & 0 deletions src/support/bitset.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Copyright 2023 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_support_bitset_h
#define wasm_support_bitset_h

#include <cassert>
#include <cstddef>
#include <iterator>
#include <vector>

namespace wasm {

// Represent a set of integers in [0, N) with a bitvector of size N, where a 1
// bit signifies set membership. This interface is intended to match that of
// std::set or std::unordered_set as closely as possible.
class BitSet : std::vector<bool> {
std::vector<bool>& vec() noexcept { return *this; }
const std::vector<bool>& vec() const noexcept { return *this; }

void ensure(size_t value) noexcept {
if (vec().size() <= value) {
vec().resize(value + 1);
}
}

public:
using key_type = size_t;
using value_type = size_t;

class iterator {
const std::vector<bool>* parent = nullptr;
size_t index = 0;

iterator(const std::vector<bool>* parent) : parent(parent) {
const auto size = parent->size();
while (index < size && !(*parent)[index]) {
++index;
}
}

iterator(const std::vector<bool>* parent, size_t index)
: parent(parent), index(index) {}

friend BitSet;

public:
using difference_type = ptrdiff_t;
using value_type = size_t;
using pointer = size_t*;
using reference = size_t&;
using iterator_category = std::bidirectional_iterator_tag;

iterator() = default;
iterator(const iterator&) = default;
iterator(iterator&&) = default;
iterator& operator=(const iterator&) = default;
iterator& operator=(iterator&&) = default;

size_t operator*() const noexcept { return index; }

iterator& operator++() noexcept {
const auto size = parent->size();
do {
++index;
} while (index < size && !(*parent)[index]);
return *this;
}

iterator operator++(int) noexcept {
iterator it = *this;
++(*this);
return it;
}

iterator& operator--() noexcept {
do {
--index;
assert(index != -1ull);
} while (!(*parent)[index]);
return *this;
}

iterator operator--(int) noexcept {
iterator it = *this;
--(*this);
return it;
}

bool operator==(const iterator& other) const noexcept {
assert(parent == other.parent);
return index == other.index;
}
bool operator!=(const iterator& other) const noexcept {
return !(*this == other);
assert(parent == other.parent);
return index == other.index;
}
};

BitSet() = default;
BitSet(const BitSet&) = default;
BitSet(BitSet&&) = default;

template<typename It> BitSet(It begin, It end) {
for (auto it = begin; it != end; ++it) {
insert(*it);
}
}
BitSet(const std::initializer_list<size_t>& vals)
: BitSet(vals.begin(), vals.end()) {}

BitSet& operator=(const BitSet&) = default;
BitSet& operator=(BitSet&&) = default;

iterator begin() const noexcept { return iterator({this}); }
iterator end() const noexcept { return iterator({this, vec().size()}); }

bool empty() const noexcept { return begin() == end(); }

size_t size() const noexcept {
size_t result = 0;
for (auto it = begin(); it != end(); ++it, ++result) {
}
return result;
}

void clear() noexcept { vec().clear(); }

std::pair<iterator, bool> insert(size_t value) noexcept {
ensure(value);
bool didInsert = false;
if (!(*this)[value]) {
didInsert = true;
(*this)[value] = true;
}
return {iterator({this, value}), didInsert};
}

size_t erase(size_t key) noexcept {
if (key < vec().size()) {
return false;
}
bool didErase = false;
if ((*this)[key]) {
didErase = true;
(*this)[key] = false;
}
return didErase;
}

iterator erase(iterator it) noexcept {
auto next = it;
++next;
(*this)[it.index] = false;
return next;
}

size_t count(size_t key) const noexcept {
if (key >= vec().size()) {
return 0;
}
return (*this)[key];
}

bool operator==(const BitSet& other) const {
auto thisSize = vec().size();
auto otherSize = other.vec().size();
const std::vector<bool>* shorter;
const std::vector<bool>* longer;
if (thisSize <= otherSize) {
shorter = this;
longer = &other;
} else {
shorter = &other;
longer = this;
}
size_t i = 0;
size_t shortSize = shorter->size();
size_t longSize = longer->size();
// All elements should match.
for (; i < shortSize; ++i) {
if ((*shorter)[i] != (*longer)[i]) {
return false;
}
}
// The rest of the longer vector should be zeroes.
for (; i < longSize; ++i) {
if ((*longer)[i]) {
return false;
}
}
return true;
}
};

#if __cplusplus >= 202002L
static_assert(std::bidirectional_iterator<typename BitSet::iterator>);
#endif

} // namespace wasm

#endif // wasm_support_bitset_h
Loading

0 comments on commit a34cf69

Please sign in to comment.