mirror of
https://github.com/RGBCube/serenity
synced 2025-07-23 17:47:36 +00:00
Meta: Implement support for the "unlinkable" Wasm spectest assertion
This commit is contained in:
parent
30a1a25daa
commit
1465b11b58
1 changed files with 104 additions and 46 deletions
|
@ -97,6 +97,38 @@ def generate_binary_source(chunks):
|
||||||
named_modules = {}
|
named_modules = {}
|
||||||
named_modules_inverse = {}
|
named_modules_inverse = {}
|
||||||
registered_modules = {}
|
registered_modules = {}
|
||||||
|
module_output_path: str
|
||||||
|
|
||||||
|
|
||||||
|
def generate_module(ast):
|
||||||
|
# (module ...)
|
||||||
|
name = None
|
||||||
|
mode = 'ast' # binary, quote
|
||||||
|
start_index = 1
|
||||||
|
if len(ast) > 1:
|
||||||
|
if isinstance(ast[1], tuple) and isinstance(ast[1][0], str) and ast[1][0].startswith('$'):
|
||||||
|
name = ast[1][0]
|
||||||
|
if len(ast) > 2:
|
||||||
|
if isinstance(ast[2], tuple) and ast[2][0] in ('binary', 'quote'):
|
||||||
|
mode = ast[2][0]
|
||||||
|
start_index = 3
|
||||||
|
else:
|
||||||
|
start_index = 2
|
||||||
|
elif isinstance(ast[1][0], str):
|
||||||
|
mode = ast[1][0]
|
||||||
|
start_index = 2
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'ast': lambda: ('parse', generate_module_source_for_compilation(ast)),
|
||||||
|
'binary': lambda: ('literal', generate_binary_source(ast[start_index:])),
|
||||||
|
# FIXME: Make this work when we have a WAT parser
|
||||||
|
'quote': lambda: ('literal', ast[start_index]),
|
||||||
|
}[mode]()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'module': result,
|
||||||
|
'name': name
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def generate(ast):
|
def generate(ast):
|
||||||
|
@ -107,35 +139,29 @@ def generate(ast):
|
||||||
tests = []
|
tests = []
|
||||||
for entry in ast:
|
for entry in ast:
|
||||||
if len(entry) > 0 and entry[0] == ('module',):
|
if len(entry) > 0 and entry[0] == ('module',):
|
||||||
name = None
|
gen = generate_module(entry)
|
||||||
mode = 'ast' # binary, quote
|
module, name = gen['module'], gen['name']
|
||||||
start_index = 1
|
|
||||||
if len(entry) > 1:
|
|
||||||
if isinstance(entry[1], tuple) and isinstance(entry[1][0], str) and entry[1][0].startswith('$'):
|
|
||||||
name = entry[1][0]
|
|
||||||
if len(entry) > 2:
|
|
||||||
if isinstance(entry[2], tuple) and entry[2][0] in ('binary', 'quote'):
|
|
||||||
mode = entry[2][0]
|
|
||||||
start_index = 3
|
|
||||||
else:
|
|
||||||
start_index = 2
|
|
||||||
elif isinstance(entry[1][0], str):
|
|
||||||
mode = entry[1][0]
|
|
||||||
start_index = 2
|
|
||||||
|
|
||||||
tests.append({
|
tests.append({
|
||||||
"module": {
|
"module": module,
|
||||||
'ast': lambda: ('parse', generate_module_source_for_compilation(entry)),
|
|
||||||
'binary': lambda: ('literal', generate_binary_source(entry[start_index:])),
|
|
||||||
# FIXME: Make this work when we have a WAT parser
|
|
||||||
'quote': lambda: ('literal', entry[start_index]),
|
|
||||||
}[mode](),
|
|
||||||
"tests": []
|
"tests": []
|
||||||
})
|
})
|
||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
named_modules[name] = len(tests) - 1
|
named_modules[name] = len(tests) - 1
|
||||||
named_modules_inverse[len(tests) - 1] = (name, None)
|
named_modules_inverse[len(tests) - 1] = (name, None)
|
||||||
|
elif entry[0] == ('assert_unlinkable',):
|
||||||
|
# (assert_unlinkable module message)
|
||||||
|
if len(entry) < 2 or not isinstance(entry[1], list) or entry[1][0] != ('module',):
|
||||||
|
print(f"Invalid argument to assert_unlinkable: {entry[1]}", file=stderr)
|
||||||
|
continue
|
||||||
|
result = generate_module(entry[1])
|
||||||
|
tests.append({
|
||||||
|
'module': None,
|
||||||
|
'tests': [{
|
||||||
|
"kind": "unlinkable",
|
||||||
|
"module": result['module'],
|
||||||
|
}]
|
||||||
|
})
|
||||||
elif len(entry) in [2, 3] and entry[0][0].startswith('assert_'):
|
elif len(entry) in [2, 3] and entry[0][0].startswith('assert_'):
|
||||||
if entry[1][0] == ('invoke',):
|
if entry[1][0] == ('invoke',):
|
||||||
arg, name, module = 0, None, None
|
arg, name, module = 0, None, None
|
||||||
|
@ -293,7 +319,7 @@ def genarg(spec):
|
||||||
all_names_in_main = {}
|
all_names_in_main = {}
|
||||||
|
|
||||||
|
|
||||||
def genresult(ident, entry):
|
def genresult(ident, entry, index):
|
||||||
expectation = f'expect().fail("Unknown result structure " + {json.dumps(entry)})'
|
expectation = f'expect().fail("Unknown result structure " + {json.dumps(entry)})'
|
||||||
if "function" in entry:
|
if "function" in entry:
|
||||||
tmodule = 'module'
|
tmodule = 'module'
|
||||||
|
@ -320,7 +346,16 @@ def genresult(ident, entry):
|
||||||
return expectation
|
return expectation
|
||||||
|
|
||||||
if entry['kind'] == 'unlinkable':
|
if entry['kind'] == 'unlinkable':
|
||||||
return
|
name = f'mod-{ident}-{index}.wasm'
|
||||||
|
outpath = path.join(module_output_path, name)
|
||||||
|
if not compile_wasm_source(entry['module'], outpath):
|
||||||
|
return 'throw new Error("Module compilation failed");'
|
||||||
|
return (
|
||||||
|
f' expect(() => {{\n'
|
||||||
|
f' let content = readBinaryWasmFile("Fixtures/SpecTests/{name}");\n'
|
||||||
|
f' parseWebAssemblyModule(content, globalImportObject);\n'
|
||||||
|
f' }}).toThrow(TypeError, "Linking failed");'
|
||||||
|
)
|
||||||
|
|
||||||
if entry['kind'] == 'testgen_fail':
|
if entry['kind'] == 'testgen_fail':
|
||||||
return f'throw Exception("Test Generator Failure: " + {json.dumps(entry["reason"])});\n '
|
return f'throw Exception("Test Generator Failure: " + {json.dumps(entry["reason"])});\n '
|
||||||
|
@ -328,9 +363,20 @@ def genresult(ident, entry):
|
||||||
return f'throw Exception("(Test Generator) Unknown test kind {entry["kind"]}");\n '
|
return f'throw Exception("(Test Generator) Unknown test kind {entry["kind"]}");\n '
|
||||||
|
|
||||||
|
|
||||||
|
raw_test_number = 0
|
||||||
|
|
||||||
|
|
||||||
def gentest(entry, main_name):
|
def gentest(entry, main_name):
|
||||||
|
global raw_test_number
|
||||||
isfunction = 'function' in entry
|
isfunction = 'function' in entry
|
||||||
name = json.dumps((entry["function"] if isfunction else entry["get"])["name"])[1:-1]
|
name: str
|
||||||
|
isempty = False
|
||||||
|
if isfunction or 'get' in entry:
|
||||||
|
name = json.dumps((entry["function"] if isfunction else entry["get"])["name"])[1:-1]
|
||||||
|
else:
|
||||||
|
isempty = True
|
||||||
|
name = str(f"_inline_test_{raw_test_number}")
|
||||||
|
raw_test_number += 1
|
||||||
if type(name) != str:
|
if type(name) != str:
|
||||||
print("Unsupported test case (call to", name, ")", file=stderr)
|
print("Unsupported test case (call to", name, ")", file=stderr)
|
||||||
return '\n '
|
return '\n '
|
||||||
|
@ -339,15 +385,19 @@ def gentest(entry, main_name):
|
||||||
all_names_in_main[name] = count + 1
|
all_names_in_main[name] = count + 1
|
||||||
test_name = f'execution of {main_name}: {name} (instance {count})'
|
test_name = f'execution of {main_name}: {name} (instance {count})'
|
||||||
tmodule = 'module'
|
tmodule = 'module'
|
||||||
key = "function" if "function" in entry else "get"
|
if not isempty:
|
||||||
if entry[key]['module'] is not None:
|
key = "function" if "function" in entry else "get"
|
||||||
tmodule = f'namedModules[{json.dumps(named_modules_inverse[entry[key]["module"]][0])}]'
|
if entry[key]['module'] is not None:
|
||||||
|
tmodule = f'namedModules[{json.dumps(named_modules_inverse[entry[key]["module"]][0])}]'
|
||||||
source = (
|
source = (
|
||||||
f'test({json.dumps(test_name)}, () => {{\n'
|
f'test({json.dumps(test_name)}, () => {{\n' +
|
||||||
f'let {ident} = {tmodule}.getExport({json.dumps(name)});\n '
|
(
|
||||||
f'expect({ident}).not.toBeUndefined();\n '
|
f'let {ident} = {tmodule}.getExport({json.dumps(name)});\n '
|
||||||
f'{genresult(ident, entry)}'
|
f'expect({ident}).not.toBeUndefined();\n '
|
||||||
'});\n\n '
|
if not isempty else ''
|
||||||
|
) +
|
||||||
|
f'{genresult(ident, entry, count)}'
|
||||||
|
'});\n\n '
|
||||||
)
|
)
|
||||||
return source
|
return source
|
||||||
|
|
||||||
|
@ -373,7 +423,24 @@ def nth(a, x, y=None):
|
||||||
return a[x]
|
return a[x]
|
||||||
|
|
||||||
|
|
||||||
|
def compile_wasm_source(mod, outpath):
|
||||||
|
if not mod:
|
||||||
|
return True
|
||||||
|
if mod[0] == 'literal':
|
||||||
|
with open(outpath, 'wb+') as f:
|
||||||
|
f.write(mod[1])
|
||||||
|
return True
|
||||||
|
elif mod[0] == 'parse':
|
||||||
|
with NamedTemporaryFile("w+") as temp:
|
||||||
|
temp.write(mod[1])
|
||||||
|
temp.flush()
|
||||||
|
rc = call(["wat2wasm", temp.name, "-o", outpath])
|
||||||
|
return rc == 0
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
global module_output_path
|
||||||
with open(argv[1]) as f:
|
with open(argv[1]) as f:
|
||||||
sexp = f.read()
|
sexp = f.read()
|
||||||
name = argv[2]
|
name = argv[2]
|
||||||
|
@ -385,21 +452,12 @@ def main():
|
||||||
testname = f'{name}_{index}'
|
testname = f'{name}_{index}'
|
||||||
outpath = path.join(module_output_path, f'{testname}.wasm')
|
outpath = path.join(module_output_path, f'{testname}.wasm')
|
||||||
mod = description["module"]
|
mod = description["module"]
|
||||||
if mod[0] == 'literal':
|
if not compile_wasm_source(mod, outpath):
|
||||||
with open('outpath', 'wb+') as f:
|
print("Failed to compile", name, "module index", index, "skipping that test", file=stderr)
|
||||||
f.write(mod[1])
|
continue
|
||||||
elif mod[0] == 'parse':
|
|
||||||
with NamedTemporaryFile("w+") as temp:
|
|
||||||
temp.write(mod[1])
|
|
||||||
temp.flush()
|
|
||||||
rc = call(["wat2wasm", temp.name, "-o", outpath])
|
|
||||||
if rc != 0:
|
|
||||||
print("Failed to compile", name, "module index", index, "skipping that test", file=stderr)
|
|
||||||
continue
|
|
||||||
|
|
||||||
sep = ""
|
sep = ""
|
||||||
print(f'''describe({json.dumps(testname)}, () => {{
|
print(f'''describe({json.dumps(testname)}, () => {{
|
||||||
{gen_parse_module(testname, index)}
|
{gen_parse_module(testname, index) if mod else ''}
|
||||||
{sep.join(gentest(x, testname) for x in description["tests"])}
|
{sep.join(gentest(x, testname) for x in description["tests"])}
|
||||||
}});
|
}});
|
||||||
''')
|
''')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue