Skip to content

Commit 8968d0f

Browse files
Parsing for thread blocks
1 parent 7317be6 commit 8968d0f

File tree

18 files changed

+1795
-6
lines changed

18 files changed

+1795
-6
lines changed

check.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,10 @@ def check_expected(actual, expected, stdout=None):
200200

201201
UNSPLITTABLE_TESTS = [Path(x) for x in [
202202
"spec/testsuite/instance.wast",
203-
"spec/instance.wast"]]
203+
"spec/instance.wast",
204+
205+
# TODO: support module splitting for (thread ...) blocks
206+
"spec/threads/*"]]
204207

205208

206209
def is_splittable(wast: Path):

scripts/test/shared.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,15 @@ def get_tests(test_dir, extensions=[], recursive=False):
394394

395395
# Test invalid
396396
'elem.wast',
397+
398+
# Requires wast `either` support
399+
'threads/thread.wast',
400+
401+
# Requires better support for multi-threaded tests
402+
'threads/wait_notify.wast',
403+
404+
# Non-natural alignment is invalid for atomic operations
405+
'threads/atomic.wast',
397406
]
398407
SPEC_TESTSUITE_PROPOSALS_TO_SKIP = [
399408
'custom-page-sizes',

src/parser/wast-parser.cpp

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ using namespace std::string_view_literals;
2525

2626
namespace {
2727

28+
Result<WASTCommand> command(Lexer& in);
29+
2830
Result<Literal> const_(Lexer& in) {
2931
if (in.takeSExprStart("ref.extern"sv)) {
3032
auto n = in.takeI32();
@@ -496,12 +498,79 @@ MaybeResult<ModuleInstantiation> instantiation(Lexer& in) {
496498
return ModuleInstantiation{moduleId, instanceId};
497499
}
498500

499-
// instantiate | module | register | action | assertion
501+
// (thread name (shared (module name))? command*)
502+
MaybeResult<ThreadBlock> threadBlock(Lexer& in) {
503+
if (!in.takeSExprStart("thread"sv)) {
504+
return {};
505+
}
506+
507+
auto name = in.takeID();
508+
if (!name) {
509+
return in.err("expected thread name");
510+
}
511+
512+
std::optional<Name> sharedModule;
513+
if (in.takeSExprStart("shared"sv)) {
514+
if (!in.takeSExprStart("module"sv)) {
515+
return in.err("expected module keyword in (shared ...) block");
516+
}
517+
518+
auto modName = in.takeID();
519+
if (!modName) {
520+
return in.err("expected module name after (shared (module ...))");
521+
}
522+
sharedModule = *modName;
523+
524+
if (!in.takeRParen()) {
525+
return in.err("expected end of shared module");
526+
}
527+
if (!in.takeRParen()) {
528+
return in.err("expected end of (shared ...) expression");
529+
}
530+
}
531+
532+
std::vector<ScriptEntry> commands;
533+
while (!in.peekRParen() && !in.empty()) {
534+
size_t line = in.position().line;
535+
auto cmd = command(in);
536+
CHECK_ERR(cmd);
537+
commands.push_back({std::move(*cmd), line});
538+
}
539+
if (!in.takeRParen()) {
540+
return in.err("expected end of thread");
541+
}
542+
return ThreadBlock{*name, sharedModule, std::move(commands)};
543+
}
544+
545+
// (wait name)
546+
MaybeResult<Wait> wait(Lexer& in) {
547+
if (!in.takeSExprStart("wait"sv)) {
548+
return {};
549+
}
550+
auto name = in.takeID();
551+
if (!name) {
552+
return in.err("expected thread name in wait");
553+
}
554+
if (!in.takeRParen()) {
555+
return in.err("expected end of wait");
556+
}
557+
return Wait{*name};
558+
}
559+
560+
// instantiate | module | register | action | assertion | thread | wait
500561
Result<WASTCommand> command(Lexer& in) {
501562
if (auto cmd = register_(in)) {
502563
CHECK_ERR(cmd);
503564
return *cmd;
504565
}
566+
if (auto cmd = threadBlock(in)) {
567+
CHECK_ERR(cmd);
568+
return *cmd;
569+
}
570+
if (auto cmd = wait(in)) {
571+
CHECK_ERR(cmd);
572+
return *cmd;
573+
}
505574
if (auto cmd = maybeAction(in)) {
506575
CHECK_ERR(cmd);
507576
return *cmd;

src/parser/wat-parser.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,32 @@ struct ModuleInstantiation {
128128
std::optional<Name> instanceName;
129129
};
130130

131-
using WASTCommand =
132-
std::variant<WASTModule, Register, Action, Assertion, ModuleInstantiation>;
131+
struct ScriptEntry;
132+
using WASTScript = std::vector<ScriptEntry>;
133+
134+
struct ThreadBlock {
135+
Name name;
136+
std::optional<Name> sharedModule;
137+
WASTScript commands;
138+
};
139+
140+
struct Wait {
141+
Name thread;
142+
};
143+
144+
using WASTCommand = std::variant<WASTModule,
145+
Register,
146+
Action,
147+
Assertion,
148+
ModuleInstantiation,
149+
ThreadBlock,
150+
Wait>;
133151

134152
struct ScriptEntry {
135153
WASTCommand cmd;
136154
size_t line;
137155
};
138156

139-
using WASTScript = std::vector<ScriptEntry>;
140-
141157
Result<WASTScript> parseScript(std::string_view in);
142158

143159
} // namespace wasm::WATParser

src/tools/wasm-shell.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,21 @@ struct Shell {
9393
} else if (auto* instantiateModule =
9494
std::get_if<ModuleInstantiation>(&cmd)) {
9595
return doInstantiate(*instantiateModule);
96+
} else if (auto* thread = std::get_if<ThreadBlock>(&cmd)) {
97+
return doThread(*thread);
98+
} else if (auto* wait = std::get_if<Wait>(&cmd)) {
99+
return doWait(*wait);
96100
} else {
97101
WASM_UNREACHABLE("unexpected command");
98102
}
99103
}
100104

105+
// Run threads in a blocking manner for now.
106+
// TODO: yield on blocking instructions e.g. memory.atomic.wait32.
107+
Result<> doThread(ThreadBlock& thread) { return run(thread.commands); }
108+
109+
Result<> doWait(Wait& wait) { return Ok{}; }
110+
101111
Result<std::shared_ptr<Module>> makeModule(WASTModule& mod) {
102112
std::shared_ptr<Module> wasm;
103113
if (auto* quoted = std::get_if<QuotedModule>(&mod.module)) {

test/spec/threads/LB.wast

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
(module $Mem
2+
(memory (export "shared") 1 1 shared)
3+
)
4+
(register "mem")
5+
6+
(thread $T1 (shared (module $Mem))
7+
(register "mem" $Mem)
8+
(module
9+
(memory (import "mem" "shared") 1 1 shared)
10+
(func (export "run")
11+
(local i32)
12+
(i32.load (i32.const 4))
13+
(local.set 0)
14+
(i32.store (i32.const 0) (i32.const 1))
15+
16+
;; store results for checking
17+
(i32.store (i32.const 24) (local.get 0))
18+
)
19+
)
20+
(invoke "run")
21+
)
22+
23+
(thread $T2 (shared (module $Mem))
24+
(register "mem" $Mem)
25+
(module
26+
(memory (import "mem" "shared") 1 1 shared)
27+
(func (export "run")
28+
(local i32)
29+
(i32.load (i32.const 0))
30+
(local.set 0)
31+
(i32.store (i32.const 4) (i32.const 1))
32+
33+
;; store results for checking
34+
(i32.store (i32.const 32) (local.get 0))
35+
)
36+
)
37+
38+
(invoke "run")
39+
)
40+
41+
(wait $T1)
42+
(wait $T2)
43+
44+
(module $Check
45+
(memory (import "mem" "shared") 1 1 shared)
46+
47+
(func (export "check") (result i32)
48+
(local i32 i32)
49+
(i32.load (i32.const 24))
50+
(local.set 0)
51+
(i32.load (i32.const 32))
52+
(local.set 1)
53+
54+
;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 1)
55+
56+
(i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
57+
(i32.or (i32.eq (local.get 1) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0)))
58+
(i32.and)
59+
(return)
60+
)
61+
)
62+
63+
(assert_return (invoke $Check "check") (i32.const 1))

test/spec/threads/LB_atomic.wast

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
(module $Mem
2+
(memory (export "shared") 1 1 shared)
3+
)
4+
(register "mem")
5+
6+
(thread $T1 (shared (module $Mem))
7+
(register "mem" $Mem)
8+
(module
9+
(memory (import "mem" "shared") 1 1 shared)
10+
(func (export "run")
11+
(local i32)
12+
(i32.atomic.load (i32.const 4))
13+
(local.set 0)
14+
(i32.atomic.store (i32.const 0) (i32.const 1))
15+
16+
;; store results for checking
17+
(i32.store (i32.const 24) (local.get 0))
18+
)
19+
)
20+
(invoke "run")
21+
)
22+
23+
(thread $T2 (shared (module $Mem))
24+
(register "mem" $Mem)
25+
(module
26+
(memory (import "mem" "shared") 1 1 shared)
27+
(func (export "run")
28+
(local i32)
29+
(i32.atomic.load (i32.const 0))
30+
(local.set 0)
31+
(i32.atomic.store (i32.const 4) (i32.const 1))
32+
33+
;; store results for checking
34+
(i32.store (i32.const 32) (local.get 0))
35+
)
36+
)
37+
38+
(invoke "run")
39+
)
40+
41+
(wait $T1)
42+
(wait $T2)
43+
44+
(module $Check
45+
(memory (import "mem" "shared") 1 1 shared)
46+
47+
(func (export "check") (result i32)
48+
(local i32 i32)
49+
(i32.load (i32.const 24))
50+
(local.set 0)
51+
(i32.load (i32.const 32))
52+
(local.set 1)
53+
54+
;; allowed results: (L_0 = 0 && L_1 = 0) || (L_0 = 0 && L_1 = 1) || (L_0 = 1 && L_1 = 0)
55+
56+
(i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 0)))
57+
(i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 1)))
58+
(i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0)))
59+
(i32.or)
60+
(i32.or)
61+
(return)
62+
)
63+
)
64+
65+
(assert_return (invoke $Check "check") (i32.const 1))

test/spec/threads/MP.wast

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
(module $Mem
2+
(memory (export "shared") 1 1 shared)
3+
)
4+
(register "mem")
5+
6+
(thread $T1 (shared (module $Mem))
7+
(register "mem" $Mem)
8+
(module
9+
(memory (import "mem" "shared") 1 1 shared)
10+
(func (export "run")
11+
(i32.store (i32.const 0) (i32.const 42))
12+
(i32.store (i32.const 4) (i32.const 1))
13+
)
14+
)
15+
(invoke "run")
16+
)
17+
18+
(thread $T2 (shared (module $Mem))
19+
(register "mem" $Mem)
20+
(module
21+
(memory (import "mem" "shared") 1 1 shared)
22+
(func (export "run")
23+
(local i32 i32)
24+
(i32.load (i32.const 4))
25+
(local.set 0)
26+
(i32.load (i32.const 0))
27+
(local.set 1)
28+
29+
;; store results for checking
30+
(i32.store (i32.const 24) (local.get 0))
31+
(i32.store (i32.const 32) (local.get 1))
32+
)
33+
)
34+
35+
(invoke "run")
36+
)
37+
38+
(wait $T1)
39+
(wait $T2)
40+
41+
(module $Check
42+
(memory (import "mem" "shared") 1 1 shared)
43+
44+
(func (export "check") (result i32)
45+
(local i32 i32)
46+
(i32.load (i32.const 24))
47+
(local.set 0)
48+
(i32.load (i32.const 32))
49+
(local.set 1)
50+
51+
;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 42)
52+
53+
(i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0)))
54+
(i32.or (i32.eq (local.get 1) (i32.const 42)) (i32.eq (local.get 1) (i32.const 0)))
55+
(i32.and)
56+
(return)
57+
)
58+
)
59+
60+
(assert_return (invoke $Check "check") (i32.const 1))

0 commit comments

Comments
 (0)