envoc-rs: the EnvoL compiler (EnvoLang's
compiler/) ported to Rust, function for function, so the same compiler can be timed
built by envoc and built by rustc. That tells us how good EnvoL's generated code is,
and where to make it better.
Rules for the port:
- Same algorithms and data structures. The hash map is envoc's (FNV-1a, linear
probing, the same growth); the tree is the same flat array of nodes; the same steps in
the same order. What changes is only what the language makes natural: Rust's
Vec, slices and borrowing instead of EnvoL'sList,strandref. - Same output, byte for byte. Every command prints exactly what
envocprints, errors included;scripts/parity.shchecks it on every.envolfile in EnvoLang. - No crates. The standard library only.
| Stage | Ported | Same output as envoc |
|---|---|---|
Lexer (lex) |
yes | 751 of 751 files |
Parser (parse, parse --core) |
yes | 751 of 751 files, both modes |
Checker (check) |
next | |
EIR, optimizer, x86-64 backend (eir, build) |
later |
The compiler's own sources joined ten times (237,550 lines, 9.7 MB); best of five runs;
both write the same output (46 MB for parse). CI times it on Linux on every push (the
job summary has the table).
On Linux (CI, envoc 0.5.0 against envoc-rs with fat LTO):
| envoc (EnvoL) | envoc-rs (Rust) | ||
|---|---|---|---|
lex |
2,606 ms | 228 ms | Rust 11× faster |
parse |
2,850 ms | 285 ms | Rust 10× faster |
What each change to envoc's code generation gave, on Windows 10 (envoc-rs: 656 ms and 617 ms, without LTO):
| envoc | lex |
parse |
|---|---|---|
| 0.5.0 | 4,429 ms | 5,028 ms |
locals promoted to SSA values (opt/mem2reg.envol) |
3,755 ms | 4,418 ms |
| compare and branch fused, fall-through, immediates, direct branch moves | 3,328 ms | 4,195 ms |
| results computed in their own registers | 3,133 ms | 3,894 ms |
| ten registers instead of five | 3,000 ms | 3,724 ms |
unsigned division by constants, match as jump tables |
2,752 ms | 3,505 ms |
arguments in registers, &&/` |
` threaded into one branch, small multi-block functions inlined | |
small copies unrolled (str and slice copies were rep movsb), field addresses folded into loads and stores, short encodings, loop-invariant loads hoisted |
1,518 ms | 1,835 ms |
So far: 66% and 64% faster, and about 2.3× behind (envoc-rs measured 671 ms and 757 ms
the same day). The profile (scripts/winprof.py)
says what is next: bounds and overflow checks inside loops, the temporary copies made
to pass a str or slice, and the allocator.
cargo build --release
./scripts/parity.sh <envoc> target/release/envoc-rs <EnvoLang checkout> parseOn Windows, build with the GNU toolchain (cargo +stable-x86_64-pc-windows-gnu build --release) if the MSVC linker is not installed. The release profile leaves fat LTO off,
because Windows Defender flags fat-LTO MinGW builds as malware (a false positive); the
lto profile turns it on for the Linux benchmarks.
| Script | |
|---|---|
scripts/parity.sh |
Both compilers on every file: stdout, stderr and exit codes must match |
scripts/bench-files.sh |
Times a command over every file in a folder |
scripts/winprof.py |
A sampling profiler for envoc-built Windows programs, using envoc's -map |