/*
 * Copyright (C) 2015-2022 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 "Identifier.h"
#include "InitializeThreading.h"
#include "JSCInlines.h"
#include "JSCJSValue.h"
#include "JSGlobalObject.h"
#include "JSLock.h"
#include "JSObject.h"
#include "VM.h"
#include <wtf/MainThread.h>
#include <wtf/text/StringCommon.h>

using namespace JSC;

namespace {

Lock crashLock;
const char* nameFilter;
unsigned requestedIterationCount;

#define CHECK(x) do {                                                   \
        if (!!(x))                                                      \
            break;                                                      \
        crashLock.lock();                                               \
        WTFReportAssertionFailure(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, #x); \
        CRASH();                                                        \
    } while (false)

template<typename Callback>
NEVER_INLINE void benchmarkImpl(const char* name, unsigned iterationCount, const Callback& callback)
{
    if (nameFilter && WTF::findIgnoringASCIICaseWithoutLength(name, nameFilter) == WTF::notFound)
        return;

    if (requestedIterationCount)
        iterationCount = requestedIterationCount;
    
    MonotonicTime before = MonotonicTime::now();
    callback(iterationCount);
    MonotonicTime after = MonotonicTime::now();
    dataLog(name, ": ", (after - before).milliseconds(), " ms.\n");
}

} // anonymous namespace

// Use WTF_IGNORES_THREAD_SAFETY_ANALYSIS because the function keeps holding crashLock when returning.
int main(int argc, char** argv) WTF_IGNORES_THREAD_SAFETY_ANALYSIS
{
    if (argc >= 2) {
        if (argv[1][0] == '-') {
            dataLog("Usage: dynbench [<filter> [<iteration count>]]\n");
            return 1;
        }

        nameFilter = argv[1];

        if (argc >= 3) {
            if (sscanf(argv[2], "%u", &requestedIterationCount) != 1) {
                dataLog("Could not parse iteration count ", argv[2], "\n");
                return 1;
            }
        }
    }
    
    WTF::initializeMainThread();
    JSC::initialize();

    VM& vm = VM::create(HeapType::Large).leakRef();
    {
        JSLockHolder locker(vm);

        JSGlobalObject* globalObject =
            JSGlobalObject::create(vm, JSGlobalObject::createStructure(vm, jsNull()));

        Identifier identF = Identifier::fromString(vm, "f");
        Identifier identG = Identifier::fromString(vm, "g");

        Structure* objectStructure =
            JSFinalObject::createStructure(vm, globalObject, globalObject->objectPrototype(), 2);

        // Non-strict dynamic get by id:
        JSValue object = JSFinalObject::create(vm, objectStructure);
        {
            PutPropertySlot slot(object, false, PutPropertySlot::PutById);
            object.putInline(globalObject, identF, jsNumber(42), slot);
        }
        {
            PutPropertySlot slot(object, false, PutPropertySlot::PutById);
            object.putInline(globalObject, identG, jsNumber(43), slot);
        }
        benchmarkImpl(
            "Non Strict Dynamic Get By Id",
            1000000,
            [&] (unsigned iterationCount) {
                for (unsigned i = iterationCount; i--;) {
                    JSValue result = object.get(globalObject, identF);
                    CHECK(result == jsNumber(42));
                    result = object.get(globalObject, identG);
                    CHECK(result == jsNumber(43));
                }
            });

        // Non-strict dynamic put by id replace:
        object = JSFinalObject::create(vm, objectStructure);
        {
            PutPropertySlot slot(object, false, PutPropertySlot::PutById);
            object.putInline(globalObject, identF, jsNumber(42), slot);
        }
        {
            PutPropertySlot slot(object, false, PutPropertySlot::PutById);
            object.putInline(globalObject, identG, jsNumber(43), slot);
        }
        benchmarkImpl(
            "Non Strict Dynamic Put By Id Replace",
            1000000,
            [&] (unsigned iterationCount) {
                for (unsigned i = iterationCount; i--;) {
                    {
                        PutPropertySlot slot(object, false, PutPropertySlot::PutById);
                        object.putInline(globalObject, identF, jsNumber(i), slot);
                    }
                    {
                        PutPropertySlot slot(object, false, PutPropertySlot::PutById);
                        object.putInline(globalObject, identG, jsNumber(i), slot);
                    }
                }
            });

        // Non-strict dynamic put by id transition with object allocation:
        benchmarkImpl(
            "Non Strict Dynamic Allocation and Put By Id Transition",
            1000000,
            [&] (unsigned iterationCount) {
                for (unsigned i = iterationCount; i--;) {
                    JSValue object = JSFinalObject::create(vm, objectStructure);
                    {
                        PutPropertySlot slot(object, false, PutPropertySlot::PutById);
                        object.putInline(globalObject, identF, jsNumber(i), slot);
                    }
                    {
                        PutPropertySlot slot(object, false, PutPropertySlot::PutById);
                        object.putInline(globalObject, identG, jsNumber(i), slot);
                    }
                }
            });

        // Non-strict dynamic get by id with dynamic store context:
        object = JSFinalObject::create(vm, objectStructure);
        {
            PutPropertySlot slot(object, false);
            object.putInline(globalObject, identF, jsNumber(42), slot);
        }
        {
            PutPropertySlot slot(object, false);
            object.putInline(globalObject, identG, jsNumber(43), slot);
        }
        benchmarkImpl(
            "Non Strict Dynamic Get By Id With Dynamic Store Context",
            1000000,
            [&] (unsigned iterationCount) {
                for (unsigned i = iterationCount; i--;) {
                    JSValue result = object.get(globalObject, identF);
                    CHECK(result == jsNumber(42));
                    result = object.get(globalObject, identG);
                    CHECK(result == jsNumber(43));
                }
            });

        // Non-strict dynamic put by id replace with dynamic store context:
        object = JSFinalObject::create(vm, objectStructure);
        {
            PutPropertySlot slot(object, false);
            object.putInline(globalObject, identF, jsNumber(42), slot);
        }
        {
            PutPropertySlot slot(object, false);
            object.putInline(globalObject, identG, jsNumber(43), slot);
        }
        benchmarkImpl(
            "Non Strict Dynamic Put By Id Replace With Dynamic Store Context",
            1000000,
            [&] (unsigned iterationCount) {
                for (unsigned i = iterationCount; i--;) {
                    {
                        PutPropertySlot slot(object, false);
                        object.putInline(globalObject, identF, jsNumber(i), slot);
                    }
                    {
                        PutPropertySlot slot(object, false);
                        object.putInline(globalObject, identG, jsNumber(i), slot);
                    }
                }
            });

        // Non-strict dynamic put by id transition with object allocation with dynamic store context:
        benchmarkImpl(
            "Non Strict Dynamic Allocation and Put By Id Transition With Dynamic Store Context",
            1000000,
            [&] (unsigned iterationCount) {
                for (unsigned i = iterationCount; i--;) {
                    JSValue object = JSFinalObject::create(vm, objectStructure);
                    {
                        PutPropertySlot slot(object, false);
                        object.putInline(globalObject, identF, jsNumber(i), slot);
                    }
                    {
                        PutPropertySlot slot(object, false);
                        object.putInline(globalObject, identG, jsNumber(i), slot);
                    }
                }
            });
    }

    crashLock.lock();
    return 0;
}

