#!/bin/sh
#
# Code generator for trace events
#
# Copyright IBM, Corp. 2010
#
# This work is licensed under the terms of the GNU GPL, version 2.  See
# the COPYING file in the top-level directory.

# Disable pathname expansion, makes processing text with '*' characters simpler
set -f

usage()
{
    cat >&2 <<EOF
usage: $0 --nop [-h | -c]
Generate tracing code for a file on stdin.

Backends:
  --nop     Tracing disabled
  --dtrace  DTrace/SystemTAP backend

Output formats:
  -h     Generate .h file
  -c     Generate .c file
  -d     Generate .d file (DTrace only)
  --stap Generate .stp file (DTrace with SystemTAP only)

Options:
  --binary       [path]    Full path to QEMU binary
  --target-arch  [arch]    QEMU emulator target arch
  --target-type  [type]    QEMU emulator target type ('system' or 'user')
  --probe-prefix [prefix]  Prefix for dtrace probe names
                           (default: qemu-\$targettype-\$targetarch)

EOF
    exit 1
}

# Get the name of a trace event
get_name()
{
    echo ${1%%\(*}
}

# Get the argument list of a trace event, including types and names
get_args()
{
    local args
    args=${1#*\(}
    args=${args%%\)*}
    echo "$args"
}

# Get the argument name list of a trace event
get_argnames()
{
    local nfields field name sep
    nfields=0
    sep="$2"
    for field in $(get_args "$1"); do
        nfields=$((nfields + 1))

        # Drop pointer star
        field=${field#\*}

        # Only argument names have commas at the end
        name=${field%,}
        test "$field" = "$name" && continue

        printf "%s%s " $name $sep
    done

    # Last argument name
    if [ "$nfields" -gt 1 ]
    then
        printf "%s" "$name"
    fi
}

# Get the format string for a trace event
get_fmt()
{
    local fmt
    fmt=${1#*\"}
    fmt=${fmt%\"*}
    echo "$fmt"
}

# Get the state of a trace event
get_state()
{
    local str disable state
    str=$(get_name "$1")
    disable=${str##disable }
    if [ "$disable" = "$str" ] ; then
        state=1
    else
        state=0
    fi
    echo "$state"
}

linetoh_begin_nop()
{
    return
}

linetoh_nop()
{
    local name args
    name=$(get_name "$1")
    args=$(get_args "$1")

    # Define an empty function for the trace event
    cat <<EOF
static inline void trace_$name($args)
{
}
EOF
}

linetoh_end_nop()
{
    return
}

linetoc_begin_nop()
{
    return
}

linetoc_nop()
{
    # No need for function definitions in nop backend
    return
}

linetoc_end_nop()
{
    return
}

linetoh_begin_dtrace()
{
    cat <<EOF
#include "trace-dtrace.h"
EOF
}

linetoh_dtrace()
{
    local name args argnames state nameupper
    name=$(get_name "$1")
    args=$(get_args "$1")
    argnames=$(get_argnames "$1", ",")
    state=$(get_state "$1")
    if [ "$state" = "0" ] ; then
        name=${name##disable }
    fi

    nameupper=`echo $name | tr '[:lower:]' '[:upper:]'`

    # Define an empty function for the trace event
    cat <<EOF
static inline void trace_$name($args) {
    if (QEMU_${nameupper}_ENABLED()) {
        QEMU_${nameupper}($argnames);
    }
}
EOF
}

linetoh_end_dtrace()
{
    return
}

linetoc_begin_dtrace()
{
    return
}

linetoc_dtrace()
{
    # No need for function definitions in dtrace backend
    return
}

linetoc_end_dtrace()
{
    return
}

linetod_begin_dtrace()
{
    cat <<EOF
provider qemu {
EOF
}

linetod_dtrace()
{
    local name args state
    name=$(get_name "$1")
    args=$(get_args "$1")
    state=$(get_state "$1")
    if [ "$state" = "0" ] ; then
        name=${name##disable }
    fi

    # DTrace provider syntax expects foo() for empty
    # params, not foo(void)
    if [ "$args" = "void" ]; then
       args=""
    fi

    # Define prototype for probe arguments
    cat <<EOF
        probe $name($args);
EOF
}

linetod_end_dtrace()
{
    cat <<EOF
};
EOF
}

linetostap_begin_dtrace()
{
    return
}

linetostap_dtrace()
{
    local i arg name args arglist state
    name=$(get_name "$1")
    args=$(get_args "$1")
    arglist=$(get_argnames "$1", "")
    state=$(get_state "$1")
    if [ "$state" = "0" ] ; then
        name=${name##disable }
    fi

    # Define prototype for probe arguments
    cat <<EOF
probe $probeprefix.$name = process("$binary").mark("$name")
{
EOF

    i=1
    for arg in $arglist
    do
        # 'limit' is a reserved keyword
        if [ "$arg" = "limit" ]; then
          arg="_limit"
        fi
        cat <<EOF
  $arg = \$arg$i;
EOF
	i="$((i+1))"
    done

    cat <<EOF
}
EOF
}

linetostap_end_dtrace()
{
    return
}

# Process stdin by calling begin, line, and end functions for the backend
convert()
{
    local begin process_line end str disable
    begin="lineto$1_begin_$backend"
    process_line="lineto$1_$backend"
    end="lineto$1_end_$backend"

    "$begin"

    while read -r str; do
        # Skip comments and empty lines
        test -z "${str%%#*}" && continue

        # Process the line.  The nop backend handles disabled lines.
        disable=${str%%disable *}
        echo
        if test -z "$disable"; then
            # Pass the disabled state as an arg for the simple
            # or DTrace backends which handle it dynamically.
            # For all other backends, call lineto$1_nop()
            if [ $backend = "simple" -o "$backend" = "dtrace" ]; then
                "$process_line" "$str"
            else
                "lineto$1_nop" "${str##disable }"
            fi
        else
            "$process_line" "$str"
        fi
    done

    echo
    "$end"
}

tracetoh()
{
    cat <<EOF
#ifndef TRACE_H
#define TRACE_H

/* This file is autogenerated by tracetool, do not edit. */

#include "qemu-common.h"
EOF
    convert h
    echo "#endif /* TRACE_H */"
}

tracetoc()
{
    echo "/* This file is autogenerated by tracetool, do not edit. */"
    convert c
}

tracetod()
{
    if [ $backend != "dtrace" ]; then
       echo "DTrace probe generator not applicable to $backend backend"
       exit 1
    fi
    echo "/* This file is autogenerated by tracetool, do not edit. */"
    convert d
}

tracetostap()
{
    if [ $backend != "dtrace" ]; then
       echo "SystemTAP tapset generator not applicable to $backend backend"
       exit 1
    fi
    if [ -z "$binary" ]; then
       echo "--binary is required for SystemTAP tapset generator"
       exit 1
    fi
    if [ -z "$probeprefix" -a -z "$targettype" ]; then
       echo "--target-type is required for SystemTAP tapset generator"
       exit 1
    fi
    if [ -z "$probeprefix" -a -z "$targetarch" ]; then
       echo "--target-arch is required for SystemTAP tapset generator"
       exit 1
    fi
    if [ -z "$probeprefix" ]; then
	probeprefix="qemu.$targettype.$targetarch";
    fi
    echo "/* This file is autogenerated by tracetool, do not edit. */"
    convert stap
}


backend=
output=
binary=
targettype=
targetarch=
probeprefix=


until [ -z "$1" ]
do
  case "$1" in
    "--nop" | "--dtrace") backend="${1#--}" ;;

    "--binary") shift ; binary="$1" ;;
    "--target-arch") shift ; targetarch="$1" ;;
    "--target-type") shift ; targettype="$1" ;;
    "--probe-prefix") shift ; probeprefix="$1" ;;

    "-h" | "-c" | "-d") output="${1#-}" ;;
    "--stap") output="${1#--}" ;;

    "--check-backend") exit 0 ;; # used by ./configure to test for backend

    *)
      usage;;
  esac
  shift
done

if [ "$backend" = "" -o "$output" = "" ]; then
  usage
fi

gen="traceto$output"
"$gen"

exit 0
