Every lowering of one program, side by side
This commit is contained in:
parent
7f44cc3c8f
commit
f77216212e
29
bin/main.ml
29
bin/main.ml
@ -423,6 +423,33 @@ let () =
|
||||
"flan emit: --target is refused — the emitted IR carries no triple and \
|
||||
no datalayout, and the target is chosen at build.";
|
||||
exit 2
|
||||
(* --x86 prints the hand-written backend's assembly where the default prints
|
||||
LLVM IR. The two are the same act — here is what this program compiles to,
|
||||
before an assembler or an optimiser has touched it — and reading one
|
||||
against the other is the only way to check a lowering by eye. --sanitize
|
||||
is refused rather than ignored: ASan is an LLVM pass and this backend has
|
||||
no arm for it, so honouring the flag is impossible and dropping it
|
||||
silently would print something that is not what --sanitize builds. *)
|
||||
| _ :: "emit" :: args
|
||||
when List.mem x86_flag args && List.exists (fun a -> not (is_flag a)) args ->
|
||||
if List.mem sanitize_flag args then begin
|
||||
prerr_endline
|
||||
"flan emit --x86: --sanitize is an LLVM pass and this backend has no \
|
||||
arm for it — emit without --x86 to see what a sanitized build compiles.";
|
||||
exit 2
|
||||
end;
|
||||
let checks = not (List.mem no_checks_flag args) in
|
||||
let dev = List.mem dev_flag args in
|
||||
let debug = List.mem debug_flag args in
|
||||
let files = List.filter (fun a -> not (is_flag a)) args in
|
||||
List.iter
|
||||
(fun path ->
|
||||
with_errors path (fun () ->
|
||||
load path |> fun l ->
|
||||
Flan.Check.program_all l.decls
|
||||
|> Flan.X86.program ~checks ~dev ~debug
|
||||
|> print_string))
|
||||
files
|
||||
| _ :: "emit" :: args when List.exists (fun a -> not (is_flag a)) args ->
|
||||
let checks = not (List.mem no_checks_flag args) in
|
||||
let dev = List.mem dev_flag args in
|
||||
@ -582,7 +609,7 @@ let () =
|
||||
exit code)
|
||||
| _ ->
|
||||
prerr_endline
|
||||
"usage: flan (read|parse|check|emit|shim) <file.flan>...\n\
|
||||
"usage: flan (read|parse|check|emit|shim) <file.flan>...\n flan emit <file.flan> [--x86] [--dev] [--debug] [--no-bounds-checks]\n\
|
||||
\ flan import-c <header.h> [package.flan...] [clang flags...]\n\
|
||||
\ flan generate-c <package-dir>\n\
|
||||
\ flan build <file.flan> [-o out] [--no-bounds-checks] [--dev] \
|
||||
|
||||
66
spike/x86/dump.sh
Executable file
66
spike/x86/dump.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/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. 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.
|
||||
#
|
||||
# $ spike/x86/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 four dev lowerings rather than the
|
||||
# four 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"
|
||||
# The backend writes machine code, not mnemonics, so its .s is .byte blobs.
|
||||
# Assembling and disassembling is what makes it readable -- 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
|
||||
|
||||
for f in "$out/$base.fn.ll" "$out/$base.fn.O0.s" "$out/$base.fn.O2.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 four in $out"
|
||||
Loading…
x
Reference in New Issue
Block a user