When trying to about the performance of my programs or the overhead of various design decisions (for example, choosing a immutable vs mutable representation of data), I often find that I don’t have a clear mental model of how compiler optimizations will affect the assembly will actually be generated.
While for small programs it would be fairly simple to manually run ocamlopt myself to verify my intuition, when I’m working on a larger project with several dependencies using dune, in order to compile the project, I would need the same flags that dune uses when building.
I’ve somewhat managed to achieve this with the hacky decompile script below (here with error-handling), but this is somewhat brittle.
Is there a more systematic and robust way of achieving this?
Note: I’ve tried passing the -S flag via link_flags in the dune file, but I think dune seems to delete the generated file automatically.
FILE=${1?File to be decompiled}
BASENAME=$(basename "${FILE%.*}")
DIRNAME=$(dirname "${FILE%.*}")
TARGETNAME="${DIRNAME}/${BASENAME}.s"
# run dune with verbose and copy last output
BUILD_COMMAND=$(dune clean && dune build --verbose 2>&1 | tail -n1)
# retrieve build directory from output
BUILD_DIR=$(echo $BUILD_COMMAND | grep ocamlopt | sed 's/.*(cd \(.*\) && .*/\1/')
ML_FILES=$(find . -type f -name '*.ml' | grep -v _build/default)
SORTED_ML_FILES=$(ocamldep -sort $ML_FILES)
# run ocamlopt with flags from dune, but set it up to output assembly file and only compile selected file
COMPILE_COMMAND=$(echo $BUILD_COMMAND | sed "s/.*:\(.*\))/\1 -S /")
eval "$COMPILE_COMMAND ${SORTED_ML_FILES})"
OUTPUT_FILE="$BUILD_DIR/$TARGETNAME"
cat "$OUTPUT_FILE"