Expect tests

This commit is contained in:
Fabian 2018-04-17 18:20:47 -05:00
parent f093e23f9e
commit e6af9f3d7f
22 changed files with 793 additions and 0 deletions

3
.travis-run-expect.sh Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
set -e
make expect-tests

View file

@ -17,3 +17,4 @@ env:
- TEST_SUITE=codegen
- TEST_SUITE=jshint
- TEST_SUITE=jit-paging
- TEST_SUITE=expect

View file

@ -296,6 +296,10 @@ kvm-unit-test: build/libv86-debug.js build/v86-debug.wasm
codegen-test: build/codegen-test.wasm
./tests/codegen/codegen.js
expect-tests: build/libv86-debug.js build/libwabt.js
make -C tests/expect/tests
./tests/expect/run.js
covreport:
mkdir -p $(COVERAGE_DIR)/build/
$(COVERAGE_DIR)/gen_report.js

View file

@ -311,6 +311,8 @@ CPU.prototype.wasm_patch = function(wm)
this.clear_tlb = this.wm.exports["_clear_tlb"];
this.full_clear_tlb = this.wm.exports["_full_clear_tlb"];
this.jit_force_generate_unsafe = this.wm.exports["_jit_force_generate_unsafe"];
};
CPU.prototype.jit_clear_func = function(index)
@ -1278,6 +1280,11 @@ CPU.prototype.codegen_finalize = function(wasm_table_index, start, end, first_op
}
seen_code[start] = (seen_code[start] || 0) + 1;
if(this.test_hook_did_generate_wasm)
{
this.test_hook_did_generate_wasm(code);
}
}
// Make a copy of jit_imports, since some imports change and

View file

@ -1375,6 +1375,13 @@ static void jit_generate(uint32_t phys_addr, uint32_t page_dirtiness)
*instruction_pointer = start;
}
// for testing
void jit_force_generate_unsafe(uint32_t phys_addr)
{
*instruction_pointer = phys_addr;
jit_generate(phys_addr, 0);
}
void cycle_internal()
{
#if ENABLE_JIT

21
tests/expect/readme.md Normal file
View file

@ -0,0 +1,21 @@
Expect tests
------------
These so-called "expect tests" test the code generation, i.e. the translation
of x86 assembly to Web Assembly. Use the following workflow:
1. Hack on the code generator
2. Run make `expect-tests`
3. For each failing test:
- Manually verify that the generated code changes are as expected by the diff
- If so, accept the new code by copying the .actual.wast file over the .wast file
and checking the new .wast file into git
In order to add a new expect test:
1. Create a new .asm file in tests/
2. Run make `expect-tests`
3. Verify the generated code and use the printed cp command to accept the test
For more information, see https://blog.janestreet.com/testing-with-expectations/

151
tests/expect/run.js Executable file
View file

@ -0,0 +1,151 @@
#!/usr/bin/env node
"use strict";
const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");
const libwabt = require("../../build/libwabt.js");
try {
var V86 = require("../../build/libv86-debug.js").V86;
}
catch(e) {
console.error(e);
console.error("Failed to import build/libv86-debug.js. Run " +
"`make build/libv86-debug.js` first.");
process.exit(1);
}
const LOG_LEVEL = 0;
const GIT_DIFF_FLAGS = [ "--no-index", "--patience", "--color=always"];
const TEST_DIR = path.join(__dirname, "tests");
const BUILD_DIR = path.join(TEST_DIR, "build");
function run_all()
{
const asm_files = fs.readdirSync(TEST_DIR).filter(filename => filename.endsWith(".asm"));
const files = asm_files.map(asm_file => {
const name = asm_file.slice(0, -4);
return {
name,
expect_file: path.relative(".", path.join(TEST_DIR, name + ".wast")),
actual_file: path.relative(".", path.join(BUILD_DIR, name + ".actual.wast")),
asm_file: path.join(TEST_DIR, name + ".asm"),
executable_file: path.join(BUILD_DIR, name + ".bin"),
};
});
files.forEach(run_test);
}
function run_test({ name, executable_file, expect_file, actual_file, asm_file })
{
const emulator = new V86({
autostart: false,
memory_size: 2 * 1024 * 1024,
log_level: LOG_LEVEL,
});
const executable = fs.readFileSync(executable_file);
const asm = fs.readFileSync(asm_file);
const is_32 = asm.includes("BITS 32");
emulator.add_listener("emulator-loaded", function()
{
const cpu = emulator.v86.cpu;
const hook_not_called_timeout = setTimeout(() => {
throw new Error("Hook for code generation not called");
}, 1000);
cpu.test_hook_did_generate_wasm = function(wasm)
{
const wast = disassemble_wasm(wasm);
clearTimeout(hook_not_called_timeout);
fs.writeFileSync(actual_file, wast);
cpu.test_hook_did_generate_wasm = function()
{
cpu.test_hook_did_generate_wasm = function() {};
throw new Error("Hook for wasm generation called multiple times");
};
if(!fs.existsSync(expect_file))
{
// enhanced workflow: If file doesn't exist yet print full diff
var expect_file_for_diff = "/dev/null";
}
else
{
expect_file_for_diff = expect_file;
}
const result = spawnSync("git",
[].concat(
"diff",
GIT_DIFF_FLAGS,
expect_file_for_diff,
actual_file
),
{ encoding: "utf8" });
if(result.status)
{
console.log(result.stdout);
console.log(result.stderr);
console.log(
"%s.asm failed:\n" +
"The code generator produced different code. If you believe this change is intentional,\n" +
"verify the diff above and run the following command to accept the change:\n\n" +
" cp %s %s", name, actual_file, expect_file);
process.exit(1);
}
else
{
console.log("%s ok", name);
console.assert(!result.stdout);
console.assert(!result.stderr);
}
};
if(is_32)
{
cpu.is_32[0] = true;
cpu.stack_size_32[0] = true;
}
const START_ADDRESS = 0x1000;
cpu.mem8.set(executable, START_ADDRESS);
cpu.jit_force_generate_unsafe(START_ADDRESS);
});
}
function disassemble_wasm(wasm)
{
// Need to make a small copy otherwise libwabt goes nuts trying to copy
// the whole underlying buffer
wasm = wasm.slice();
try
{
var module = libwabt.readWasm(wasm, { readDebugNames: false });
module.generateNames();
module.applyNames();
return module.toText({ foldExprs: true, inlineExport: true });
}
finally
{
module && module.destroy();
}
}
run_all();

View file

@ -0,0 +1,9 @@
source_files := $(wildcard *.asm)
executable_files := $(patsubst %.asm,build/%.bin,$(source_files))
all: $(executable_files)
mkdir -p build
build/%.bin: %.asm
nasm $< -o $@

View file

@ -0,0 +1,8 @@
BITS 32
call test
hlt
test:
inc eax
ret

View file

@ -0,0 +1,51 @@
(module
(type $t0 (func))
(type $t1 (func (param i32)))
(type $t2 (func (param i32 i32)))
(type $t3 (func (param i32 i32 i32)))
(type $t4 (func (result i32)))
(type $t5 (func (param i32) (result i32)))
(type $t6 (func (param i32 i32) (result i32)))
(import "e" "get_seg" (func $e.get_seg (type $t5)))
(import "e" "instr32_E8" (func $e.instr32_E8 (type $t1)))
(import "e" "m" (memory $e.m 256))
(func $f (export "f") (type $t0)
(local $l0 i32) (local $l1 i32)
(set_local $l0
(i32.const 0))
(set_local $l1
(i32.const 10000))
(loop $L0
(set_local $l1
(i32.add
(get_local $l1)
(i32.const -1)))
(if $I1
(i32.eqz
(get_local $l1))
(then
(return)))
(block $B2
(block $B3
(br_table $B3 $B2
(get_local $l0)))
(i32.store
(i32.const 560)
(i32.load
(i32.const 556)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 5)))
(call $e.instr32_E8
(i32.const 1))
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 1)))
(return))
(unreachable))))

View file

@ -0,0 +1,8 @@
BITS 32
start:
inc ebx
cmp eax, 10
jnz start
hlt

View file

@ -0,0 +1,91 @@
(module
(type $t0 (func))
(type $t1 (func (param i32)))
(type $t2 (func (param i32 i32)))
(type $t3 (func (param i32 i32 i32)))
(type $t4 (func (result i32)))
(type $t5 (func (param i32) (result i32)))
(type $t6 (func (param i32 i32) (result i32)))
(import "e" "get_seg" (func $e.get_seg (type $t5)))
(import "e" "instr32_43" (func $e.instr32_43 (type $t0)))
(import "e" "instr32_83_7_reg" (func $e.instr32_83_7_reg (type $t2)))
(import "e" "test_nz" (func $e.test_nz (type $t4)))
(import "e" "instr_F4" (func $e.instr_F4 (type $t0)))
(import "e" "m" (memory $e.m 256))
(func $f (export "f") (type $t0)
(local $l0 i32) (local $l1 i32)
(set_local $l0
(i32.const 0))
(set_local $l1
(i32.const 10000))
(loop $L0
(set_local $l1
(i32.add
(get_local $l1)
(i32.const -1)))
(if $I1
(i32.eqz
(get_local $l1))
(then
(return)))
(block $B2
(block $B3
(block $B4
(br_table $B4 $B3 $B2
(get_local $l0)))
(i32.store
(i32.const 560)
(i32.add
(i32.load
(i32.const 556))
(i32.const 4)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 6)))
(call $e.instr32_43)
(call $e.instr32_83_7_reg
(i32.const 0)
(i32.const 10))
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 3)))
(if $I5
(call $e.test_nz)
(then
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const -6)))
(set_local $l0
(i32.const 0)))
(else
(set_local $l0
(i32.const 1))))
(br $L0))
(i32.store
(i32.const 560)
(i32.load
(i32.const 556)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 1)))
(call $e.instr_F4)
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 1)))
(return))
(unreachable))))

View file

@ -0,0 +1,8 @@
BITS 32
cmp eax, 5
jg else
inc ecx
else:
inc ebx
hlt

111
tests/expect/tests/if.wast Normal file
View file

@ -0,0 +1,111 @@
(module
(type $t0 (func))
(type $t1 (func (param i32)))
(type $t2 (func (param i32 i32)))
(type $t3 (func (param i32 i32 i32)))
(type $t4 (func (result i32)))
(type $t5 (func (param i32) (result i32)))
(type $t6 (func (param i32 i32) (result i32)))
(import "e" "get_seg" (func $e.get_seg (type $t5)))
(import "e" "instr32_83_7_reg" (func $e.instr32_83_7_reg (type $t2)))
(import "e" "test_nle" (func $e.test_nle (type $t4)))
(import "e" "instr32_41" (func $e.instr32_41 (type $t0)))
(import "e" "instr32_43" (func $e.instr32_43 (type $t0)))
(import "e" "instr_F4" (func $e.instr_F4 (type $t0)))
(import "e" "m" (memory $e.m 256))
(func $f (export "f") (type $t0)
(local $l0 i32) (local $l1 i32)
(set_local $l0
(i32.const 0))
(set_local $l1
(i32.const 10000))
(loop $L0
(set_local $l1
(i32.add
(get_local $l1)
(i32.const -1)))
(if $I1
(i32.eqz
(get_local $l1))
(then
(return)))
(block $B2
(block $B3
(block $B4
(block $B5
(br_table $B5 $B4 $B3 $B2
(get_local $l0)))
(i32.store
(i32.const 560)
(i32.add
(i32.load
(i32.const 556))
(i32.const 3)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 5)))
(call $e.instr32_83_7_reg
(i32.const 0)
(i32.const 5))
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 2)))
(if $I6
(call $e.test_nle)
(then
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 1)))
(set_local $l0
(i32.const 2)))
(else
(set_local $l0
(i32.const 1))))
(br $L0))
(call $e.instr32_41)
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 1)))
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 1)))
(set_local $l0
(i32.const 2))
(br $L0))
(i32.store
(i32.const 560)
(i32.add
(i32.load
(i32.const 556))
(i32.const 1)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 2)))
(call $e.instr32_43)
(call $e.instr_F4)
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 2)))
(return))
(unreachable))))

View file

@ -0,0 +1,4 @@
BITS 32
call [eax]
hlt

View file

@ -0,0 +1,55 @@
(module
(type $t0 (func))
(type $t1 (func (param i32)))
(type $t2 (func (param i32 i32)))
(type $t3 (func (param i32 i32 i32)))
(type $t4 (func (result i32)))
(type $t5 (func (param i32) (result i32)))
(type $t6 (func (param i32 i32) (result i32)))
(import "e" "get_seg" (func $e.get_seg (type $t5)))
(import "e" "instr32_FF_2_mem" (func $e.instr32_FF_2_mem (type $t1)))
(import "e" "m" (memory $e.m 256))
(func $f (export "f") (type $t0)
(local $l0 i32) (local $l1 i32)
(set_local $l0
(i32.const 0))
(set_local $l1
(i32.const 10000))
(loop $L0
(set_local $l1
(i32.add
(get_local $l1)
(i32.const -1)))
(if $I1
(i32.eqz
(get_local $l1))
(then
(return)))
(block $B2
(block $B3
(br_table $B3 $B2
(get_local $l0)))
(i32.store
(i32.const 560)
(i32.load
(i32.const 556)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 2)))
(call $e.instr32_FF_2_mem
(i32.add
(i32.load
(i32.const 4))
(call $e.get_seg
(i32.const 3))))
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 1)))
(return))
(unreachable))))

View file

@ -0,0 +1,4 @@
BITS 32
pop eax
hlt

View file

@ -0,0 +1,60 @@
(module
(type $t0 (func))
(type $t1 (func (param i32)))
(type $t2 (func (param i32 i32)))
(type $t3 (func (param i32 i32 i32)))
(type $t4 (func (result i32)))
(type $t5 (func (param i32) (result i32)))
(type $t6 (func (param i32 i32) (result i32)))
(import "e" "get_seg" (func $e.get_seg (type $t5)))
(import "e" "pop32s_ss32" (func $e.pop32s_ss32 (type $t4)))
(import "e" "instr_F4" (func $e.instr_F4 (type $t0)))
(import "e" "m" (memory $e.m 256))
(func $f (export "f") (type $t0)
(local $l0 i32) (local $l1 i32)
(set_local $l0
(i32.const 0))
(set_local $l1
(i32.const 10000))
(loop $L0
(set_local $l1
(i32.add
(get_local $l1)
(i32.const -1)))
(if $I1
(i32.eqz
(get_local $l1))
(then
(return)))
(block $B2
(block $B3
(br_table $B3 $B2
(get_local $l0)))
(i32.store
(i32.const 560)
(i32.load
(i32.const 556)))
(i32.store
(i32.const 4)
(call $e.pop32s_ss32))
(i32.store
(i32.const 560)
(i32.add
(i32.load
(i32.const 556))
(i32.const 1)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 2)))
(call $e.instr_F4)
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 2)))
(return))
(unreachable))))

View file

@ -0,0 +1,4 @@
BITS 32
push eax
hlt

View file

@ -0,0 +1,60 @@
(module
(type $t0 (func))
(type $t1 (func (param i32)))
(type $t2 (func (param i32 i32)))
(type $t3 (func (param i32 i32 i32)))
(type $t4 (func (result i32)))
(type $t5 (func (param i32) (result i32)))
(type $t6 (func (param i32 i32) (result i32)))
(import "e" "get_seg" (func $e.get_seg (type $t5)))
(import "e" "push32_ss32" (func $e.push32_ss32 (type $t1)))
(import "e" "instr_F4" (func $e.instr_F4 (type $t0)))
(import "e" "m" (memory $e.m 256))
(func $f (export "f") (type $t0)
(local $l0 i32) (local $l1 i32)
(set_local $l0
(i32.const 0))
(set_local $l1
(i32.const 10000))
(loop $L0
(set_local $l1
(i32.add
(get_local $l1)
(i32.const -1)))
(if $I1
(i32.eqz
(get_local $l1))
(then
(return)))
(block $B2
(block $B3
(br_table $B3 $B2
(get_local $l0)))
(i32.store
(i32.const 560)
(i32.load
(i32.const 556)))
(call $e.push32_ss32
(i32.load
(i32.const 4)))
(i32.store
(i32.const 560)
(i32.add
(i32.load
(i32.const 556))
(i32.const 1)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 2)))
(call $e.instr_F4)
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 2)))
(return))
(unreachable))))

View file

@ -0,0 +1,10 @@
BITS 32
start:
cmp eax, 10
jz end
inc ebx
jmp start
end:
hlt

View file

@ -0,0 +1,116 @@
(module
(type $t0 (func))
(type $t1 (func (param i32)))
(type $t2 (func (param i32 i32)))
(type $t3 (func (param i32 i32 i32)))
(type $t4 (func (result i32)))
(type $t5 (func (param i32) (result i32)))
(type $t6 (func (param i32 i32) (result i32)))
(import "e" "get_seg" (func $e.get_seg (type $t5)))
(import "e" "instr32_83_7_reg" (func $e.instr32_83_7_reg (type $t2)))
(import "e" "test_z" (func $e.test_z (type $t4)))
(import "e" "instr32_43" (func $e.instr32_43 (type $t0)))
(import "e" "instr_EB" (func $e.instr_EB (type $t1)))
(import "e" "instr_F4" (func $e.instr_F4 (type $t0)))
(import "e" "m" (memory $e.m 256))
(func $f (export "f") (type $t0)
(local $l0 i32) (local $l1 i32)
(set_local $l0
(i32.const 0))
(set_local $l1
(i32.const 10000))
(loop $L0
(set_local $l1
(i32.add
(get_local $l1)
(i32.const -1)))
(if $I1
(i32.eqz
(get_local $l1))
(then
(return)))
(block $B2
(block $B3
(block $B4
(block $B5
(br_table $B5 $B4 $B3 $B2
(get_local $l0)))
(i32.store
(i32.const 560)
(i32.add
(i32.load
(i32.const 556))
(i32.const 3)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 5)))
(call $e.instr32_83_7_reg
(i32.const 0)
(i32.const 10))
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 2)))
(if $I6
(call $e.test_z)
(then
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 3)))
(set_local $l0
(i32.const 2)))
(else
(set_local $l0
(i32.const 1))))
(br $L0))
(i32.store
(i32.const 560)
(i32.add
(i32.load
(i32.const 556))
(i32.const 1)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 3)))
(call $e.instr32_43)
(call $e.instr_EB
(i32.const -8))
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 2)))
(set_local $l0
(i32.const 0))
(br $L0))
(i32.store
(i32.const 560)
(i32.load
(i32.const 556)))
(i32.store
(i32.const 556)
(i32.add
(i32.load
(i32.const 556))
(i32.const 1)))
(call $e.instr_F4)
(i32.store
(i32.const 664)
(i32.add
(i32.load
(i32.const 664))
(i32.const 1)))
(return))
(unreachable))))