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:
parent
a3420c5f32
commit
8354a1dafb
1
Makefile
1
Makefile
|
@ -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
|
||||
|
|
|
@ -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
111
tests/nasm/gdb-extract-def
Normal 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
112
tests/nasm/gen_fixtures.js
Executable 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);
|
||||
}
|
Loading…
Reference in a new issue