/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=4 sw=4 et tw=99 ft=cpp:
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released
 * May 28, 2008.
 *
 * The Initial Developer of the Original Code is
 *   the Mozilla Corporation.
 *
 * Contributor(s):
 *   Nicholas Nethercote <nnethercote@mozilla.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "jsprf.h"
#include "jstl.h"
#include "jscompartment.h"
#include "Writer.h"
#include "nanojit.h"

namespace js {
namespace tjit {

using namespace nanojit;

class FuncFilter : public LirWriter
{
public:
    FuncFilter(LirWriter *out):
        LirWriter(out)
    {
    }

    LIns *ins2(LOpcode v, LIns *s0, LIns *s1)
    {
        if (s0 == s1 && v == LIR_eqd) {
            // 'eqd x, x' will always succeed if 'x' cannot be NaN
            if (IsPromotedInt32OrUint32(s0)) {
                // x = <a number that fits in int32 or uint32>      # cannot be NaN
                // c = eqd x, x
                return insImmI(1);
            }
            if (s0->isop(LIR_addd) || s0->isop(LIR_subd) || s0->isop(LIR_muld)) {
                LIns *lhs = s0->oprnd1();
                LIns *rhs = s0->oprnd2();
                if (IsPromotedInt32OrUint32(lhs) && IsPromotedInt32OrUint32(rhs)) {
                    // a = <a number that fits in int32 or uint32>  # cannot be NaN
                    // b = <a number that fits in int32 or uint32>  # cannot be NaN
                    // x = addd/subd/muld a, b                      # cannot be NaN
                    // c = eqd x, x
                    return insImmI(1);
                }
            }
        } else if (isCmpDOpcode(v)) {
            if (IsPromotedInt32(s0) && IsPromotedInt32(s1)) {
                v = cmpOpcodeD2I(v);
                return out->ins2(v, DemoteToInt32(out, s0), DemoteToInt32(out, s1));
            } else if (IsPromotedUint32(s0) && IsPromotedUint32(s1)) {
                // uint compare
                v = cmpOpcodeD2UI(v);
                return out->ins2(v, DemoteToUint32(out, s0), DemoteToUint32(out, s1));
            }
        }
        return out->ins2(v, s0, s1);
    }
};

void
Writer::init(LogControl *logc_)
{
    JS_ASSERT(logc_);
    logc = logc_;

    LirWriter *&lir = InitConst(this->lir);
    CseFilter *&cse = InitConst(this->cse);
    lir = new (alloc) LirBufWriter(lirbuf, AvmCore::config);
#ifdef DEBUG
    ValidateWriter *validate2;
    lir = validate2 =
        new (alloc) ValidateWriter(lir, lirbuf->printer, "end of writer pipeline");
#endif
#ifdef JS_JIT_SPEW
    if (logc->lcbits & LC_TMRecorder)
       lir = new (alloc) VerboseWriter(*alloc, lir, lirbuf->printer, logc);
#endif
    if (avmplus::AvmCore::config.cseopt) {
        cse = new (alloc) CseFilter(lir, TM_NUM_USED_ACCS, *alloc);
        if (!cse->initOOM)
            lir = cse;      // Skip CseFilter if we OOM'd when creating it.
    }
    lir = new (alloc) ExprFilter(lir);
    lir = new (alloc) FuncFilter(lir);
#ifdef DEBUG
    ValidateWriter *validate1 =
        new (alloc) ValidateWriter(lir, lirbuf->printer, "start of writer pipeline");
    lir = validate1;
#endif
}

bool
IsPromotedInt32(LIns* ins)
{
    if (ins->isop(LIR_i2d))
        return true;
    if (ins->isImmD()) {
        jsdouble d = ins->immD();
        return d == jsdouble(jsint(d)) && !JSDOUBLE_IS_NEGZERO(d);
    }
    return false;
}

bool
IsPromotedUint32(LIns* ins)
{
    if (ins->isop(LIR_ui2d))
        return true;
    if (ins->isImmD()) {
        jsdouble d = ins->immD();
        return d == jsdouble(jsuint(d)) && !JSDOUBLE_IS_NEGZERO(d);
    }
    return false;
}

bool
IsPromotedInt32OrUint32(LIns* ins)
{
    return IsPromotedInt32(ins) || IsPromotedUint32(ins);
}

LIns *
DemoteToInt32(LirWriter *out, LIns *ins)
{
    JS_ASSERT(IsPromotedInt32(ins));
    if (ins->isop(LIR_i2d))
        return ins->oprnd1();
    JS_ASSERT(ins->isImmD());
    return out->insImmI(int32_t(ins->immD()));
}

LIns *
DemoteToUint32(LirWriter *out, LIns *ins)
{
    JS_ASSERT(IsPromotedUint32(ins));
    if (ins->isop(LIR_ui2d))
        return ins->oprnd1();
    JS_ASSERT(ins->isImmD());
    return out->insImmI(uint32_t(ins->immD()));
}

}   /* namespace tjit */
}   /* namespace js */

#ifdef DEBUG
namespace nanojit {

using namespace js;
using namespace js::tjit;

static bool
match(LIns *base, LOpcode opcode, AccSet accSet, int32_t disp)
{
    return base->isop(opcode) &&
           base->accSet() == accSet &&
           base->disp() == disp;
}

static bool
match(LIns *base, LOpcode opcode, AccSet accSet, LoadQual loadQual, int32_t disp)
{
    return base->isop(opcode) &&
           base->accSet() == accSet &&
           base->loadQual() == loadQual &&
           base->disp() == disp;
}

static bool
couldBeObjectOrString(LIns *ins)
{
    bool ret = false;

    if (ins->isop(LIR_callp)) {
        // ins = callp ...      # could be a call to an object-creating function
        ret = true;

    } else if (ins->isop(LIR_ldp)) {
        // ins = ldp ...        # could be an object, eg. loaded from the stack
        ret = true;

    } else if (ins->isImmP()) {
        // ins = immp ...       # could be a pointer to an object
        uintptr_t val = uintptr_t(ins->immP());
        if (val == 0 || val > 4096)
            ret = true;         // Looks like a pointer

    } else if (ins->isop(LIR_cmovp)) {
        // ins = cmovp <JSObject>, <JSObject>
        ret = couldBeObjectOrString(ins->oprnd2()) &&
              couldBeObjectOrString(ins->oprnd3());

    } else if (!avmplus::AvmCore::use_cmov() &&
               ins->isop(LIR_ori) &&
               ins->oprnd1()->isop(LIR_andi) &&
               ins->oprnd2()->isop(LIR_andi))
    {
        // This is a partial check for the insChoose() code that only occurs
        // is use_cmov() is false.
        //
        // ins_oprnd1 = andi ...
        // ins_oprnd2 = andi ...
        // ins = ori ins_oprnd1, ins_oprnd2
        ret = true;

#if JS_BITS_PER_WORD == 64
    } else if (ins->isop(LIR_andq) &&
               ins->oprnd1()->isop(LIR_ldq) &&
               ins->oprnd2()->isImmQ() &&
               uintptr_t(ins->oprnd2()->immQ()) == JSVAL_PAYLOAD_MASK)
    {
        // ins_oprnd1 = ldq ...
        // ins_oprnd2 = immq JSVAL_PAYLOAD_MASK
        // ins = andq ins_oprnd1, ins_oprnd2
        ret = true;
#endif
    } else if (ins->isop(LIR_addp) &&
               ((ins->oprnd1()->isImmP() &&
                 (void *)ins->oprnd1()->immP() == JSString::unitStringTable) ||
                (ins->oprnd2()->isImmP() &&
                 (void *)ins->oprnd2()->immP() == JSString::unitStringTable)))
    {
        // (String only)
        // ins = addp ..., JSString::unitStringTable
        //   OR
        // ins = addp JSString::unitStringTable, ...
        ret = true;
    }

    return ret;
}

static bool
isConstPrivatePtr(LIns *ins, unsigned slot)
{
#if JS_BITS_PER_WORD == 32
    // ins = ldp.slots/c ...[<offset of slot>]
    return match(ins, LIR_ldp, ACCSET_SLOTS, LOAD_CONST, slot * sizeof(Value) + sPayloadOffset);
#elif JS_BITS_PER_WORD == 64
    // ins_oprnd1 = ldp.slots/c ...[<offset of slot>]
    // ins_oprnd2 = immi 1
    // ins = lshq ins_oprnd1, ins_oprnd2
    return ins->isop(LIR_lshq) &&
           match(ins->oprnd1(), LIR_ldp, ACCSET_SLOTS, LOAD_CONST, slot * sizeof(Value)) &&
           ins->oprnd2()->isImmI(1);
#endif
}

/*
 * Any time you use an AccSet annotation other than ACCSET_ALL, you are making
 * a promise to Nanojit about the properties of the annotated load/store/call.
 * If that annotation is wrong, it could cause rare and subtle bugs.  So this
 * function does its damnedest to prevent such bugs occurring by carefully
 * checking every load and store.
 *
 * For some access regions, we can check perfectly -- eg. for an ACCSET_STATE
 * load/store, the base pointer must be 'state'.  For others, we can only
 * check imperfectly -- eg. for an ACCSET_OBJ_CLASP load/store, we can check that
 * the base pointer has one of several forms, but it's possible that a
 * non-object has that form as well.  This imperfect checking is unfortunate
 * but unavoidable.  Also, multi-region load/store AccSets are not checked,
 * and so are best avoided (they're rarely needed).  Finally, the AccSet
 * annotations on calls cannot be checked here;  in some cases they can be
 * partially checked via assertions (eg. by checking that certain values
 * are not changed by the function).
 */
void ValidateWriter::checkAccSet(LOpcode op, LIns *base, int32_t disp, AccSet accSet)
{
    bool ok;

    NanoAssert(accSet != ACCSET_NONE);

    #define dispWithin(Struct) \
        (0 <= disp && disp < int32_t(sizeof(Struct)))

    switch (accSet) {
      case ACCSET_STATE:
        // base = paramp 0 0
        // ins  = {ld,st}X.state base[<disp within TracerState>]
        ok = dispWithin(TracerState) && 
             base->isop(LIR_paramp) &&
             base->paramKind() == 0 &&
             base->paramArg() == 0;
        break;

      case ACCSET_STACK:
        // base = ldp.state ...[offsetof(TracerState, sp)]
        // ins  = {ld,st}X.sp base[...]
        //   OR
        // base_oprnd1 = ldp.state ...[offsetof(TraceState, sp)]
        // base        = addp base_oprnd1, ...
        // ins         = {ld,st}X.sp base[...]
        ok = match(base, LIR_ldp, ACCSET_STATE, offsetof(TracerState, sp)) ||
             (base->isop(LIR_addp) &&
              match(base->oprnd1(), LIR_ldp, ACCSET_STATE, offsetof(TracerState, sp)));
        break;

      case ACCSET_RSTACK:
        // base = ldp.state ...[offsetof(TracerState, rp)]
        // ins  = {ld,st}p.rp base[...]
        //   OR
        // base = ldp.state ...[offsetof(TracerState, callstackBaseOffset)]
        // ins  = {ld,st}p.rp base[...]
        ok = (op == LIR_ldp || op == LIR_stp) &&
             (match(base, LIR_ldp, ACCSET_STATE, offsetof(TracerState, rp)) ||
              match(base, LIR_ldp, ACCSET_STATE, offsetof(TracerState, callstackBase)));
        break;

      case ACCSET_CX:
        // base = ldp.state ...[offsetof(TracerState, cx)]
        // ins  = {ld,st}X.cx base[<disp within JSContext>]
        ok = dispWithin(JSContext) &&
             match(base, LIR_ldp, ACCSET_STATE, offsetof(TracerState, cx));
        break;

      case ACCSET_TM:
          // base = immp
          ok = base->isImmP() && disp == 0;
          break;

      case ACCSET_EOS:
        // base = ldp.state ...[offsetof(TracerState, eos)]
        // ins  = {ld,st}X.eos base[...]
        ok = match(base, LIR_ldp, ACCSET_STATE, offsetof(TracerState, eos));
        break;

      case ACCSET_ALLOC:
        // base = allocp ...
        // ins  = {ld,st}X.alloc base[...]
        //   OR
        // base_oprnd1 = allocp ...
        // base        = addp base_oprnd1, ...
        // ins         = {ld,st}X.alloc base[...]
        ok = base->isop(LIR_allocp) ||
             (base->isop(LIR_addp) &&
              base->oprnd1()->isop(LIR_allocp));
        break;

      case ACCSET_FRAMEREGS:
        // base = ldp.cx ...[offsetof(JSContext, regs)]
        // ins  = ldp.regs base[<disp within JSFrameRegs>]
        ok = op == LIR_ldp &&
             dispWithin(JSFrameRegs) && 
             match(base, LIR_ldp, ACCSET_CX, offsetof(JSContext, regs));
        break;

      case ACCSET_STACKFRAME:
        // base = ldp.regs ...[offsetof(JSFrameRegs, fp)]
        // ins  = {ld,st}X.sf base[<disp within JSStackFrame>]
        ok = dispWithin(JSStackFrame) && 
             match(base, LIR_ldp, ACCSET_FRAMEREGS, offsetof(JSFrameRegs, fp));
        break;

      case ACCSET_RUNTIME:
        // base = ldp.cx ...[offsetof(JSContext, runtime)]
        // ins  = ldp.rt base[<disp within JSRuntime>]
        ok = dispWithin(JSRuntime) &&
             match(base, LIR_ldp, ACCSET_CX, offsetof(JSContext, runtime));
        break;

      // This check is imperfect.
      //
      // base = <JSObject>
      // ins  = ldp.obj<field> base[offsetof(JSObject, <field>)]
      #define OK_OBJ_FIELD(ldop, field) \
            op == ldop && \
            disp == offsetof(JSObject, field) && \
            couldBeObjectOrString(base)

      case ACCSET_OBJ_CLASP:
        ok = OK_OBJ_FIELD(LIR_ldp, clasp);
        break;

      case ACCSET_OBJ_FLAGS:
        ok = OK_OBJ_FIELD(LIR_ldi, flags);
        break;

      case ACCSET_OBJ_SHAPE:
        ok = OK_OBJ_FIELD(LIR_ldi, objShape);
        break;

      case ACCSET_OBJ_PROTO:
        ok = OK_OBJ_FIELD(LIR_ldp, proto);
        break;

      case ACCSET_OBJ_PARENT:
        ok = OK_OBJ_FIELD(LIR_ldp, parent);
        break;

      case ACCSET_OBJ_PRIVATE:
        // base = <JSObject>
        // ins  = {ld,st}p.objprivate base[offsetof(JSObject, privateData)]
        ok = (op == LIR_ldi || op == LIR_ldp ||
              op == LIR_sti || op == LIR_stp) &&
             disp == offsetof(JSObject, privateData) &&
             couldBeObjectOrString(base);
        break;

      case ACCSET_OBJ_CAPACITY:
        ok = OK_OBJ_FIELD(LIR_ldi, capacity);
        break;

      case ACCSET_OBJ_SLOTS:
        ok = OK_OBJ_FIELD(LIR_ldp, slots);
        break;

      case ACCSET_SLOTS:
        // This check is imperfect.
        //
        // base = <JSObject>                                          # direct slot access
        // ins  = {ld,st}X.slots base[...]
        //   OR
        // base = ldp.objslots ...[offsetof(JSObject, slots)]         # indirect slot access
        // ins  = {ld,st}X.slots base[...]
        //   OR
        // base_oprnd1 = ldp.objslots ...[offsetof(JSObject, slots)]  # indirect scaled slot access
        // base        = addp base_oprnd1, ...
        // ins         = {ld,st}X.slots base[...]
        ok = couldBeObjectOrString(base) ||
             match(base, LIR_ldp, ACCSET_OBJ_SLOTS, offsetof(JSObject, slots)) ||
             (base->isop(LIR_addp) &&
              match(base->oprnd1(), LIR_ldp, ACCSET_OBJ_SLOTS, offsetof(JSObject, slots)));
        break;

      case ACCSET_TARRAY:
        // This check is imperfect.
        //
        // base = ldp.objprivate ...[offsetof(JSObject, privateData)]
        // ins = ld{i,p}.tarray base[<disp within TypedArray>]
        ok = (op == LIR_ldi || op == LIR_ldp) &&
             dispWithin(TypedArray) &&
             match(base, LIR_ldp, ACCSET_OBJ_PRIVATE, offsetof(JSObject, privateData));
        break;

      case ACCSET_TARRAY_DATA:
        // base = ldp.tarray/c ...[TypedArray::dataOffset()]
        // ins  = {ld,st}X.tdata base[...]
        //   OR
        // base_oprnd1 = ldp.tarray/c ...[TypedArray::dataOffset()]
        // base        = addp base_oprnd1, ...
        // ins         = {ld,st}X.tdata base[...]
        ok = match(base, LIR_ldp, ACCSET_TARRAY, LOAD_CONST, TypedArray::dataOffset()) ||
             (base->isop(LIR_addp) &&
              match(base->oprnd1(), LIR_ldp, ACCSET_TARRAY, LOAD_CONST, TypedArray::dataOffset()));
        break;

      case ACCSET_ITER:
        // base = ldp.objprivate ...[offsetof(JSObject, privateData)]
        // ins = {ld,st}p.iter base[<disp within NativeIterator>]
        ok = (op == LIR_ldp || op == LIR_stp) &&
             dispWithin(NativeIterator) &&
             match(base, LIR_ldp, ACCSET_OBJ_PRIVATE, offsetof(JSObject, privateData));
        break;

      case ACCSET_ITER_PROPS:
        // base = ldp.iter ...[offsetof(NativeIterator, props_cursor)]
        // ins  = ld{i,p,d}.iterprops base[0|4]
        ok = (op == LIR_ldi || op == LIR_ldp || op == LIR_ldd) &&
             (disp == 0 || disp == 4) &&
             match(base, LIR_ldp, ACCSET_ITER, offsetof(NativeIterator, props_cursor));
        break;

      case ACCSET_STRING:
        // This check is imperfect.
        //
        // base = <JSString>
        // ins  = {ld,st}X.str base[<disp within JSString>]
        ok = dispWithin(JSString) &&
             couldBeObjectOrString(base);
        break;

      case ACCSET_STRING_MCHARS:
        // base = ldp.string ...[offsetof(JSString, chars)]
        // ins  = ldus2ui.strchars/c base[0]
        //   OR
        // base_oprnd1 = ldp.string ...[offsetof(JSString, chars)]
        // base        = addp base_oprnd1, ...
        // ins         = ldus2ui.strchars/c base[0]
        ok = op == LIR_ldus2ui &&
             disp == 0 &&
             (match(base, LIR_ldp, ACCSET_STRING, JSString::offsetOfChars()) ||
              (base->isop(LIR_addp) &&
               match(base->oprnd1(), LIR_ldp, ACCSET_STRING, JSString::offsetOfChars())));
        break;

      case ACCSET_TYPEMAP:
        // This check is imperfect, things get complicated once you get back
        // farther than 'base'.  But the parts we check are pretty distinctive
        // and should be good enough.
        //
        // base = addp base_oprnd1, ...
        // ins  = lduc2ui.typemap/c base[0]
        ok = op == LIR_lduc2ui &&
             disp == 0 &&
             base->isop(LIR_addp);
        break;

      case ACCSET_FCSLOTS:
        // This check is imperfect.
        //
        // base = <const private ptr slots[JSSLOT_FLAT_CLOSURE_UPVARS]>
        // ins = {ld,st}X.fcslots base[...]
        ok = isConstPrivatePtr(base, JSObject::JSSLOT_FLAT_CLOSURE_UPVARS);
        break;

      case ACCSET_ARGS_DATA:
        // This check is imperfect.
        //
        // base = <const private ptr slots[JSSLOT_ARGS_DATA]>
        // ins = st{i,p,d}.argsdata base[...]
        //   OR
        // base_oprnd1 = <const private ptr slots[JSSLOT_ARGS_DATA]>
        // base        = addp base_oprnd1, ...
        // ins         = {ld,st}X.argsdata base[...]
        ok = (isConstPrivatePtr(base, JSObject::JSSLOT_ARGS_DATA) ||
              (base->isop(LIR_addp) &&
               isConstPrivatePtr(base->oprnd1(), JSObject::JSSLOT_ARGS_DATA)));
        break;

      default:
        // This assertion will fail if any single-region AccSets aren't covered
        // by the switch -- only multi-region AccSets should be handled here.
        JS_ASSERT(!isSingletonAccSet(accSet));
        ok = true;
        break;
    }

    if (!ok) {
        InsBuf b1, b2;
        printer->formatIns(&b1, base);
        JS_snprintf(b2.buf, b2.len, "base = (%s); disp = %d", b1.buf, disp);
        errorAccSet(lirNames[op], accSet, b2.buf);
    }
}

}

#endif

