93 lines
4.1 KiB
Bash
Executable File
93 lines
4.1 KiB
Bash
Executable File
#!/bin/sh
|
|
# Every lowering of one program, side by side: the LLVM IR the frontend emits,
|
|
# what LLVM makes of it at -O0 and at -O2, and what the hand-written backend
|
|
# emits -- the last of those twice, as the annotated listing it writes and as
|
|
# the disassembly of the object that listing assembles to. Reading one against
|
|
# another is the only way to check a lowering by eye, and the whole reason the
|
|
# second backend is trustworthy is that the two agree.
|
|
#
|
|
# $ tools/dump.sh file.flan [name]
|
|
#
|
|
# With a name, each output is narrowed to that function -- which is almost
|
|
# always what you want, because the prelude is emitted too and a two-line
|
|
# program is ten thousand lines of IR. The name is the Flan one: `twice`, not
|
|
# `flan.twice`.
|
|
#
|
|
# Output goes to a directory printed at the end and is not cleaned up; these are
|
|
# files you are going to read several times and diff against each other.
|
|
#
|
|
# FLAN overrides the compiler; the default is the one dune just built. Flags
|
|
# after the name are passed to every stage that understands them, so
|
|
# `dump.sh f.flan twice --dev` compares the dev lowerings rather than the
|
|
# release ones.
|
|
set -e
|
|
here=$(cd "$(dirname "$0")" && pwd)
|
|
root=$(cd "$here/.." && pwd)
|
|
FLAN=${FLAN:-$root/_build/default/bin/main.exe}
|
|
case $FLAN in /*) ;; *) FLAN=$(cd "$(dirname "$FLAN")" && pwd)/$(basename "$FLAN") ;; esac
|
|
|
|
[ -n "$1" ] || { echo "usage: $0 <file.flan> [name] [flags...]" >&2; exit 2; }
|
|
src=$1; shift
|
|
case $1 in ''|-*) fn= ;; *) fn=$1; shift ;; esac
|
|
|
|
out=$(mktemp -d "${TMPDIR:-/tmp}/flan-dump.XXXXXX")
|
|
base=$(basename "$src" .flan)
|
|
|
|
"$FLAN" emit "$src" "$@" > "$out/$base.ll"
|
|
llc -O0 "$out/$base.ll" -o "$out/$base.O0.s"
|
|
llc -O2 "$out/$base.ll" -o "$out/$base.O2.s"
|
|
# Two files out of the backend, and they answer different questions.
|
|
#
|
|
# The .s is what it emits, and it is annotated: a frame map above each
|
|
# function saying which rbp displacement is which parameter and which is which
|
|
# named local, the Flan form above each run of bytes that produced it, and a
|
|
# name for each piece of bookkeeping the compiler added. That is the *why*, and
|
|
# it is the file to read when the question is where eleven instructions came
|
|
# from.
|
|
#
|
|
# The disassembly is the *what*. The backend writes machine code rather than
|
|
# mnemonics -- .byte blobs, so that every offset stays exactly known -- so
|
|
# assembling and disassembling is the only way to see the instructions, and it
|
|
# is also a check that the bytes are well formed, which reading them never
|
|
# would be.
|
|
"$FLAN" emit --x86 "$src" "$@" > "$out/$base.x86.s"
|
|
as --64 -o "$out/$base.x86.o" "$out/$base.x86.s"
|
|
objdump -d --no-show-raw-insn "$out/$base.x86.o" > "$out/$base.x86.dis"
|
|
|
|
if [ -n "$fn" ]; then
|
|
# LLVM names a Flan function `flan.<name>`; the IR spells it @"flan.<name>"
|
|
# when it needs quoting, and the assembler output as a plain label.
|
|
awk -v f="flan.$fn" '
|
|
$0 ~ "^define .*@\"?" f "\"?\\(" {p=1} p; p && /^}/ {p=0}' \
|
|
"$out/$base.ll" > "$out/$base.fn.ll" || true
|
|
for o in O0 O2; do
|
|
awk -v f="flan.$fn" '
|
|
$0 ~ "^\"?" f "\"?:" {p=1} p; p && /\.size/ {p=0}' \
|
|
"$out/$base.$o.s" > "$out/$base.fn.$o.s" || true
|
|
done
|
|
awk -v f="<flan.$fn>:" '$0 ~ f {p=1} p; p && /^$/ {p=0}' \
|
|
"$out/$base.x86.dis" > "$out/$base.fn.x86" || true
|
|
# The annotated listing for the same function, which is the half of the four
|
|
# that says *why*. It has to carry the frame map with it -- the map sits above
|
|
# the .globl, and without it every displacement in the body is anonymous -- so
|
|
# this keeps the most recent run of comment lines in hand and prints it when
|
|
# the function's label arrives.
|
|
awk -v f="flan.$fn" '
|
|
/^#/ { if (!inc) buf = ""; inc = 1; buf = buf $0 "\n"; next }
|
|
{ inc = 0 }
|
|
$0 ~ "^\"?" f "\"?:" { printf "%s", buf; p = 1 }
|
|
p
|
|
p && /\.size/ { p = 0 }' \
|
|
"$out/$base.x86.s" > "$out/$base.fn.x86.s" || true
|
|
|
|
for f in "$out/$base.fn.ll" "$out/$base.fn.O0.s" "$out/$base.fn.O2.s" \
|
|
"$out/$base.fn.x86.s" "$out/$base.fn.x86"; do
|
|
[ -s "$f" ] || { echo "nothing for '$fn' in $(basename "$f")" >&2; continue; }
|
|
echo "=== $(basename "$f") ==="
|
|
cat "$f"
|
|
echo
|
|
done
|
|
fi
|
|
|
|
echo "all of them in $out"
|