/*
 * Copyright (C) 2015-2020 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. 
 */

#include "config.h"
#include "B3LowerMacrosAfterOptimizations.h"

#if ENABLE(B3_JIT)

#include "AirArg.h"
#include "B3BlockInsertionSet.h"
#include "B3CCallValue.h"
#include "B3ConstDoubleValue.h"
#include "B3ConstFloatValue.h"
#include "B3ConstPtrValue.h"
#include "B3InsertionSetInlines.h"
#include "B3PhaseScope.h"
#include "B3ValueInlines.h"

namespace JSC { namespace B3 {

using Arg = Air::Arg;
using Code = Air::Code;
using Tmp = Air::Tmp;

namespace {

class LowerMacrosAfterOptimizations {
public:
    LowerMacrosAfterOptimizations(Procedure& proc)
        : m_proc(proc)
        , m_blockInsertionSet(proc)
        , m_insertionSet(proc)
    {
    }

    bool run()
    {
        for (BasicBlock* block : m_proc) {
            m_block = block;
            processCurrentBlock();
        }
        m_changed |= m_blockInsertionSet.execute();
        if (m_changed) {
            m_proc.resetReachability();
            m_proc.invalidateCFG();
        }
        return m_changed;
    }
    
private:
    void processCurrentBlock()
    {
        for (m_index = 0; m_index < m_block->size(); ++m_index) {
            m_value = m_block->at(m_index);
            m_origin = m_value->origin();
            switch (m_value->opcode()) {
            case Abs: {
                // ARM supports this instruction natively.
                if (isARM64())
                    break;

                Value* mask = nullptr;
                if (m_value->type() == Double)
                    mask = m_insertionSet.insert<ConstDoubleValue>(m_index, m_origin, bitwise_cast<double>(~(1ll << 63)));
                else if (m_value->type() == Float)
                    mask = m_insertionSet.insert<ConstFloatValue>(m_index, m_origin, bitwise_cast<float>(~(1 << 31)));
                else
                    RELEASE_ASSERT_NOT_REACHED();
                Value* result = m_insertionSet.insert<Value>(m_index, BitAnd, m_origin, m_value->child(0), mask);
                m_value->replaceWithIdentity(result);
                break;
            }
            case Ceil: {
                if (MacroAssembler::supportsFloatingPointRounding())
                    break;

                Value* functionAddress = nullptr;
                if (m_value->type() == Double)
                    functionAddress = m_insertionSet.insert<ConstPtrValue>(m_index, m_origin, tagCFunction<OperationPtrTag>(Math::ceilDouble));
                else if (m_value->type() == Float)
                    functionAddress = m_insertionSet.insert<ConstPtrValue>(m_index, m_origin, tagCFunction<OperationPtrTag>(Math::ceilFloat));
                else
                    RELEASE_ASSERT_NOT_REACHED();

                Value* result = m_insertionSet.insert<CCallValue>(m_index,
                    m_value->type(),
                    m_origin,
                    Effects::none(),
                    functionAddress,
                    m_value->child(0));
                m_value->replaceWithIdentity(result);
                break;
            }
            case Floor: {
                if (MacroAssembler::supportsFloatingPointRounding())
                    break;

                Value* functionAddress = nullptr;
                if (m_value->type() == Double)
                    functionAddress = m_insertionSet.insert<ConstPtrValue>(m_index, m_origin, tagCFunction<OperationPtrTag>(Math::floorDouble));
                else if (m_value->type() == Float)
                    functionAddress = m_insertionSet.insert<ConstPtrValue>(m_index, m_origin, tagCFunction<OperationPtrTag>(Math::floorFloat));
                else
                    RELEASE_ASSERT_NOT_REACHED();

                Value* result = m_insertionSet.insert<CCallValue>(m_index,
                    m_value->type(),
                    m_origin,
                    Effects::none(),
                    functionAddress,
                    m_value->child(0));
                m_value->replaceWithIdentity(result);
                break;
            }
            case Neg: {
                if (!m_value->type().isFloat())
                    break;
                
                // X86 is odd in that it requires this.
                if (!isX86())
                    break;

                Value* mask = nullptr;
                if (m_value->type() == Double)
                    mask = m_insertionSet.insert<ConstDoubleValue>(m_index, m_origin, -0.0);
                else {
                    RELEASE_ASSERT(m_value->type() == Float);
                    mask = m_insertionSet.insert<ConstFloatValue>(m_index, m_origin, -0.0f);
                }

                Value* result = m_insertionSet.insert<Value>(
                    m_index, BitXor, m_origin, m_value->child(0), mask);
                m_value->replaceWithIdentity(result);
                break;
            }

            case RotL: {
                // ARM64 doesn't have a rotate left.
                if (isARM64()) {
                    Value* newShift = m_insertionSet.insert<Value>(m_index, Neg, m_value->origin(), m_value->child(1));
                    Value* rotate = m_insertionSet.insert<Value>(m_index, RotR, m_value->origin(), m_value->child(0), newShift);
                    m_value->replaceWithIdentity(rotate);
                    break;
                }
                break;
            }
                
            default:
                break;
            }
        }
        m_insertionSet.execute(m_block);
    }
    
    Procedure& m_proc;
    BlockInsertionSet m_blockInsertionSet;
    InsertionSet m_insertionSet;
    BasicBlock* m_block;
    unsigned m_index;
    Value* m_value;
    Origin m_origin;
    bool m_changed { false };
};

bool lowerMacrosImpl(Procedure& proc)
{
    LowerMacrosAfterOptimizations lowerMacros(proc);
    return lowerMacros.run();
}

} // anonymous namespace

bool lowerMacrosAfterOptimizations(Procedure& proc)
{
    PhaseScope phaseScope(proc, "lowerMacrosAfterOptimizations"_s);
    bool result = lowerMacrosImpl(proc);
    if (shouldValidateIR())
        RELEASE_ASSERT(!lowerMacrosImpl(proc));
    return result;
}

} } // namespace JSC::B3

#endif // ENABLE(B3_JIT)

