Speed up NASM tests' fixture generation phase

The gen_fixtures.js currently generates fixtures for all
host_executables (*.bin) files, regardless of whether they need to be
regenerated or not.

The script speeds the process up by:

- Listing all files that need fixtures
- Finding nr_of_cpus (to parallelize execution)
- Dividing them up to roughly even "chunks" based on nr_of_cpus
- Spawning N gdb processes, which iteratively call a user-defined command named
  "extract-state" (see gdb-extract-def); the command automatically loads the new
  file and extracts its state into a fixture file

Rough benchmarks for 3725 tests:

Old method (after `rm build/*.fixture`):
make -j4  325.23s user 40.28s system 206% cpu 2:57.38 total
make -j4  342.65s user 43.73s system 289% cpu 2:13.23 total
make -j4  435.69s user 55.61s system 289% cpu 2:49.63 total

New method:
node gen_fixtures.js  11.85s user 4.86s system 140% cpu 11.890 total
node gen_fixtures.js  12.48s user 5.48s system 114% cpu 15.736 total
node gen_fixtures.js  14.54s user 6.33s system 141% cpu 14.769 total
This commit is contained in:
Amaan Cheval 2018-01-05 17:53:01 +05:30 committed by Fabian
parent a3420c5f32
commit 8354a1dafb
4 changed files with 225 additions and 6 deletions

View file

@ -225,6 +225,7 @@ tests: build/libv86.js build/v86.wasm
nasmtests: build/libv86-debug.js build/v86-debug.wasm
$(MAKE) -C $(NASM_TEST_DIR) all
$(NASM_TEST_DIR)/gen_fixtures.js
$(NASM_TEST_DIR)/run.js
jitpagingtests: build/libv86.js build/v86.wasm

View file

@ -4,11 +4,10 @@ source_files += $(addprefix build/,$(wildcard *.asm))
obj_files := $(patsubst %.asm,%.o,$(source_files))
host_executables := $(patsubst %.asm,%.bin,$(source_files))
v86_executables := $(patsubst %.asm,%.img,$(source_files))
host_fixtures := $(patsubst %.asm,%.fixture,$(source_files))
inc_files := $(addprefix build/,$(wildcard *.inc))
all: $(source_files) $(obj_files) $(inc_files) $(host_executables) $(v86_executables) $(host_fixtures)
all: $(source_files) $(obj_files) $(inc_files) $(host_executables) $(v86_executables)
.PHONY: all
build/%.o: build/%.asm $(inc_files)
@ -18,10 +17,6 @@ build/%.o: build/%.asm $(inc_files)
build/%.bin: build/%.o
ld -g $< -m elf_i386 --section-start=.bss=0x100000 -o $@
# To generate a fixture using gdb
build/%.fixture: build/%.bin
gdb -quiet -batch -x gdbauto $< > $@
# To use as a multiboot kernel image for v86
build/%.img: build/%.o
ld -g $< -m elf_i386 --section-start=.bss=0x100000 --section-start=.text=0x8000 -o $@

111
tests/nasm/gdb-extract-def Normal file
View file

@ -0,0 +1,111 @@
# Invocation: gdb -x gdb-extract-def
# Set a breakpoint "in the future", which all the test binaries can then share
set breakpoint pending on
break loop
# extract-state /path/to/foo.bin /path/to/foo.fixture
define extract-state
file $arg0
set $STACK_TOP=0x120000
# Disables logging to stdout - only log to file
set logging redirect on
set logging file $arg1
set logging overwrite on
set logging on
run
printf "---BEGIN JSON---\n"
printf "[\n"
printf " %d,\n", $eax
printf " %d,\n", $ecx
printf " %d,\n", $edx
printf " %d,\n", $ebx
printf " %d,\n", $esp
printf " %d,\n", $ebp
printf " %d,\n", $esi
printf " %d,\n", $edi
printf " \n"
printf " %d,\n", $mm0.v2_int32[0]
printf " %d,\n", $mm0.v2_int32[1]
printf " %d,\n", $mm1.v2_int32[0]
printf " %d,\n", $mm1.v2_int32[1]
printf " %d,\n", $mm2.v2_int32[0]
printf " %d,\n", $mm2.v2_int32[1]
printf " %d,\n", $mm3.v2_int32[0]
printf " %d,\n", $mm3.v2_int32[1]
printf " %d,\n", $mm4.v2_int32[0]
printf " %d,\n", $mm4.v2_int32[1]
printf " %d,\n", $mm5.v2_int32[0]
printf " %d,\n", $mm5.v2_int32[1]
printf " %d,\n", $mm6.v2_int32[0]
printf " %d,\n", $mm6.v2_int32[1]
printf " %d,\n", $mm7.v2_int32[0]
printf " %d,\n", $mm7.v2_int32[1]
printf " \n"
printf " %d,\n", $xmm0.v4_int32[0]
printf " %d,\n", $xmm0.v4_int32[1]
printf " %d,\n", $xmm0.v4_int32[2]
printf " %d,\n", $xmm0.v4_int32[3]
printf " %d,\n", $xmm1.v4_int32[0]
printf " %d,\n", $xmm1.v4_int32[1]
printf " %d,\n", $xmm1.v4_int32[2]
printf " %d,\n", $xmm1.v4_int32[3]
printf " %d,\n", $xmm2.v4_int32[0]
printf " %d,\n", $xmm2.v4_int32[1]
printf " %d,\n", $xmm2.v4_int32[2]
printf " %d,\n", $xmm2.v4_int32[3]
printf " %d,\n", $xmm3.v4_int32[0]
printf " %d,\n", $xmm3.v4_int32[1]
printf " %d,\n", $xmm3.v4_int32[2]
printf " %d,\n", $xmm3.v4_int32[3]
printf " %d,\n", $xmm4.v4_int32[0]
printf " %d,\n", $xmm4.v4_int32[1]
printf " %d,\n", $xmm4.v4_int32[2]
printf " %d,\n", $xmm4.v4_int32[3]
printf " %d,\n", $xmm5.v4_int32[0]
printf " %d,\n", $xmm5.v4_int32[1]
printf " %d,\n", $xmm5.v4_int32[2]
printf " %d,\n", $xmm5.v4_int32[3]
printf " %d,\n", $xmm6.v4_int32[0]
printf " %d,\n", $xmm6.v4_int32[1]
printf " %d,\n", $xmm6.v4_int32[2]
printf " %d,\n", $xmm6.v4_int32[3]
printf " %d,\n", $xmm7.v4_int32[0]
printf " %d,\n", $xmm7.v4_int32[1]
printf " %d,\n", $xmm7.v4_int32[2]
printf " %d,\n", $xmm7.v4_int32[3]
printf " \n"
printf " %d,\n", *(int*)($STACK_TOP-64)
printf " %d,\n", *(int*)($STACK_TOP-60)
printf " %d,\n", *(int*)($STACK_TOP-56)
printf " %d,\n", *(int*)($STACK_TOP-52)
printf " %d,\n", *(int*)($STACK_TOP-48)
printf " %d,\n", *(int*)($STACK_TOP-44)
printf " %d,\n", *(int*)($STACK_TOP-40)
printf " %d,\n", *(int*)($STACK_TOP-36)
printf " %d,\n", *(int*)($STACK_TOP-32)
printf " %d,\n", *(int*)($STACK_TOP-28)
printf " %d,\n", *(int*)($STACK_TOP-24)
printf " %d,\n", *(int*)($STACK_TOP-20)
printf " %d,\n", *(int*)($STACK_TOP-16)
printf " %d,\n", *(int*)($STACK_TOP-12)
printf " %d,\n", *(int*)($STACK_TOP-8)
printf " %d,\n", *(int*)($STACK_TOP-4)
printf " \n"
printf " %d\n", $eflags
printf "]\n"
printf "---END JSON---\n"
set logging off
end

112
tests/nasm/gen_fixtures.js Executable file
View file

@ -0,0 +1,112 @@
#!/usr/bin/env node
"use strict";
const fs = require("fs");
const os = require("os");
const { spawn } = require("child_process");
const DEBUG = process.env.DEBUG || false;
// Limit the number of binaries for which fixtures are generated in DEBUG mode
const DEBUG_FILE_LIMIT = 0;
// Maximum number of gdb processes to spawn in parallel
const MAX_PARALLEL_PROCS = +process.env.MAX_PARALLEL_PROCS || 32;
// Usage: console.log(CYAN_FMT, "This shows up in cyan!")
const CYAN_FMT = "\x1b[36m%s\x1b[0m";
const YELLOW_FMT = "\x1b[33m%s\x1b[0m";
const TEST_DIR = __dirname + "/";
const BUILD_DIR = TEST_DIR + "build/";
const GDB_DEFAULT_ARGS = [
"-batch",
`--command=${TEST_DIR}gdb-extract-def`
];
/* Split up a string into semi-evenly sized chunks
* Eg. chunk("0 0 1 1 2 2 2 3 3 3".split(" "), 4) ->
* [ [ "0", "0" ],
* [ "1", "1" ],
* [ "2", "2", "2" ],
* [ "3", "3", "3" ] ]
*/
function chunk(source, num_chunks=4)
{
const arr = source.slice();
const ret = [];
let rem_chunks = num_chunks;
while(rem_chunks > 0)
{
// We guarantee that the entire array is processed because when rem_chunk=1 -> len/1 = len
ret.push(arr.splice(0, Math.floor(arr.length / rem_chunks)));
rem_chunks--;
}
return ret;
}
const dir_files = fs.readdirSync(BUILD_DIR);
let test_files = dir_files.filter((name) => {
return name.endsWith(".bin");
}).map(name => {
return name.slice(0, -4);
});
if(DEBUG && DEBUG_FILE_LIMIT)
{
console.log(CYAN_FMT, "[DEBUG]", `Limiting to ${DEBUG_FILE_LIMIT} fixtures`);
test_files = test_files.slice(0, DEBUG_FILE_LIMIT);
}
const nr_of_cpus = Math.min(
os.cpus().length || 1,
test_files.length,
MAX_PARALLEL_PROCS
);
console.log("[+] Using %d cpus to generate fixtures", nr_of_cpus);
const workloads = chunk(test_files, nr_of_cpus);
function test_arg_formatter(workload)
{
return workload.map(test => {
const test_path = BUILD_DIR + test;
return `--eval-command=extract-state ${test_path}.bin ${test_path}.fixture`;
});
}
function setProcHandlers(proc, n)
{
proc.on("close", (code) => {
console.log(`[+] child process ${n} exited with code ${code}`);
if(code !== 0)
{
process.exit(code);
}
});
if(DEBUG)
{
proc.stdout.on("data", (data) => {
console.log(CYAN_FMT, "stdout", `${n}: ${data}`);
});
proc.stderr.on("data", (data) => {
console.log(YELLOW_FMT, "stderr", `${n}: ${data}`);
});
}
}
const gdb_args = [];
for(let i = 0; i < nr_of_cpus; i++)
{
gdb_args[i] = GDB_DEFAULT_ARGS.concat(test_arg_formatter(workloads[i]));
if(DEBUG)
{
console.log(CYAN_FMT, "[DEBUG]", "gdb", gdb_args[i].join(" "));
}
const gdb = spawn("gdb", gdb_args[i]);
setProcHandlers(gdb, i);
}