Skip to content

Commit dd2c3b6

Browse files
committed
Use direct pointers as Type IDs
Instead of using indices into the global interned type table. This means that a lock is *never* needed to access an expanded Type. The Type lock is now only acquired when a complex Type is created. On a real-world wasm2js workload this improves wall clock time by 23% on my machine with 72 cores and makes traffic on the Type lock entirely insignificant. **Before** 72 cores real 0m6.914s user 184.014s sys 0m3.995s 1 core real 0m25.903s user 0m25.658s sys 0m0.253s **After** 72 cores real 5.349s user 70.309s sys 9.691s 1 core real 25.859s user 25.615s sys 0.253s
1 parent 1b56c2b commit dd2c3b6

File tree

4 files changed

+45
-66
lines changed

4 files changed

+45
-66
lines changed

src/binaryen-c.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ typedef uint32_t BinaryenIndex;
9090
// Core types (call to get the value of each; you can cache them, they
9191
// never change)
9292

93-
typedef uint32_t BinaryenType;
93+
typedef uintptr_t BinaryenType;
9494

9595
BINARYEN_API BinaryenType BinaryenTypeNone(void);
9696
BINARYEN_API BinaryenType BinaryenTypeInt32(void);

src/passes/LocalCSE.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ struct LocalCSE : public WalkerPass<LinearExecutionWalker<LocalCSE>> {
6363

6464
struct UsableHasher {
6565
HashType operator()(const Usable value) const {
66-
return rehash(value.hashed.hash, value.localType.getID());
66+
return rehash(uint64_t(value.hashed.hash), value.localType.getID());
6767
}
6868
};
6969

src/wasm-type.h

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@
2424
namespace wasm {
2525

2626
class Type {
27-
// enough for the limit of 1000 function arguments
28-
static constexpr unsigned SIZE_BITS = 10;
29-
static constexpr unsigned ID_BITS = 32 - SIZE_BITS;
30-
unsigned id : ID_BITS;
31-
unsigned _size : SIZE_BITS;
27+
uintptr_t id;
3228
void init(const std::vector<Type>&);
3329

3430
public:
@@ -44,20 +40,16 @@ class Type {
4440
anyref,
4541
nullref,
4642
exnref,
43+
_last_value_type = exnref
4744
};
4845

49-
private:
50-
// Not in the enum because we don't want to have to have a case for it
51-
static constexpr uint32_t last_value_type = exnref;
52-
53-
public:
5446
Type() = default;
5547

5648
// ValueType can be implicitly upgraded to Type
57-
constexpr Type(ValueType id) : id(id), _size(id == none ? 0 : 1){};
49+
constexpr Type(ValueType id) : id(id){};
5850

5951
// But converting raw uint32_t is more dangerous, so make it explicit
60-
explicit Type(uint32_t id);
52+
explicit Type(uint64_t id) : id(id){};
6153

6254
// Construct from lists of elementary types
6355
Type(std::initializer_list<Type> types);
@@ -68,8 +60,10 @@ class Type {
6860
const std::vector<Type>& expand() const;
6961

7062
// Predicates
71-
constexpr bool isSingle() const { return id >= i32 && id <= last_value_type; }
72-
constexpr bool isMulti() const { return id > last_value_type; }
63+
constexpr bool isSingle() const {
64+
return id >= i32 && id <= _last_value_type;
65+
}
66+
constexpr bool isMulti() const { return id > _last_value_type; }
7367
constexpr bool isConcrete() const { return id >= i32; }
7468
constexpr bool isInteger() const { return id == i32 || id == i64; }
7569
constexpr bool isFloat() const { return id == f32 || id == f64; }
@@ -91,7 +85,7 @@ class Type {
9185
bool hasVector() { return hasPredicate<&Type::isVector>(); }
9286
bool hasRef() { return hasPredicate<&Type::isRef>(); }
9387

94-
constexpr uint32_t getID() const { return id; }
88+
constexpr uint64_t getID() const { return id; }
9589
constexpr ValueType getSingle() const {
9690
assert(!isMulti() && "Unexpected multivalue type");
9791
return static_cast<ValueType>(id);

src/wasm/wasm-type.cpp

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
template<> class std::hash<std::vector<wasm::Type>> {
2828
public:
2929
size_t operator()(const std::vector<wasm::Type>& types) const {
30-
uint32_t res = wasm::rehash(0, uint32_t(types.size()));
30+
uint64_t res = wasm::rehash(0, uint32_t(types.size()));
3131
for (auto t : types) {
3232
res = wasm::rehash(res, t.getID());
3333
}
@@ -36,13 +36,13 @@ template<> class std::hash<std::vector<wasm::Type>> {
3636
};
3737

3838
size_t std::hash<wasm::Type>::operator()(const wasm::Type& type) const {
39-
return std::hash<uint32_t>{}(type.getID());
39+
return std::hash<uint64_t>{}(type.getID());
4040
}
4141

4242
size_t std::hash<wasm::Signature>::
4343
operator()(const wasm::Signature& sig) const {
44-
return std::hash<uint64_t>{}(uint64_t(sig.params.getID()) << 32 |
45-
uint64_t(sig.results.getID()));
44+
return wasm::rehash(std::hash<uint64_t>{}(sig.params.getID()),
45+
std::hash<uint64_t>{}(sig.results.getID()));
4646
}
4747

4848
namespace wasm {
@@ -52,28 +52,20 @@ namespace {
5252
// TODO: switch to std::shared_mutex in C++17
5353
std::shared_timed_mutex mutex;
5454

55-
std::vector<std::unique_ptr<std::vector<Type>>> typeLists = [] {
56-
std::vector<std::unique_ptr<std::vector<Type>>> lists;
57-
58-
auto add = [&](std::initializer_list<Type> types) {
59-
return lists.push_back(std::make_unique<std::vector<Type>>(types));
60-
};
61-
62-
add({});
63-
add({Type::unreachable});
64-
add({Type::i32});
65-
add({Type::i64});
66-
add({Type::f32});
67-
add({Type::f64});
68-
add({Type::v128});
69-
add({Type::funcref});
70-
add({Type::anyref});
71-
add({Type::nullref});
72-
add({Type::exnref});
73-
return lists;
74-
}();
75-
76-
std::unordered_map<std::vector<Type>, uint32_t> indices = {
55+
std::array<std::vector<Type>, Type::_last_value_type + 1> typeLists = {
56+
std::vector<Type>{},
57+
{Type::unreachable},
58+
{Type::i32},
59+
{Type::i64},
60+
{Type::f32},
61+
{Type::f64},
62+
{Type::v128},
63+
{Type::funcref},
64+
{Type::anyref},
65+
{Type::nullref},
66+
{Type::exnref}};
67+
68+
std::unordered_map<std::vector<Type>, uintptr_t> indices = {
7769
{{}, Type::none},
7870
{{Type::unreachable}, Type::unreachable},
7971
{{Type::i32}, Type::i32},
@@ -96,10 +88,14 @@ void Type::init(const std::vector<Type>& types) {
9688
}
9789
#endif
9890

99-
if (types.size() >= (1 << SIZE_BITS)) {
100-
WASM_UNREACHABLE("Type too large");
91+
if (types.size() == 0) {
92+
id = none;
93+
return;
94+
}
95+
if (types.size() == 1) {
96+
*this = types[0];
97+
return;
10198
}
102-
_size = types.size();
10399

104100
auto lookup = [&]() {
105101
auto indexIt = indices.find(types);
@@ -124,11 +120,9 @@ void Type::init(const std::vector<Type>& types) {
124120
if (lookup()) {
125121
return;
126122
}
127-
if (typeLists.size() >= (1 << ID_BITS)) {
128-
WASM_UNREACHABLE("Too many types!");
129-
}
130-
id = typeLists.size();
131-
typeLists.push_back(std::make_unique<std::vector<Type>>(types));
123+
auto* vec = new std::vector<Type>(types);
124+
id = uintptr_t(vec);
125+
assert(id > _last_value_type);
132126
indices[types] = id;
133127
}
134128
}
@@ -137,23 +131,14 @@ Type::Type(std::initializer_list<Type> types) { init(types); }
137131

138132
Type::Type(const std::vector<Type>& types) { init(types); }
139133

140-
Type::Type(uint32_t _id) {
141-
if (_id <= last_value_type) {
142-
*this = Type(static_cast<ValueType>(_id));
143-
} else {
144-
id = _id;
145-
// Unknown complex type; look up the size
146-
std::shared_lock<std::shared_timed_mutex> lock(mutex);
147-
_size = typeLists[id]->size();
148-
}
149-
}
150-
151-
size_t Type::size() const { return _size; }
134+
size_t Type::size() const { return expand().size(); }
152135

153136
const std::vector<Type>& Type::expand() const {
154-
std::shared_lock<std::shared_timed_mutex> lock(mutex);
155-
assert(id < typeLists.size());
156-
return *typeLists[id].get();
137+
if (id <= _last_value_type) {
138+
return typeLists[id];
139+
} else {
140+
return *(std::vector<Type>*)id;
141+
}
157142
}
158143

159144
bool Type::operator<(const Type& other) const {

0 commit comments

Comments
 (0)