Skip to content

Commit 7c8cd2f

Browse files
authored
Support atomic struct accessors (#7155)
Implement support for both sequentially consistent and acquire-release variants of `struct.atomic.get` and `struct.atomic.set`, as proposed by shared-everything-threads. Introduce a new `MemoryOrdering` enum for describing different levels of atomicity (or the lack thereof). This new enum should eventually be adopted by linear memory atomic accessors as well to support acquire-release semantics, but for now just use it in `StructGet` and `StructSet`. In addition to implementing parsing and emitting for the instructions, validate that shared-everything is enabled to use them, mark them as having synchronization side effects, and lightly optimize them by relaxing acquire-release accesses to non-shared structs to normal, unordered accesses. This is valid because such accesses cannot possibly synchronize with other threads. Also update Precompute to avoid optimizing out synchronization points. There are probably other passes that need to be updated to avoid incorrectly optimizing synchronizing accesses, but identifying and fixing them is left as future work.
1 parent 9f5f8dd commit 7c8cd2f

22 files changed

+813
-42
lines changed

scripts/gen-s-parser.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,11 @@
617617
("struct.get", "makeStructGet()"),
618618
("struct.get_s", "makeStructGet(true)"),
619619
("struct.get_u", "makeStructGet(false)"),
620+
("struct.atomic.get", "makeAtomicStructGet()"),
621+
("struct.atomic.get_s", "makeAtomicStructGet(true)"),
622+
("struct.atomic.get_u", "makeAtomicStructGet(false)"),
620623
("struct.set", "makeStructSet()"),
624+
("struct.atomic.set", "makeAtomicStructSet()"),
621625
("array.new", "makeArrayNew(false)"),
622626
("array.new_default", "makeArrayNew(true)"),
623627
("array.new_data", "makeArrayNewData()"),

src/gen-s-parser.inc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5013,6 +5013,45 @@ switch (buf[0]) {
50135013
}
50145014
case 'u': {
50155015
switch (buf[7]) {
5016+
case 'a': {
5017+
switch (buf[14]) {
5018+
case 'g': {
5019+
switch (buf[17]) {
5020+
case '\0':
5021+
if (op == "struct.atomic.get"sv) {
5022+
CHECK_ERR(makeAtomicStructGet(ctx, pos, annotations));
5023+
return Ok{};
5024+
}
5025+
goto parse_error;
5026+
case '_': {
5027+
switch (buf[18]) {
5028+
case 's':
5029+
if (op == "struct.atomic.get_s"sv) {
5030+
CHECK_ERR(makeAtomicStructGet(ctx, pos, annotations, true));
5031+
return Ok{};
5032+
}
5033+
goto parse_error;
5034+
case 'u':
5035+
if (op == "struct.atomic.get_u"sv) {
5036+
CHECK_ERR(makeAtomicStructGet(ctx, pos, annotations, false));
5037+
return Ok{};
5038+
}
5039+
goto parse_error;
5040+
default: goto parse_error;
5041+
}
5042+
}
5043+
default: goto parse_error;
5044+
}
5045+
}
5046+
case 's':
5047+
if (op == "struct.atomic.set"sv) {
5048+
CHECK_ERR(makeAtomicStructSet(ctx, pos, annotations));
5049+
return Ok{};
5050+
}
5051+
goto parse_error;
5052+
default: goto parse_error;
5053+
}
5054+
}
50165055
case 'g': {
50175056
switch (buf[10]) {
50185057
case '\0':

src/ir/effects.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,18 @@ class EffectAnalyzer {
872872
if (curr->ref->type.isNullable()) {
873873
parent.implicitTrap = true;
874874
}
875+
switch (curr->order) {
876+
case MemoryOrder::Unordered:
877+
break;
878+
case MemoryOrder::SeqCst:
879+
// Synchronizes with other threads.
880+
parent.isAtomic = true;
881+
break;
882+
case MemoryOrder::AcqRel:
883+
// Only synchronizes if other threads can read the field.
884+
parent.isAtomic = curr->ref->type.getHeapType().isShared();
885+
break;
886+
}
875887
}
876888
void visitStructSet(StructSet* curr) {
877889
if (curr->ref->type.isNull()) {
@@ -883,6 +895,9 @@ class EffectAnalyzer {
883895
if (curr->ref->type.isNullable()) {
884896
parent.implicitTrap = true;
885897
}
898+
if (curr->order != MemoryOrder::Unordered) {
899+
parent.isAtomic = true;
900+
}
886901
}
887902
void visitArrayNew(ArrayNew* curr) {}
888903
void visitArrayNewData(ArrayNewData* curr) {

src/parser/contexts.h

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -735,13 +735,20 @@ struct NullInstrParserCtx {
735735
return Ok{};
736736
}
737737
template<typename HeapTypeT>
738-
Result<> makeStructGet(
739-
Index, const std::vector<Annotation>&, HeapTypeT, FieldIdxT, bool) {
738+
Result<> makeStructGet(Index,
739+
const std::vector<Annotation>&,
740+
HeapTypeT,
741+
FieldIdxT,
742+
bool,
743+
MemoryOrder = MemoryOrder::Unordered) {
740744
return Ok{};
741745
}
742746
template<typename HeapTypeT>
743-
Result<>
744-
makeStructSet(Index, const std::vector<Annotation>&, HeapTypeT, FieldIdxT) {
747+
Result<> makeStructSet(Index,
748+
const std::vector<Annotation>&,
749+
HeapTypeT,
750+
FieldIdxT,
751+
MemoryOrder = MemoryOrder::Unordered) {
745752
return Ok{};
746753
}
747754
template<typename HeapTypeT>
@@ -2448,15 +2455,17 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
24482455
const std::vector<Annotation>& annotations,
24492456
HeapType type,
24502457
Index field,
2451-
bool signed_) {
2452-
return withLoc(pos, irBuilder.makeStructGet(type, field, signed_));
2458+
bool signed_,
2459+
MemoryOrder order = MemoryOrder::Unordered) {
2460+
return withLoc(pos, irBuilder.makeStructGet(type, field, signed_, order));
24532461
}
24542462

24552463
Result<> makeStructSet(Index pos,
24562464
const std::vector<Annotation>& annotations,
24572465
HeapType type,
2458-
Index field) {
2459-
return withLoc(pos, irBuilder.makeStructSet(type, field));
2466+
Index field,
2467+
MemoryOrder order = MemoryOrder::Unordered) {
2468+
return withLoc(pos, irBuilder.makeStructSet(type, field, order));
24602469
}
24612470

24622471
Result<> makeArrayNew(Index pos,

src/parser/parsers.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ template<typename Ctx> Result<typename Ctx::LimitsT> limits64(Ctx&);
4848
template<typename Ctx> Result<typename Ctx::MemTypeT> memtype(Ctx&);
4949
template<typename Ctx>
5050
Result<typename Ctx::MemTypeT> memtypeContinued(Ctx&, Type addressType);
51+
template<typename Ctx> Result<MemoryOrder> memorder(Ctx&);
5152
template<typename Ctx> Result<typename Ctx::TableTypeT> tabletype(Ctx&);
5253
template<typename Ctx>
5354
Result<typename Ctx::TableTypeT> tabletypeContinued(Ctx&, Type addressType);
@@ -246,8 +247,15 @@ Result<> makeStructGet(Ctx&,
246247
const std::vector<Annotation>&,
247248
bool signed_ = false);
248249
template<typename Ctx>
250+
Result<> makeAtomicStructGet(Ctx&,
251+
Index,
252+
const std::vector<Annotation>&,
253+
bool signed_ = false);
254+
template<typename Ctx>
249255
Result<> makeStructSet(Ctx&, Index, const std::vector<Annotation>&);
250256
template<typename Ctx>
257+
Result<> makeAtomicStructSet(Ctx&, Index, const std::vector<Annotation>&);
258+
template<typename Ctx>
251259
Result<>
252260
makeArrayNew(Ctx&, Index, const std::vector<Annotation>&, bool default_);
253261
template<typename Ctx>
@@ -801,6 +809,17 @@ Result<typename Ctx::MemTypeT> memtypeContinued(Ctx& ctx, Type addressType) {
801809
return ctx.makeMemType(addressType, *limits, shared);
802810
}
803811

812+
// memorder ::= '' | 'seqcst' | 'acqrel'
813+
template<typename Ctx> Result<MemoryOrder> memorder(Ctx& ctx) {
814+
if (ctx.in.takeKeyword("seqcst"sv)) {
815+
return MemoryOrder::SeqCst;
816+
}
817+
if (ctx.in.takeKeyword("acqrel"sv)) {
818+
return MemoryOrder::AcqRel;
819+
}
820+
return MemoryOrder::SeqCst;
821+
}
822+
804823
// tabletype ::= (limits32 | 'i32' limits32 | 'i64' limit64) reftype
805824
template<typename Ctx> Result<typename Ctx::TableTypeT> tabletype(Ctx& ctx) {
806825
Type addressType = Type::i32;
@@ -2224,6 +2243,20 @@ Result<> makeStructGet(Ctx& ctx,
22242243
return ctx.makeStructGet(pos, annotations, *type, *field, signed_);
22252244
}
22262245

2246+
template<typename Ctx>
2247+
Result<> makeAtomicStructGet(Ctx& ctx,
2248+
Index pos,
2249+
const std::vector<Annotation>& annotations,
2250+
bool signed_) {
2251+
auto order = memorder(ctx);
2252+
CHECK_ERR(order);
2253+
auto type = typeidx(ctx);
2254+
CHECK_ERR(type);
2255+
auto field = fieldidx(ctx, *type);
2256+
CHECK_ERR(field);
2257+
return ctx.makeStructGet(pos, annotations, *type, *field, signed_, *order);
2258+
}
2259+
22272260
template<typename Ctx>
22282261
Result<>
22292262
makeStructSet(Ctx& ctx, Index pos, const std::vector<Annotation>& annotations) {
@@ -2234,6 +2267,19 @@ makeStructSet(Ctx& ctx, Index pos, const std::vector<Annotation>& annotations) {
22342267
return ctx.makeStructSet(pos, annotations, *type, *field);
22352268
}
22362269

2270+
template<typename Ctx>
2271+
Result<> makeAtomicStructSet(Ctx& ctx,
2272+
Index pos,
2273+
const std::vector<Annotation>& annotations) {
2274+
auto order = memorder(ctx);
2275+
CHECK_ERR(order);
2276+
auto type = typeidx(ctx);
2277+
CHECK_ERR(type);
2278+
auto field = fieldidx(ctx, *type);
2279+
CHECK_ERR(field);
2280+
return ctx.makeStructSet(pos, annotations, *type, *field, *order);
2281+
}
2282+
22372283
template<typename Ctx>
22382284
Result<> makeArrayNew(Ctx& ctx,
22392285
Index pos,

src/passes/OptimizeInstructions.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,6 +1831,12 @@ struct OptimizeInstructions
18311831
void visitStructGet(StructGet* curr) {
18321832
skipNonNullCast(curr->ref, curr);
18331833
trapOnNull(curr, curr->ref);
1834+
// Relax acquire loads of unshared fields to unordered because they cannot
1835+
// synchronize with other threads.
1836+
if (curr->order == MemoryOrder::AcqRel && curr->ref->type.isRef() &&
1837+
!curr->ref->type.getHeapType().isShared()) {
1838+
curr->order = MemoryOrder::Unordered;
1839+
}
18341840
}
18351841

18361842
void visitStructSet(StructSet* curr) {
@@ -1847,6 +1853,13 @@ struct OptimizeInstructions
18471853
optimizeStoredValue(curr->value, fields[curr->index].getByteSize());
18481854
}
18491855
}
1856+
1857+
// Relax release stores of unshared fields to unordered because they cannot
1858+
// synchronize with other threads.
1859+
if (curr->order == MemoryOrder::AcqRel && curr->ref->type.isRef() &&
1860+
!curr->ref->type.getHeapType().isShared()) {
1861+
curr->order = MemoryOrder::Unordered;
1862+
}
18501863
}
18511864

18521865
void visitArrayNew(ArrayNew* curr) {

src/passes/Precompute.cpp

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,37 @@ class PrecomputingExpressionRunner
134134
}
135135
Flow visitStructSet(StructSet* curr) { return Flow(NONCONSTANT_FLOW); }
136136
Flow visitStructGet(StructGet* curr) {
137-
if (curr->ref->type != Type::unreachable && !curr->ref->type.isNull()) {
138-
// If this field is immutable then we may be able to precompute this, as
139-
// if we also created the data in this function (or it was created in an
140-
// immutable global) then we know the value in the field. If it is
141-
// immutable, call the super method which will do the rest here. That
142-
// includes checking for the data being properly created, as if it was
143-
// not then we will not have a constant value for it, which means the
144-
// local.get of that value will stop us.
145-
auto& field =
146-
curr->ref->type.getHeapType().getStruct().fields[curr->index];
147-
if (field.mutable_ == Immutable) {
148-
return Super::visitStructGet(curr);
149-
}
137+
if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) {
138+
return Flow(NONCONSTANT_FLOW);
150139
}
151-
152-
// Otherwise, we've failed to precompute.
153-
return Flow(NONCONSTANT_FLOW);
140+
switch (curr->order) {
141+
case MemoryOrder::Unordered:
142+
// This can always be precomputed.
143+
break;
144+
case MemoryOrder::SeqCst:
145+
// This can never be precomputed away because it synchronizes with other
146+
// threads.
147+
return Flow(NONCONSTANT_FLOW);
148+
case MemoryOrder::AcqRel:
149+
// This synchronizes only with writes to the same data, so it can still
150+
// be precomputed if the data is not shared with other threads.
151+
if (curr->ref->type.getHeapType().isShared()) {
152+
return Flow(NONCONSTANT_FLOW);
153+
}
154+
break;
155+
}
156+
// If this field is immutable then we may be able to precompute this, as
157+
// if we also created the data in this function (or it was created in an
158+
// immutable global) then we know the value in the field. If it is
159+
// immutable, call the super method which will do the rest here. That
160+
// includes checking for the data being properly created, as if it was
161+
// not then we will not have a constant value for it, which means the
162+
// local.get of that value will stop us.
163+
auto& field = curr->ref->type.getHeapType().getStruct().fields[curr->index];
164+
if (field.mutable_ == Mutable) {
165+
return Flow(NONCONSTANT_FLOW);
166+
}
167+
return Super::visitStructGet(curr);
154168
}
155169
Flow visitArrayNew(ArrayNew* curr) {
156170
auto flow = Super::visitArrayNew(curr);

src/passes/Print.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2276,24 +2276,47 @@ struct PrintExpressionContents
22762276
o << index;
22772277
}
22782278
}
2279+
void printMemoryOrder(MemoryOrder order) {
2280+
switch (order) {
2281+
// Unordered should have a different base instruction, so there is nothing
2282+
// to print. We could be explicit and print seqcst, but we choose not to
2283+
// for more concise output.
2284+
case MemoryOrder::Unordered:
2285+
case MemoryOrder::SeqCst:
2286+
break;
2287+
case MemoryOrder::AcqRel:
2288+
o << "acqrel ";
2289+
break;
2290+
}
2291+
}
22792292
void visitStructGet(StructGet* curr) {
22802293
auto heapType = curr->ref->type.getHeapType();
22812294
const auto& field = heapType.getStruct().fields[curr->index];
2295+
printMedium(o, "struct");
2296+
if (curr->order != MemoryOrder::Unordered) {
2297+
printMedium(o, ".atomic");
2298+
}
22822299
if (field.type == Type::i32 && field.packedType != Field::not_packed) {
22832300
if (curr->signed_) {
2284-
printMedium(o, "struct.get_s ");
2301+
printMedium(o, ".get_s ");
22852302
} else {
2286-
printMedium(o, "struct.get_u ");
2303+
printMedium(o, ".get_u ");
22872304
}
22882305
} else {
2289-
printMedium(o, "struct.get ");
2306+
printMedium(o, ".get ");
22902307
}
2308+
printMemoryOrder(curr->order);
22912309
printHeapType(heapType);
22922310
o << ' ';
22932311
printFieldName(heapType, curr->index);
22942312
}
22952313
void visitStructSet(StructSet* curr) {
2296-
printMedium(o, "struct.set ");
2314+
if (curr->order == MemoryOrder::Unordered) {
2315+
printMedium(o, "struct.set ");
2316+
} else {
2317+
printMedium(o, "struct.atomic.set ");
2318+
}
2319+
printMemoryOrder(curr->order);
22972320
auto heapType = curr->ref->type.getHeapType();
22982321
printHeapType(heapType);
22992322
o << ' ';

src/wasm-binary.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,15 @@ enum ASTNodes {
11251125
I31GetU = 0x1e,
11261126
RefI31Shared = 0x1f,
11271127

1128+
// Shared GC Opcodes
1129+
1130+
OrderSeqCst = 0x0,
1131+
OrderAcqRel = 0x1,
1132+
StructAtomicGet = 0x5c,
1133+
StructAtomicGetS = 0x5d,
1134+
StructAtomicGetU = 0x5e,
1135+
StructAtomicSet = 0x5f,
1136+
11281137
// stringref opcodes
11291138

11301139
StringConst = 0x82,
@@ -1352,6 +1361,8 @@ class WasmBinaryWriter {
13521361

13531362
void writeField(const Field& field);
13541363

1364+
void writeMemoryOrder(MemoryOrder order);
1365+
13551366
private:
13561367
Module* wasm;
13571368
BufferWithRandomAccess& o;
@@ -1587,6 +1598,7 @@ class WasmBinaryReader {
15871598

15881599
Index readMemoryAccess(Address& alignment, Address& offset);
15891600
std::tuple<Name, Address, Address> getMemarg();
1601+
MemoryOrder getMemoryOrder();
15901602

15911603
[[noreturn]] void throwError(std::string text) {
15921604
throw ParseException(text, 0, pos);

0 commit comments

Comments
 (0)