#!/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

Output formats:
  -h    Generate .h file
  -c    Generate .c file
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
    nfields=0
    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" "$name, "
    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 "}"
}

# 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 to lineto$1_simple().
            # For all other cases, call lineto$1_nop()
            if [ $backend = "simple" ]; 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
}

# Choose backend
case "$1" in
"--nop" | "--simple" | "--ust") backend="${1#--}" ;;
*) usage ;;
esac
shift

case "$1" in
"-h") tracetoh ;;
"-c") tracetoc ;;
"--check-backend") exit 0 ;; # used by ./configure to test for backend
*) usage ;;
esac

exit 0