Skip to content

Commit 616165c

Browse files
Support (module definition ...) and (module instance ...) in WAST spec tests
1 parent 4ab430d commit 616165c

File tree

8 files changed

+157
-43
lines changed

8 files changed

+157
-43
lines changed

check.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,6 @@ def check_expected(actual, expected):
221221

222222
check_expected(actual, expected)
223223

224-
run_spec_test(wast)
225-
226224
# check binary format. here we can verify execution of the final
227225
# result, no need for an output verification
228226
actual = ''

scripts/test/shared.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ def get_tests(test_dir, extensions=[], recursive=False):
443443
'imports.wast', # Missing validation of missing function on instantiation
444444
'proposals/threads/imports.wast', # Missing memory type validation on instantiation
445445
'linking.wast', # Missing function type validation on instantiation
446-
'memory.wast', # Requires wast `module definition` support
446+
# 'memory.wast', # Requires wast `module definition` support
447447
'proposals/threads/memory.wast', # Missing memory type validation on instantiation
448448
'memory64-imports.wast', # Missing validation on instantiation
449449
'annotations.wast', # String annotations IDs should be allowed

scripts/test/support.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,13 @@ def untar(tarfile, outdir):
9090

9191
QUOTED = re.compile(r'\(module\s*(\$\S*)?\s+(quote|binary)')
9292

93+
MODULE_DEFINITION_OR_INSTANCE = re.compile(r'(?m)\(module\s+(instance|definition)')
9394

9495
def split_wast(wastFile):
96+
'''
97+
Returns a list of pairs of module definitions and assertions.
98+
Module invalidity tests, as well as (module definition ...) and (module instance ...) are skipped.
99+
'''
95100
# if it's a binary, leave it as is, we can't split it
96101
wast = None
97102
if not wastFile.endswith('.wasm'):
@@ -152,6 +157,8 @@ def to_end(j):
152157
ignoring_quoted = True
153158
continue
154159
if chunk.startswith('(module'):
160+
if MODULE_DEFINITION_OR_INSTANCE.match(chunk):
161+
continue
155162
ignoring_quoted = False
156163
ret += [(chunk, [])]
157164
elif chunk.startswith('(assert_invalid'):
@@ -190,14 +197,13 @@ def run_command(cmd, expected_status=0, stderr=None,
190197
out, err = proc.communicate()
191198
code = proc.returncode
192199
if expected_status is not None and code != expected_status:
193-
raise Exception(('run_command failed (%s)' % code, out + str(err or '')))
200+
raise Exception(f"run_command `{" ".join(cmd)}` failed ({code}) {err or ""}")
194201
if expected_err is not None:
195202
if err_ignore is not None:
196203
err = "\n".join([line for line in err.split('\n') if err_ignore not in line])
197204
err_correct = expected_err in err if err_contains else expected_err == err
198205
if not err_correct:
199-
raise Exception(('run_command unexpected stderr',
200-
"expected '%s', actual '%s'" % (expected_err, err)))
206+
raise Exception(f"run_command unexpected stderr. Expected '{expected_err}', actual '{err}'")
201207
return out
202208

203209

src/parser/wast-parser.cpp

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,20 @@ Result<Action> action(Lexer& in) {
8888
return in.err("expected action");
8989
}
9090

91-
// (module id? binary string*)
92-
// (module id? quote string*)
93-
// (module ...)
91+
// (module id binary string*)
92+
// (module id quote string*)
93+
// (module definition id? ...)
9494
Result<WASTModule> wastModule(Lexer& in, bool maybeInvalid = false) {
9595
Lexer reset = in;
9696
if (!in.takeSExprStart("module"sv)) {
9797
return in.err("expected module");
9898
}
99-
// TODO: use ID?
100-
[[maybe_unused]] auto id = in.takeID();
99+
100+
bool isDefinition = in.takeKeyword("definition"sv);
101+
102+
// We'll read this again in the 'inline module' case
103+
(void)in.takeID();
104+
101105
QuotedModuleType type;
102106
if (in.takeKeyword("quote"sv)) {
103107
type = QuotedModuleType::Text;
@@ -118,14 +122,31 @@ Result<WASTModule> wastModule(Lexer& in, bool maybeInvalid = false) {
118122
}
119123
}
120124
std::string mod(reset.next().substr(0, in.getPos() - reset.getPos()));
121-
return QuotedModule{QuotedModuleType::Text, mod};
125+
return QuotedModule{!isDefinition, QuotedModuleType::Text, mod};
122126
} else {
123-
// This is a normal inline module that should be parseable. Reset to the
124-
// start and parse it normally.
127+
// In this case the module is mostly valid WAT, unless it is a module
128+
// definition in which case it will begin with (module definition ...)
125129
in = std::move(reset);
130+
131+
// We already checked this before resetting
132+
if (!in.takeSExprStart("module"sv)) {
133+
return in.err("expected module");
134+
}
135+
136+
bool isDefinition = in.takeKeyword("definition"sv);
137+
126138
auto wasm = std::make_shared<Module>();
139+
if (auto id = in.takeID()) {
140+
wasm->name = *id;
141+
}
142+
127143
wasm->features = FeatureSet::All;
128-
CHECK_ERR(parseModule(*wasm, in));
144+
CHECK_ERR(parseModuleBody(*wasm, in));
145+
if (!in.takeRParen()) {
146+
return in.err("expected end of module");
147+
}
148+
149+
wasm->isDefinition = isDefinition;
129150
return wasm;
130151
}
131152

@@ -139,7 +160,7 @@ Result<WASTModule> wastModule(Lexer& in, bool maybeInvalid = false) {
139160
return in.err("expected end of module");
140161
}
141162

142-
return QuotedModule{type, ss.str()};
163+
return QuotedModule{isDefinition, type, ss.str()};
143164
}
144165

145166
Result<NaNKind> nan(Lexer& in) {
@@ -440,17 +461,64 @@ MaybeResult<Register> register_(Lexer& in) {
440461
return in.err("expected name");
441462
}
442463

443-
// TODO: Do we need to use this optional id?
444-
in.takeID();
464+
auto instanceName = in.takeID();
445465

446466
if (!in.takeRParen()) {
447-
// TODO: handle optional module id.
448467
return in.err("expected end of register command");
449468
}
450-
return Register{*name};
469+
470+
return Register{.name = *name, .instanceName = instanceName};
471+
}
472+
473+
// (module instance instance_name module_name)
474+
MaybeResult<ModuleInstantiation> instantiation(Lexer& in) {
475+
if (!in.takeSExprStart("module"sv)) {
476+
std::optional<std::string_view> actual = in.peekKeyword();
477+
return in.err((std::stringstream() << "expected `module` keyword but got "
478+
<< actual.value_or("<not a keyword>"))
479+
.str());
480+
}
481+
482+
if (!in.takeKeyword("instance"sv)) {
483+
// This is not a module instance and probably a module instead.
484+
return {};
485+
}
486+
487+
auto instanceId = in.takeID();
488+
if (!instanceId.has_value()) {
489+
return in.err("expected an instance id in module instantiation");
490+
}
491+
auto moduleId = in.takeID();
492+
if (!moduleId.has_value()) {
493+
return in.err("expected a module id in module instantiation");
494+
}
495+
496+
if (!in.takeRParen()) {
497+
return in.err("expected end of module instantiation");
498+
}
499+
500+
return ModuleInstantiation{.moduleName = *moduleId,
501+
.instanceName = *instanceId};
502+
}
503+
504+
using ModuleOrInstantiation = std::variant<ModuleInstantiation, WASTModule>;
505+
506+
// (module instance ...) | (module ...)
507+
Result<ModuleOrInstantiation> moduleOrInstantiation(Lexer& in) {
508+
auto reset = in;
509+
510+
if (auto inst = instantiation(in)) {
511+
CHECK_ERR(inst);
512+
return *inst;
513+
}
514+
in = reset;
515+
516+
auto module = wastModule(in);
517+
CHECK_ERR(module);
518+
return *module;
451519
}
452520

453-
// module | register | action | assertion
521+
// instantiate | module | register | action | assertion
454522
Result<WASTCommand> command(Lexer& in) {
455523
if (auto cmd = register_(in)) {
456524
CHECK_ERR(cmd);
@@ -464,9 +532,12 @@ Result<WASTCommand> command(Lexer& in) {
464532
CHECK_ERR(cmd);
465533
return *cmd;
466534
}
467-
auto mod = wastModule(in);
468-
CHECK_ERR(mod);
469-
return *mod;
535+
auto cmd = moduleOrInstantiation(in);
536+
CHECK_ERR(cmd);
537+
538+
return std::visit(
539+
[](const auto& modOrInstantiation) -> WASTCommand { return modOrInstantiation; },
540+
*cmd);
470541
}
471542

472543
#pragma GCC diagnostic push

src/parser/wat-parser.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,8 @@ Result<> parseModule(Module& wasm, Lexer& lexer) {
137137
return doParseModule(wasm, lexer, true);
138138
}
139139

140+
Result<> parseModuleBody(Module& wasm, Lexer& lexer) {
141+
return doParseModule(wasm, lexer, true);
142+
}
143+
140144
} // namespace wasm::WATParser

src/parser/wat-parser.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ Result<> parseModule(Module& wasm,
3434
// file.
3535
Result<> parseModule(Module& wasm, Lexer& lexer);
3636

37+
// Similar to `parseModule`, parse the fields of a single WAT module (after the
38+
// initial module definition including its name) and stop at the ending right
39+
// paren.
40+
Result<> parseModuleBody(Module& wasm, Lexer& lexer);
41+
3742
Result<Literal> parseConst(Lexer& lexer);
3843

3944
#pragma GCC diagnostic push
@@ -88,6 +93,7 @@ struct AssertAction {
8893
enum class QuotedModuleType { Text, Binary };
8994

9095
struct QuotedModule {
96+
bool isDefinition = false;
9197
QuotedModuleType type;
9298
std::string module;
9399
};
@@ -104,10 +110,18 @@ struct AssertModule {
104110
using Assertion = std::variant<AssertReturn, AssertAction, AssertModule>;
105111

106112
struct Register {
113+
// TODO: rename this to distinguish it from instanceName
107114
Name name;
115+
std::optional<Name> instanceName = std::nullopt;
116+
};
117+
118+
struct ModuleInstantiation {
119+
Name moduleName;
120+
Name instanceName;
108121
};
109122

110-
using WASTCommand = std::variant<WASTModule, Register, Action, Assertion>;
123+
using WASTCommand =
124+
std::variant<WASTModule, Register, Action, Assertion, ModuleInstantiation>;
111125

112126
struct ScriptEntry {
113127
WASTCommand cmd;

0 commit comments

Comments
 (0)