diff --git a/bin/main.ml b/bin/main.ml index 6f7915c..ba7ab84 100644 --- a/bin/main.ml +++ b/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) ...\n\ + "usage: flan (read|parse|check|emit|shim) ...\n flan emit [--x86] [--dev] [--debug] [--no-bounds-checks]\n\ \ flan import-c [package.flan...] [clang flags...]\n\ \ flan generate-c \n\ \ flan build [-o out] [--no-bounds-checks] [--dev] \ diff --git a/spike/x86/dump.sh b/spike/x86/dump.sh new file mode 100755 index 0000000..998b649 --- /dev/null +++ b/spike/x86/dump.sh @@ -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 [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.`; the IR spells it @"flan." + # 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=":" '$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"