Skip to content

Commit 2480082

Browse files
Support (module definition ...) and (module instance ...) in WAST spec tests
1 parent b141e31 commit 2480082

File tree

8 files changed

+146
-45
lines changed

8 files changed

+146
-45
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: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,12 +435,11 @@ def get_tests(test_dir, extensions=[], recursive=False):
435435
'if.wast', # Requires more precise unreachable validation
436436
'imports.wast', # Missing validation of missing function on instantiation
437437
'linking.wast', # Missing function type validation on instantiation
438-
'memory.wast', # Requires wast `module definition` support
439438
'memory64-imports.wast', # Missing validation on instantiation
440439
'annotations.wast', # String annotations IDs should be allowed
441440
'id.wast', # Empty IDs should be disallowed
442-
'instance.wast', # Requires wast `module definition` support
443-
'table64.wast', # Requires wast `module definition` support
441+
'instance.wast', # Requires ref.null wast constants, and more correct handling for tag imports from different instances of the same module
442+
'table64.wast', # Requires additional validations on table size
444443
'table_grow.wast', # Incorrect table linking semantics in interpreter
445444
'tag.wast', # Non-empty tag results allowed by stack switching
446445
'try_table.wast', # Requires try_table interpretation
@@ -464,7 +463,7 @@ def get_tests(test_dir, extensions=[], recursive=False):
464463
'type-rec.wast', # Missing function type validation on instantiation
465464
'type-subtyping.wast', # ShellExternalInterface::callTable does not handle subtyping
466465
'call_indirect.wast', # Bug with 64-bit inline element segment parsing
467-
'memory64.wast', # Requires wast `module definition` support
466+
'memory64.wast', # Requires additional validations on memory size
468467
'imports0.wast', # Missing memory type validation on instantiation
469468
'imports2.wast', # Missing memory type validation on instantiation
470469
'imports3.wast', # Missing memory type validation on instantiation

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: 83 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,30 @@ 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 definition in which case it will begin with (module definition ...)
125128
in = std::move(reset);
129+
130+
// We already checked this before resetting
131+
if (!in.takeSExprStart("module"sv)) {
132+
return in.err("expected module");
133+
}
134+
135+
bool isDefinition = in.takeKeyword("definition"sv);
136+
126137
auto wasm = std::make_shared<Module>();
138+
if (auto id = in.takeID()) {
139+
wasm->name = *id;
140+
}
141+
127142
wasm->features = FeatureSet::All;
128-
CHECK_ERR(parseModule(*wasm, in));
143+
CHECK_ERR(parseModuleBody(*wasm, in));
144+
if (!in.takeRParen()) {
145+
return in.err("expected end of module");
146+
}
147+
148+
wasm->isDefinition = isDefinition;
129149
return wasm;
130150
}
131151

@@ -139,7 +159,7 @@ Result<WASTModule> wastModule(Lexer& in, bool maybeInvalid = false) {
139159
return in.err("expected end of module");
140160
}
141161

142-
return QuotedModule{type, ss.str()};
162+
return QuotedModule{isDefinition, type, ss.str()};
143163
}
144164

145165
Result<NaNKind> nan(Lexer& in) {
@@ -440,17 +460,61 @@ MaybeResult<Register> register_(Lexer& in) {
440460
return in.err("expected name");
441461
}
442462

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

446465
if (!in.takeRParen()) {
447-
// TODO: handle optional module id.
448466
return in.err("expected end of register command");
449467
}
450-
return Register{*name};
468+
469+
return Register{.name=*name, .instanceName=instanceName};
451470
}
452471

453-
// module | register | action | assertion
472+
// (module instance instance_name module_name)
473+
MaybeResult<ModuleInstantiation> instantiation(Lexer& in) {
474+
if (!in.takeSExprStart("module"sv)) {
475+
std::optional<std::string_view> actual = in.peekKeyword();
476+
return in.err((std::stringstream()<<"expected `module` keyword but got "<< actual.value_or("<not a keyword>")).str());
477+
}
478+
479+
if (!in.takeKeyword("instance"sv)) {
480+
// This is not a module instance and probably a module instead.
481+
return {};
482+
}
483+
484+
auto instanceId = in.takeID();
485+
if (!instanceId.has_value()) {
486+
return in.err("expected an instance id in module instantiation");
487+
}
488+
auto moduleId = in.takeID();
489+
if (!moduleId.has_value()) {
490+
return in.err("expected a module id in module instantiation");
491+
}
492+
493+
if (!in.takeRParen()) {
494+
return in.err("expected end of module instantiation");
495+
}
496+
497+
return ModuleInstantiation{.moduleName = *moduleId, .instanceName = *instanceId};
498+
}
499+
500+
using ModuleOrInstantiation = std::variant<ModuleInstantiation, WASTModule>;
501+
502+
// (module instance ...) | (module ...)
503+
Result<ModuleOrInstantiation> moduleOrInstantiation(Lexer& in) {
504+
auto reset = in;
505+
506+
if (auto inst = instantiation(in)) {
507+
CHECK_ERR(inst);
508+
return *inst;
509+
}
510+
in = reset;
511+
512+
auto module = wastModule(in);
513+
CHECK_ERR(module);
514+
return *module;
515+
}
516+
517+
// instantiate | module | register | action | assertion
454518
Result<WASTCommand> command(Lexer& in) {
455519
if (auto cmd = register_(in)) {
456520
CHECK_ERR(cmd);
@@ -464,9 +528,10 @@ Result<WASTCommand> command(Lexer& in) {
464528
CHECK_ERR(cmd);
465529
return *cmd;
466530
}
467-
auto mod = wastModule(in);
468-
CHECK_ERR(mod);
469-
return *mod;
531+
auto cmd = moduleOrInstantiation(in);
532+
CHECK_ERR(cmd);
533+
534+
return std::visit([](auto&& modOrInstantiation) -> WASTCommand { return modOrInstantiation; }, *cmd);
470535
}
471536

472537
#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: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ 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 initial module definition including its name) and stop at the ending right paren.
38+
Result<> parseModuleBody(Module& wasm, Lexer& lexer);
39+
3740
Result<Literal> parseConst(Lexer& lexer);
3841

3942
#pragma GCC diagnostic push
@@ -88,6 +91,7 @@ struct AssertAction {
8891
enum class QuotedModuleType { Text, Binary };
8992

9093
struct QuotedModule {
94+
bool isDefinition = false;
9195
QuotedModuleType type;
9296
std::string module;
9397
};
@@ -104,10 +108,17 @@ struct AssertModule {
104108
using Assertion = std::variant<AssertReturn, AssertAction, AssertModule>;
105109

106110
struct Register {
111+
// todo rename this
107112
Name name;
113+
std::optional<Name> instanceName = std::nullopt;
114+
};
115+
116+
struct ModuleInstantiation {
117+
Name moduleName;
118+
Name instanceName;
108119
};
109120

110-
using WASTCommand = std::variant<WASTModule, Register, Action, Assertion>;
121+
using WASTCommand = std::variant<WASTModule, Register, Action, Assertion, ModuleInstantiation>;
111122

112123
struct ScriptEntry {
113124
WASTCommand cmd;

0 commit comments

Comments
 (0)