#!/bin/sh

# -----------------------------------------------------------------------
#  gentramp.sh - Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc.
#
#  Trampoline Page Generator
#  Author: Landon Fuller <landonf@plausible.coop>
#
#  Permission is hereby granted, free of charge, to any person obtaining
#  a copy of this software and associated documentation files (the
#  ``Software''), to deal in the Software without restriction, including
#  without limitation the rights to use, copy, modify, merge, publish,
#  distribute, sublicense, and/or sell copies of the Software, and to
#  permit persons to whom the Software is furnished to do so, subject to
#  the following conditions:
#
#  The above copyright notice and this permission notice shall be included
#  in all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
#  NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
#  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
#  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#  DEALINGS IN THE SOFTWARE.
#  -----------------------------------------------------------------------

PROGNAME="$0"

INPUT_FILE_PATH="$1"
CURRENT_ARCH="$2"
PLATFORM_NAME="$3"
OUTPUT_FILE_PREFIX="$4"
OUTPUT_DIR="$5"

if [ -z "${INPUT_FILE_PATH}" ] || [ -z "${CURRENT_ARCH}" ] || [ -z "${PLATFORM_NAME}" ] || [ -z "${OUTPUT_FILE_PREFIX}" ] || [ -z "${OUTPUT_DIR}" ]; then
    echo "USAGE: $PROGNAME <input file> <arch> <sdk> <output file prefix> <output directory>"
    echo "  For example, '$PROGNAME blockimp_arm.tramp armv7 iphoneos bli_arm build' would read 'blockimp_arm.tramp'"
    echo "  and generate the following files in 'build':"
    echo "  - bli_arm_config.c"
    echo "  - bli_arm.s"
    echo "  - bli_arm.h"

    exit 1
fi

SRC_C_OUTPUT="${OUTPUT_DIR}/${OUTPUT_FILE_PREFIX}_config.c"
SRC_OUTPUT="${OUTPUT_DIR}/${OUTPUT_FILE_PREFIX}.s"
HEADER_OUTPUT="${OUTPUT_DIR}/${OUTPUT_FILE_PREFIX}.h"

# Default implementation
trampoline_prefix () {
    return 0
}

# Import the trampoline definition
. "${INPUT_FILE_PATH}"

check_required () {
    local name=$1
    eval "local var=\${$1}"
    
    if [ -z "${var}" ]; then
        echo "Required variable ${name} not defined."
        exit 1
    fi
}

check_required PAGE_SIZE
check_required PAGE_NAME

# Write a header line
header () {
    echo "$1" >> "${HEADER_OUTPUT}"
}

# Write a C source line
src () {
    echo "$1" >> "${SRC_C_OUTPUT}"
}

# Flush the assembler output buffer to disk
ASM_BUFFER=""
asm_flush () {
    echo "${ASM_BUFFER}" >> "${SRC_OUTPUT}"
    asm_discard
}

# Write the assembler buffer to disk, but don't discard the contents
asm_write () {
    echo "${ASM_BUFFER}" >> "${SRC_OUTPUT}"
}

# Discard the current assembler output buffer
asm_discard () {
    ASM_BUFFER=''
    return 0;
}

# Append data to the assembler output buffer
asm () {
    local line=""
    while read -r line; do
        ASM_BUFFER+=$line
        ASM_BUFFER+="\n"
    done
}

# Compute the assembled size of the current assembler buffer
compute_asm_size () {
    # Create the temporary assembler file
    local output=".globl _byte_count_start\n"
    output+="_byte_count_start:\n"
    output+="${ASM_BUFFER}"
    output+=".globl _byte_count_end\n"
    output+="_byte_count_end:\n"

    # Apple's ARM64 as(1) handling is a thin shim to clang's arm64 integrated
    # assembler, which does not support documented as(1) behavior. We work
    # around clang's file extension assumptions via an explicit -x <language> option;
    # see rdar://15162294 for more details.
    if [ "${CURRENT_ARCH}" == "arm64" ]; then
        local asm_args_extra="-x assembler"
    else
        local asm_args_extra=""
    fi

    local tempfile=`mktemp /tmp/as_bytecount.XXXXXXXX`
    echo "${output}" | xcrun --sdk "${PLATFORM_NAME}" as -arch "${CURRENT_ARCH}" $asm_args_extra -o "${tempfile}" -
    if [ $? != 0 ]; then
        echo "Assembling the trampoline failed"
        exit 1
    fi

    local byte_size=`xcrun --sdk "${PLATFORM_NAME}" nm -t d -P "${tempfile}" | grep ^_byte_count_end | awk '{print $3}'`
    rm -f "${tempfile}"

    echo $byte_size
}


# Write out the page header
write_page_decl () {
    # Calculate the required alignment
    local align=`perl -l -e "print log(${PAGE_SIZE})/log(2)"`
    asm << EOF
        # GENERATED CODE - DO NOT EDIT"
        # This file was generated by $PROGNAME on `date`

        # Write out the trampoline table, aligned to the page boundary
        .text
        .align ${align}
        .globl _${PAGE_NAME}
        _${PAGE_NAME}:
EOF
}

main () {
    echo '' > "${SRC_OUTPUT}"
    echo '' > "${SRC_C_OUTPUT}"
    echo '' > "${HEADER_OUTPUT}"
    
    # Write out the trampoline header file
    header "extern void *${PAGE_NAME};"
    header "extern struct pl_trampoline_table_config ${PAGE_NAME}_config;" 

    # Don't generate tables for an unsupported arch
    check_architecture "${CURRENT_ARCH}"
    if [ "$?" != "1" ]; then
        return
    fi

    # Determine the trampoline prefix size
    trampoline_prefix
    local prefix_size=$(compute_asm_size)
    asm_discard

    # Compute the size of the remaining code page.
    local page_avail=`expr $PAGE_SIZE - $prefix_size`

    # Determine the trampoline size
    trampoline
    local tramp_size=$(compute_asm_size)
    asm_discard
    if [ "${tramp_size}" = 0 ]; then
        echo "Error occured calculating trampoline size; received size of 0"
        exit 1
    fi

    # Compute the number of of available trampolines. 
    local trampoline_count=`expr $page_avail / $tramp_size`
    echo "Prefix size: ${prefix_size}"
    echo "Trampoline size: ${tramp_size}"
    echo "Trampolines per page: ${trampoline_count}"

    # Write out the page declaration
    write_page_decl
    asm_flush

    # Write out the prefix
    trampoline_prefix
    asm_flush

    # Write out the trampolines
    trampoline
    local i=0
    while [ $i -lt ${trampoline_count} ]; do
        asm_write
        local i=`expr $i + 1`
    done
    asm_discard
    
    # Write out the table configuration
    local config_src=`cat << EOF    
        #define PL_BLOCKIMP_PRIVATE 1
        #include <PLBlockIMP/trampoline_table.h>

        extern void *${PAGE_NAME};
        pl_trampoline_table_config ${PAGE_NAME}_config = {
            .trampoline_size = ${tramp_size},
            .page_offset = ${prefix_size},
            .trampoline_count = ${trampoline_count},
            .template_page = &${PAGE_NAME}
        };
EOF`
    src "${config_src}"
}

main