# Copyright (C) 2011 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This script is used on host and device. It uses a common subset
# shell dialect that should work on the host (e.g. bash), and
# Android (e.g. mksh).

# Globals
ARCHS={arm,arm64,mips,mips64,x86,x86_64}
ART_BINARY=dalvikvm
DELETE_ANDROID_DATA="no"
LAUNCH_WRAPPER=
LIBART=libart.so
JIT_PROFILE="no"
VERBOSE="no"
EXTRA_OPTIONS=()

# Follow all sym links to get the program name.
if [ z"$BASH_SOURCE" != z ]; then
  PROG_NAME="$BASH_SOURCE"
else
  PROG_NAME="$0"
fi
while [ -h "$PROG_NAME" ]; do
  # On Mac OS, readlink -f doesn't work.
  PROG_NAME="$(readlink "$PROG_NAME")"
done

function find_libdir() {
  # Get the actual file, $1 is the ART_BINARY_PATH and may be a symbolic link.
  # Use realpath instead of readlink because Android does not have a readlink.
  if [[ "$(realpath "$1")" == *dalvikvm64 ]]; then
    echo "lib64"
  else
    echo "lib"
  fi
}

function replace_compiler_filter_with_quicken() {
  ARGS_WITH_QUICKEN=("$@")

  found="false"
  ((index=0))
  while ((index <= $#)); do
    what="${ARGS_WITH_QUICKEN[$index]}"

    case "$what" in
      --compiler-filter=*)
        ARGS_WITH_QUICKEN[$index]="--compiler-filter=quicken"
        found="true"
        ;;
    esac

    ((index++))
    shift
  done
  if [ "$found" != "true" ]; then
    ARGS_WITH_QUICKEN=(-Xcompiler-option --compiler-filter=quicken "${ARGS_WITH_QUICKEN[@]}")
  fi
}

function usage() {
  cat 1>&2 <<EOF
Usage: art [OPTIONS] [--] [ART_OPTIONS] CLASS

Supported OPTIONS include:
  --32                     Use the 32-bit Android Runtime.
  --64                     Use the 64-bit Android Runtime.
  --callgrind              Launch the Android Runtime in callgrind.
  -d                       Use the debug ART library (libartd.so).
  --debug                  Equivalent to -d.
  --gdb                    Launch the Android Runtime in gdb.
  --help                   Display usage message.
  --invoke-with <program>  Launch the Android Runtime in <program>.
  --perf                   Launch the Android Runtime with perf recording.
  --perf-report            Launch the Android Runtime with perf recording with
                           report upon completion.
  --profile                Run with profiling, then run using profile data.
  --verbose                Run script verbosely.

The ART_OPTIONS are passed directly to the Android Runtime.

Example:
  art --32 -cp my_classes.dex MainClass

Common errors:
  1) Not having core.art available (see $ANDROID_BUILD_TOP/art/Android.mk).
     eg m -j32 build-art-host
  2) Not having boot.art available (see $ANDROID_BUILD_TOP/build/make/core/dex_preopt_libart_boot.mk)
     eg m -j32 out/target/product/generic_x86_64/dex_bootjars/system/framework/x86_64/boot.art
EOF
}

function clean_android_data() {
  if [ "$DELETE_ANDROID_DATA" = "yes" ]; then
    rm -rf $ANDROID_DATA
  fi
}

# Given 'VAR1=VAL VAR2=VAL2 ... cmd arg1 arg2 ... argN' run the 'cmd' with the args
# with the modified environment {VAR1=VAL,VAL2=,...}.
#
# Also prints the command to be run if verbose mode is enabled.
function verbose_run() {
  if [ "$VERBOSE" = "yes" ]; then
    echo "$@"
  fi

  env "$@"
}

# Automatically find the boot image location. It uses core.art by default.
# On a real device, it might only have a boot.art, so use that instead when core.art does not exist.
function detect_boot_image_location() {
  local image_location_dir="$ANDROID_ROOT/framework"
  local image_location_name="core.art"

  local maybe_arch
  local core_image_exists="false"

  # Parse ARCHS={a,b,c,d} syntax.
  local array
  IFS=, read -a array <<< "${ARCHS:1:(-1)}";
  for maybe_arch in "${array[@]}"; do
    if [[ -e "$image_location_dir/$maybe_arch/$image_location_name" ]]; then
      core_image_exists="true"
      break
    fi
  done

  if [[ "$core_image_exists" == "false" ]]; then
    image_location_name="boot.art"
  fi

  local image_location="$image_location_dir/$image_location_name"
  echo "$image_location"
}

function run_art() {
  local image_location="$(detect_boot_image_location)"

  verbose_run ANDROID_DATA=$ANDROID_DATA               \
              ANDROID_ROOT=$ANDROID_ROOT               \
              LD_LIBRARY_PATH=$LD_LIBRARY_PATH         \
              PATH=$ANDROID_ROOT/bin:$PATH             \
              LD_USE_LOAD_BIAS=1                       \
              $LAUNCH_WRAPPER $ART_BINARY_PATH $lib    \
              -XXlib:$LIBART                           \
              -Xnorelocate                             \
              -Ximage:"$image_location"                \
              "$@"
}

while [[ "$1" = "-"* ]]; do
  case "$1" in
  --)
    # No more arguments for this script.
    shift
    break
    ;;
  --32)
    ART_BINARY=dalvikvm32
    ;;
  --64)
    ART_BINARY=dalvikvm64
    ;;
  --callgrind)
    LAUNCH_WRAPPER="valgrind --tool=callgrind"
    ;;
  -d)
    ;& # Fallthrough
  --debug)
    LIBART="libartd.so"
    # Expect that debug mode wants all checks.
    EXTRA_OPTIONS+=(-XX:SlowDebug=true)
    ;;
  --gdb)
    LIBART="libartd.so"
    LAUNCH_WRAPPER="gdb --args"
    ;;
  --help)
    usage
    exit 0
    ;;
  --invoke-with)
    LAUNCH_WRAPPER=$2
    shift
    ;;
  --perf)
    PERF="record"
    ;;
  --perf-report)
    PERF="report"
    ;;
  --profile)
    JIT_PROFILE="yes"
    ;;
  --verbose)
    VERBOSE="yes"
    ;;
  --*)
    echo "unknown option: $1" 1>&2
    usage
    exit 1
    ;;
  *)
    break
    ;;
  esac
  shift
done

if [ $# -eq 0 ]; then
  usage
  exit 1
fi

PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
ANDROID_ROOT=$PROG_DIR/..
ART_BINARY_PATH=$ANDROID_ROOT/bin/$ART_BINARY

if [ ! -x "$ART_BINARY_PATH" ]; then
  cat 1>&2 <<EOF
Android Runtime not found: $ART_BINARY_PATH
This script should be in the same directory as the Android Runtime ($ART_BINARY).
EOF
  exit 1
fi

LIBDIR="$(find_libdir $ART_BINARY_PATH)"
LD_LIBRARY_PATH=$ANDROID_ROOT/$LIBDIR

# If ANDROID_DATA is the system ANDROID_DATA or is not set, use our own,
# and ensure we delete it at the end.
if [ "$ANDROID_DATA" = "/data" ] || [ "$ANDROID_DATA" = "" ]; then
  if [[ $PWD != / ]]; then
    ANDROID_DATA="$PWD/android-data$$"
  else
    # Use /data/local/tmp when running this from adb shell, since it starts out in /
    # by default.
    ANDROID_DATA="$ANDROID_DATA/local/tmp/android-data$$"
  fi
  mkdir -p $ANDROID_DATA/dalvik-cache/$ARCHS
  DELETE_ANDROID_DATA="yes"
fi

if [ "$PERF" != "" ]; then
  LAUNCH_WRAPPER="perf record -g -o $ANDROID_DATA/perf.data -e cycles:u $LAUNCH_WRAPPER"
  EXTRA_OPTIONS+=(-Xcompiler-option --generate-debug-info)
fi

if [ "$JIT_PROFILE" = "yes" ]; then
  # Create the profile. The runtime expects profiles to be created before
  # execution.
  PROFILE_PATH="$ANDROID_DATA/primary.prof"
  touch $PROFILE_PATH

  # Replace the compiler filter with quicken so that we
  # can capture the profile.
  ARGS_WITH_QUICKEN=
  replace_compiler_filter_with_quicken "$@"

  run_art -Xjitsaveprofilinginfo               \
          -Xps-min-methods-to-save:1           \
          -Xps-min-classes-to-save:1           \
          -Xps-min-notification-before-wake:10 \
          -Xps-profile-path:$PROFILE_PATH      \
          -Xusejit:true                        \
          "${ARGS_WITH_QUICKEN[@]}"            \
          "&>" "$ANDROID_DATA/profile_gen.log"
  EXIT_STATUS=$?

  if [ $EXIT_STATUS != 0 ]; then
    cat "$ANDROID_DATA/profile_gen.log"
    clean_android_data
    exit $EXIT_STATUS
  fi

  # Wipe dalvik-cache to prepare it for the next invocation.
  rm -rf $ANDROID_DATA/dalvik-cache/$ARCHS/*

  # Append arguments so next invocation of run_art uses the profile.
  EXTRA_OPTIONS+=(-Xcompiler-option --profile-file="$PROFILE_PATH")
fi

# Protect additional arguments in quotes to preserve whitespaces (used by
# run-jdwp-test.sh when running on device), '$' (may be used as part of
# classpath) and other special characters when evaluated.
EXTRA_OPTIONS+=("$@")

run_art "${EXTRA_OPTIONS[@]}"
EXIT_STATUS=$?

if [ "$PERF" != "" ]; then
  if [ "$PERF" = report ]; then
    perf report -i $ANDROID_DATA/perf.data
  fi
  echo "Perf data saved in: $ANDROID_DATA/perf.data"
else
  # Perf output is placed under $ANDROID_DATA so not cleaned when perf options used.
  clean_android_data
fi

exit $EXIT_STATUS
