#!/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 | --simple | --ust] [-h | -c]
Generate tracing code for a file on stdin.

Backends:
  --nop     Tracing disabled
  --simple  Simple built-in backend
  --ust     LTTng User Space Tracing backend
  --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')

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 number of arguments to a trace event
get_argc()
{
    local name argc
    argc=0
    for name in $(get_argnames "$1", ","); do
        argc=$((argc + 1))
    done
    echo $argc
}

# 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_simple()
{
    cat <<EOF
#include "simpletrace.h"
EOF

    simple_event_num=0
}

cast_args_to_uint64_t()
{
    local arg
    for arg in $(get_argnames "$1", ","); do
        printf "%s" "(uint64_t)(uintptr_t)$arg"
    done
}

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

    trace_args="$simple_event_num"
    if [ "$argc" -gt 0 ]
    then
        trace_args="$trace_args, $(cast_args_to_uint64_t "$1")"
    fi

    cat <<EOF
static inline void trace_$name($args)
{
    trace$argc($trace_args);
}
EOF

    simple_event_num=$((simple_event_num + 1))
}

linetoh_end_simple()
{
    cat <<EOF
#define NR_TRACE_EVENTS $simple_event_num
extern TraceEvent trace_list[NR_TRACE_EVENTS];
EOF
}

linetoc_begin_simple()
{
    cat <<EOF
#include "trace.h"

TraceEvent trace_list[] = {
EOF
    simple_event_num=0

}

linetoc_simple()
{
    local name state
    name=$(get_name "$1")
    state=$(get_state "$1")
    if [ "$state" = "0" ] ; then
        name=${name##disable }
    fi
    cat <<EOF
{.tp_name = "$name", .state=$state},
EOF
    simple_event_num=$((simple_event_num + 1))
}

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

# Clean up after UST headers which pollute the namespace
ust_clean_namespace() {
    cat <<EOF
#undef mutex_lock
#undef mutex_unlock
#undef inline
#undef wmb
EOF
}

linetoh_begin_ust()
{
    echo "#include <ust/tracepoint.h>"
    ust_clean_namespace
}

linetoh_ust()
{
    local name args argnames
    name=$(get_name "$1")
    args=$(get_args "$1")
    argnames=$(get_argnames "$1", ",")

    cat <<EOF
DECLARE_TRACE(ust_$name, TP_PROTO($args), TP_ARGS($argnames));
#define trace_$name trace_ust_$name
EOF
}

linetoh_end_ust()
{
    return
}

linetoc_begin_ust()
{
    cat <<EOF
#include <ust/marker.h>
$(ust_clean_namespace)
#include "trace.h"
EOF
}

linetoc_ust()
{
    local name args argnames fmt
    name=$(get_name "$1")
    args=$(get_args "$1")
    argnames=$(get_argnames "$1", ",")
    fmt=$(get_fmt "$1")

    cat <<EOF
DEFINE_TRACE(ust_$name);

static void ust_${name}_probe($args)
{
    trace_mark(ust, $name, "$fmt", $argnames);
}
EOF

    # Collect names for later
    names="$names $name"
}

linetoc_end_ust()
{
    cat <<EOF
static void __attribute__((constructor)) trace_init(void)
{
EOF

    for name in $names; do
        cat <<EOF
    register_trace_ust_$name(ust_${name}_probe);
EOF
    done

    echo "}"
}

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 qemu.$targettype.$targetarch.$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 "$targettype" ]; then
       echo "--target-type is required for SystemTAP tapset generator"
       exit 1
    fi
    if [ -z "$targetarch" ]; then
       echo "--target-arch is required for SystemTAP tapset generator"
       exit 1
    fi
    echo "/* This file is autogenerated by tracetool, do not edit. */"
    convert stap
}


backend=
output=
binary=
targettype=
targetarch=


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

    "--binary") shift ; binary="$1" ;;
    "--target-arch") shift ; targetarch="$1" ;;
    "--target-type") shift ; targettype="$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