/*
 * Copyright (c) 2013-2014 ARM Limited
 * All rights reserved.
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Andrew Bardsley
 */

#include "cpu/timing_expr.hh"

#include "base/intmath.hh"

TimingExprEvalContext::TimingExprEvalContext(const StaticInstPtr &inst_,
    ThreadContext *thread_,
    TimingExprLet *let_) :
    inst(inst_), thread(thread_), let(let_)
{
    /* Reserve space to hold the results of evaluating the
     *  let expressions */
    if (let) {
        unsigned int num_defns = let->defns.size();

        results.resize(num_defns, 0);
        resultAvailable.resize(num_defns, false);
    }
}

uint64_t TimingExprSrcReg::eval(TimingExprEvalContext &context)
{
    return context.inst->srcRegIdx(index);
}

uint64_t TimingExprReadIntReg::eval(TimingExprEvalContext &context)
{
    return context.thread->readIntReg(reg->eval(context));
}

uint64_t TimingExprLet::eval(TimingExprEvalContext &context)
{
    TimingExprEvalContext new_context(context.inst,
        context.thread, this);

    return expr->eval(new_context);
}

uint64_t TimingExprRef::eval(TimingExprEvalContext &context)
{
    /* Lookup the result, evaluating if necessary.  @todo, this
     *  should have more error checking */
    if (!context.resultAvailable[index]) {
        context.results[index] = context.let->defns[index]->eval(context);
        context.resultAvailable[index] = true;
    }

    return context.results[index];
}

uint64_t TimingExprUn::eval(TimingExprEvalContext &context)
{
    uint64_t arg_value = arg->eval(context);
    uint64_t ret = 0;

    switch (op) {
      case Enums::timingExprSizeInBits:
        if (arg_value == 0)
            ret = 0;
        else
            ret = ceilLog2(arg_value);
        break;
      case Enums::timingExprNot:
        ret = arg_value != 0;
        break;
      case Enums::timingExprInvert:
        ret = ~arg_value;
        break;
      case Enums::timingExprSignExtend32To64:
        ret = static_cast<int64_t>(
            static_cast<int32_t>(arg_value));
        break;
      case Enums::timingExprAbs:
        if (static_cast<int64_t>(arg_value) < 0)
            ret = -arg_value;
        else
            ret = arg_value;
        break;
      default:
        break;
    }

    return ret;
}

uint64_t TimingExprBin::eval(TimingExprEvalContext &context)
{
    uint64_t left_value = left->eval(context);
    uint64_t right_value = right->eval(context);
    uint64_t ret = 0;

    switch (op) {
      case Enums::timingExprAdd:
          ret = left_value + right_value;
          break;
      case Enums::timingExprSub:
          ret = left_value - right_value;
          break;
      case Enums::timingExprUMul:
          ret = left_value * right_value;
          break;
      case Enums::timingExprUDiv:
          if (right_value != 0) {
              ret = left_value / right_value;
          }
          break;
      case Enums::timingExprUCeilDiv:
          if (right_value != 0) {
              ret = (left_value + (right_value - 1)) / right_value;
          }
          break;
      case Enums::timingExprSMul:
          ret = static_cast<int64_t>(left_value) *
              static_cast<int64_t>(right_value);
          break;
      case Enums::timingExprSDiv:
          if (right_value != 0) {
              ret = static_cast<int64_t>(left_value) /
                  static_cast<int64_t>(right_value);
          }
          break;
      case Enums::timingExprEqual:
          ret = left_value == right_value;
          break;
      case Enums::timingExprNotEqual:
          ret = left_value != right_value;
          break;
      case Enums::timingExprULessThan:
          ret = left_value < right_value;
          break;
      case Enums::timingExprUGreaterThan:
          ret = left_value > right_value;
          break;
      case Enums::timingExprSLessThan:
          ret = static_cast<int64_t>(left_value) <
              static_cast<int64_t>(right_value);
          break;
      case Enums::timingExprSGreaterThan:
          ret = static_cast<int64_t>(left_value) >
              static_cast<int64_t>(right_value);
          break;
      case Enums::timingExprAnd:
          ret = (left_value != 0) && (right_value != 0);
          break;
      case Enums::timingExprOr:
          ret = (left_value != 0) || (right_value != 0);
          break;
      default:
          break;
    }

    return ret;
}

uint64_t TimingExprIf::eval(TimingExprEvalContext &context)
{
    uint64_t cond_value = cond->eval(context);

    if (cond_value != 0)
        return trueExpr->eval(context);
    else
        return falseExpr->eval(context);
}

TimingExprLiteral *
TimingExprLiteralParams::create()
{
    return new TimingExprLiteral(this);
}

TimingExprSrcReg *
TimingExprSrcRegParams::create()
{
    return new TimingExprSrcReg(this);
}

TimingExprReadIntReg *
TimingExprReadIntRegParams::create()
{
    return new TimingExprReadIntReg(this);
}

TimingExprLet *
TimingExprLetParams::create()
{
    return new TimingExprLet(this);
}

TimingExprRef *
TimingExprRefParams::create()
{
    return new TimingExprRef(this);
}

TimingExprUn *
TimingExprUnParams::create()
{
    return new TimingExprUn(this);
}

TimingExprBin *
TimingExprBinParams::create()
{
    return new TimingExprBin(this);
}

TimingExprIf *
TimingExprIfParams::create()
{
    return new TimingExprIf(this);
}
