Skip to content

Commit bc88d5d

Browse files
authored
[analysis] Add a "Shared" lattice to represent shared state (#6067)
The analysis framework stores a separate lattice element for each basic block being analyzed to represent the program state at the beginning of the block. However, in many analyses a significant portion of program state is not flow-sensitive, so does not benefit from having a separate copy per block. For example, an analysis might track constraints on the types of locals that do not vary across blocks, so it really only needs a single copy of the constrains for each local. It would be correct to simply duplicate the state across blocks anyway, but it would not be efficient. To make it possible to share a single copy of a lattice element across basic blocks, introduce a `Shared<L>` lattice. Mathematically, this lattice represents a single ascending chain in the underlying lattice and its elements are ordered according to sequence numbers corresponding to positions in that chain. Concretely, though, the `Shared<L>` lattice only ever materializes a single, monotonically increasing element of `L` and all of its elements provide access to that shared underlying element. `Shared<L>` will let us get the benefits of having mutable shared state in the concrete implementation of analyses without losing the benefits of keeping those analyses expressible purely in terms of the monotone framework.
1 parent 34cbf00 commit bc88d5d

File tree

3 files changed

+254
-3
lines changed

3 files changed

+254
-3
lines changed

src/analysis/lattices/shared.h

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2023 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_analysis_lattices_shared_h
18+
#define wasm_analysis_lattices_shared_h
19+
20+
#include <cstdint>
21+
#include <utility>
22+
23+
#include "../lattice.h"
24+
#include "bool.h"
25+
26+
namespace wasm::analysis {
27+
28+
// A lattice whose elements are a single ascending chain in lattice `L`.
29+
// Internally, there is only ever a single monotonically increasing element of L
30+
// materialized. Dereferencing any element of the Shared lattice will produce
31+
// the current value of that single element of L, which is generally safe
32+
// because the current value always overapproximates (i.e. is higher in the
33+
// lattice than) the value at the time of the Shared element's construction.
34+
//
35+
// Each element of this lattice maintains a sequence number that corresponds to
36+
// a value the shared underlying element has had at some point in time. Higher
37+
// sequence numbers correspond to greater values of the underlying element.
38+
// Elements of this lattice are compared and joined using these sequence
39+
// numbers, so blocks will correctly be re-analyzed if the value has increased
40+
// since the last time they were analyzed.
41+
template<Lattice L> struct Shared {
42+
// If we ever have extremely long-running analyses, this may need to be
43+
// changed to uint64_t.
44+
using Seq = uint32_t;
45+
46+
class Element {
47+
const typename L::Element* val;
48+
Seq seq = 0;
49+
50+
Element(const typename L::Element* val) : val(val) {}
51+
52+
public:
53+
Element() = default;
54+
Element(const Element&) = default;
55+
Element(Element&&) = default;
56+
Element& operator=(const Element&) = default;
57+
Element& operator=(Element&&) = default;
58+
59+
// Only provide const references to the value to force all updates to go
60+
// through our API.
61+
const typename L::Element& operator*() const noexcept { return *val; }
62+
const typename L::Element* operator->() const noexcept { return val; }
63+
64+
bool operator==(const Element& other) const noexcept {
65+
assert(val == other.val);
66+
return seq == other.seq;
67+
}
68+
69+
bool operator!=(const Element& other) const noexcept {
70+
return !(*this == other);
71+
}
72+
73+
friend Shared;
74+
};
75+
76+
L lattice;
77+
78+
// The current value that all elements point to and the current maximum
79+
// sequence number. The sequence numbers monotonically increase along with
80+
// `val` and serve to provide ordering between elements of this lattice. These
81+
// are mutable because they are logically not part of the lattice itself, but
82+
// rather of its elements. They are only stored inside the lattice because it
83+
// is simpler and more efficient than using shared pointers.
84+
mutable typename L::Element val;
85+
mutable Seq seq = 0;
86+
87+
Shared(L&& l) : lattice(std::move(l)), val(lattice.getBottom()) {}
88+
89+
// TODO: Delete the move constructor and the move assignment operator. This
90+
// requires fixing the lattice fuzzer first, since it depends on lattices
91+
// being moveable.
92+
93+
Element getBottom() const noexcept { return Element{&val}; }
94+
95+
LatticeComparison compare(const Element& a, const Element& b) const noexcept {
96+
assert(a.val == b.val);
97+
return a.seq < b.seq ? LESS : a.seq == b.seq ? EQUAL : GREATER;
98+
}
99+
100+
bool join(Element& joinee, const Element& joiner) const noexcept {
101+
assert(joinee.val == joiner.val);
102+
if (joinee.seq < joiner.seq) {
103+
joinee.seq = joiner.seq;
104+
return true;
105+
}
106+
return false;
107+
}
108+
109+
bool join(Element& joinee, const typename L::Element& joiner) const noexcept {
110+
if (lattice.join(val, joiner)) {
111+
// We have moved to the next value in our ascending chain. Assign it a new
112+
// sequence number and update joinee with that sequence number.
113+
joinee.seq = ++seq;
114+
return true;
115+
}
116+
return false;
117+
}
118+
};
119+
120+
#if __cplusplus >= 202002L
121+
static_assert(Lattice<Shared<Bool>>);
122+
#endif // __cplusplus >= 202002L
123+
124+
} // namespace wasm::analysis
125+
126+
#endif // wasm_analysis_lattices_shared_h

src/tools/wasm-fuzz-lattices.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "analysis/lattices/int.h"
2828
#include "analysis/lattices/inverted.h"
2929
#include "analysis/lattices/lift.h"
30+
#include "analysis/lattices/shared.h"
3031
#include "analysis/lattices/stack.h"
3132
#include "analysis/lattices/tuple.h"
3233
#include "analysis/lattices/valtype.h"
@@ -179,15 +180,17 @@ struct RandomLattice::L : std::variant<RandomFullLattice,
179180
Lift<RandomLattice>,
180181
ArrayLattice,
181182
Vector<RandomLattice>,
182-
TupleLattice> {};
183+
TupleLattice,
184+
Shared<RandomLattice>> {};
183185

184186
struct RandomLattice::ElementImpl
185187
: std::variant<typename RandomFullLattice::Element,
186188
typename Flat<uint32_t>::Element,
187189
typename Lift<RandomLattice>::Element,
188190
typename ArrayLattice::Element,
189191
typename Vector<RandomLattice>::Element,
190-
typename TupleLattice::Element> {};
192+
typename TupleLattice::Element,
193+
typename Shared<RandomLattice>::Element> {};
191194

192195
constexpr int FullLatticePicks = 7;
193196

@@ -230,7 +233,7 @@ RandomFullLattice::RandomFullLattice(Random& rand,
230233

231234
RandomLattice::RandomLattice(Random& rand, size_t depth) : rand(rand) {
232235
// TODO: Limit the depth once we get lattices with more fan-out.
233-
uint32_t pick = rand.upTo(FullLatticePicks + 5);
236+
uint32_t pick = rand.upTo(FullLatticePicks + 6);
234237

235238
if (pick < FullLatticePicks) {
236239
lattice = std::make_unique<L>(L{RandomFullLattice{rand, depth, pick}});
@@ -256,6 +259,9 @@ RandomLattice::RandomLattice(Random& rand, size_t depth) : rand(rand) {
256259
lattice = std::make_unique<L>(L{TupleLattice{
257260
RandomLattice{rand, depth + 1}, RandomLattice{rand, depth + 1}}});
258261
return;
262+
case FullLatticePicks + 5:
263+
lattice = std::make_unique<L>(L{Shared{RandomLattice{rand, depth + 1}}});
264+
return;
259265
}
260266
WASM_UNREACHABLE("unexpected pick");
261267
}
@@ -358,6 +364,11 @@ RandomLattice::Element RandomLattice::makeElement() const noexcept {
358364
typename TupleLattice::Element{std::get<0>(l->lattices).makeElement(),
359365
std::get<1>(l->lattices).makeElement()}};
360366
}
367+
if (const auto* l = std::get_if<Shared<RandomLattice>>(lattice.get())) {
368+
auto elem = l->getBottom();
369+
l->join(elem, l->lattice.makeElement());
370+
return ElementImpl{elem};
371+
}
361372
WASM_UNREACHABLE("unexpected lattice");
362373
}
363374

@@ -466,6 +477,12 @@ void printElement(std::ostream& os,
466477
printElement(os, second, depth + 1);
467478
indent(os, depth);
468479
os << ")\n";
480+
} else if (const auto* e =
481+
std::get_if<typename Shared<RandomLattice>::Element>(&*elem)) {
482+
os << "Shared(\n";
483+
printElement(os, **e, depth + 1);
484+
indent(os, depth);
485+
os << ")\n";
469486
} else {
470487
WASM_UNREACHABLE("unexpected element");
471488
}

test/gtest/lattices.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "analysis/lattices/int.h"
2121
#include "analysis/lattices/inverted.h"
2222
#include "analysis/lattices/lift.h"
23+
#include "analysis/lattices/shared.h"
2324
#include "analysis/lattices/tuple.h"
2425
#include "analysis/lattices/valtype.h"
2526
#include "analysis/lattices/vector.h"
@@ -546,3 +547,110 @@ TEST(ValTypeLattice, Meet) {
546547
Type(HeapType::array, Nullable),
547548
Type(HeapType::eq, Nullable));
548549
}
550+
551+
TEST(SharedLattice, GetBottom) {
552+
analysis::Shared<analysis::UInt32> shared{analysis::UInt32{}};
553+
EXPECT_EQ(*shared.getBottom(), 0u);
554+
}
555+
556+
TEST(SharedLattice, Compare) {
557+
analysis::Shared<analysis::UInt32> shared{analysis::UInt32{}};
558+
559+
auto zero = shared.getBottom();
560+
561+
auto one = shared.getBottom();
562+
shared.join(one, 1);
563+
564+
// This join will not change the value.
565+
auto uno = one;
566+
shared.join(uno, 1);
567+
568+
auto two = shared.getBottom();
569+
shared.join(two, 2);
570+
571+
EXPECT_EQ(shared.compare(zero, zero), analysis::EQUAL);
572+
EXPECT_EQ(shared.compare(zero, one), analysis::LESS);
573+
EXPECT_EQ(shared.compare(zero, uno), analysis::LESS);
574+
EXPECT_EQ(shared.compare(zero, two), analysis::LESS);
575+
576+
EXPECT_EQ(shared.compare(one, zero), analysis::GREATER);
577+
EXPECT_EQ(shared.compare(one, one), analysis::EQUAL);
578+
EXPECT_EQ(shared.compare(one, uno), analysis::EQUAL);
579+
EXPECT_EQ(shared.compare(one, two), analysis::LESS);
580+
581+
EXPECT_EQ(shared.compare(two, zero), analysis::GREATER);
582+
EXPECT_EQ(shared.compare(two, one), analysis::GREATER);
583+
EXPECT_EQ(shared.compare(two, uno), analysis::GREATER);
584+
EXPECT_EQ(shared.compare(two, two), analysis::EQUAL);
585+
586+
EXPECT_EQ(*zero, 2u);
587+
EXPECT_EQ(*one, 2u);
588+
EXPECT_EQ(*uno, 2u);
589+
EXPECT_EQ(*two, 2u);
590+
}
591+
592+
TEST(SharedLattice, Join) {
593+
analysis::Shared<analysis::UInt32> shared{analysis::UInt32{}};
594+
595+
auto zero = shared.getBottom();
596+
597+
auto one = shared.getBottom();
598+
shared.join(one, 1);
599+
600+
auto two = shared.getBottom();
601+
shared.join(two, 2);
602+
603+
{
604+
auto elem = zero;
605+
EXPECT_FALSE(shared.join(elem, zero));
606+
EXPECT_EQ(elem, zero);
607+
}
608+
609+
{
610+
auto elem = zero;
611+
EXPECT_TRUE(shared.join(elem, one));
612+
EXPECT_EQ(elem, one);
613+
}
614+
615+
{
616+
auto elem = zero;
617+
EXPECT_TRUE(shared.join(elem, two));
618+
EXPECT_EQ(elem, two);
619+
}
620+
621+
{
622+
auto elem = one;
623+
EXPECT_FALSE(shared.join(elem, zero));
624+
EXPECT_EQ(elem, one);
625+
}
626+
627+
{
628+
auto elem = one;
629+
EXPECT_FALSE(shared.join(elem, one));
630+
EXPECT_EQ(elem, one);
631+
}
632+
633+
{
634+
auto elem = one;
635+
EXPECT_TRUE(shared.join(elem, two));
636+
EXPECT_EQ(elem, two);
637+
}
638+
639+
{
640+
auto elem = two;
641+
EXPECT_FALSE(shared.join(elem, zero));
642+
EXPECT_EQ(elem, two);
643+
}
644+
645+
{
646+
auto elem = two;
647+
EXPECT_FALSE(shared.join(elem, one));
648+
EXPECT_EQ(elem, two);
649+
}
650+
651+
{
652+
auto elem = two;
653+
EXPECT_FALSE(shared.join(elem, two));
654+
EXPECT_EQ(elem, two);
655+
}
656+
}

0 commit comments

Comments
 (0)