/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 *
 * Copyright 2016 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "wasm/WasmCompartment.h"

#include "jscompartment.h"

#include "wasm/WasmInstance.h"

#include "vm/Debugger-inl.h"

using namespace js;
using namespace wasm;

Compartment::Compartment(Zone* zone)
  : mutatingInstances_(false),
    activationCount_(0),
    profilingEnabled_(false)
{}

Compartment::~Compartment()
{
    MOZ_ASSERT(activationCount_ == 0);
    MOZ_ASSERT(instances_.empty());
    MOZ_ASSERT(!mutatingInstances_);
}

struct InstanceComparator
{
    const Instance& target;
    explicit InstanceComparator(const Instance& target) : target(target) {}

    int operator()(const Instance* instance) const {
        if (instance == &target)
            return 0;
        MOZ_ASSERT(!target.codeSegment().containsCodePC(instance->codeBase()));
        MOZ_ASSERT(!instance->codeSegment().containsCodePC(target.codeBase()));
        return target.codeBase() < instance->codeBase() ? -1 : 1;
    }
};

void
Compartment::trace(JSTracer* trc)
{
    // A WasmInstanceObject that was initially reachable when called can become
    // unreachable while executing on the stack. Since wasm does not otherwise
    // scan the stack during GC to identify live instances, we mark all instance
    // objects live if there is any running wasm in the compartment.
    if (activationCount_) {
        for (Instance* i : instances_)
            i->trace(trc);
    }
}

bool
Compartment::registerInstance(JSContext* cx, HandleWasmInstanceObject instanceObj)
{
    Instance& instance = instanceObj->instance();
    MOZ_ASSERT(this == &instance.compartment()->wasm);

    if (!instance.ensureProfilingState(cx, profilingEnabled_))
        return false;

    size_t index;
    if (BinarySearchIf(instances_, 0, instances_.length(), InstanceComparator(instance), &index))
        MOZ_CRASH("duplicate registration");

    {
        AutoMutateInstances guard(*this);
        if (!instances_.insert(instances_.begin() + index, &instance)) {
            ReportOutOfMemory(cx);
            return false;
        }
    }

    Debugger::onNewWasmInstance(cx, instanceObj);
    return true;
}

void
Compartment::unregisterInstance(Instance& instance)
{
    size_t index;
    if (!BinarySearchIf(instances_, 0, instances_.length(), InstanceComparator(instance), &index))
        return;

    AutoMutateInstances guard(*this);
    instances_.erase(instances_.begin() + index);
}

struct PCComparator
{
    const void* pc;
    explicit PCComparator(const void* pc) : pc(pc) {}

    int operator()(const Instance* instance) const {
        if (instance->codeSegment().containsCodePC(pc))
            return 0;
        return pc < instance->codeBase() ? -1 : 1;
    }
};

Code*
Compartment::lookupCode(const void* pc) const
{
    Instance* instance = lookupInstanceDeprecated(pc);
    return instance ? &instance->code() : nullptr;
}

Instance*
Compartment::lookupInstanceDeprecated(const void* pc) const
{
    // lookupInstanceDeprecated can be called asynchronously from the interrupt
    // signal handler. In that case, the signal handler is just asking whether
    // the pc is in wasm code. If instances_ is being mutated then we can't be
    // executing wasm code so returning nullptr is fine.
    if (mutatingInstances_)
        return nullptr;

    size_t index;
    if (!BinarySearchIf(instances_, 0, instances_.length(), PCComparator(pc), &index))
        return nullptr;

    return instances_[index];
}

bool
Compartment::ensureProfilingState(JSContext* cx)
{
    bool newProfilingEnabled = cx->spsProfiler.enabled();
    if (profilingEnabled_ == newProfilingEnabled)
        return true;

    // Since one Instance can call another Instance in the same compartment
    // directly without calling through Instance::callExport(), when profiling
    // is enabled, enable it for the entire compartment at once. It is only safe
    // to enable profiling when the wasm is not on the stack, so delay enabling
    // profiling until there are no live WasmActivations in this compartment.

    if (activationCount_ > 0)
        return true;

    for (Instance* instance : instances_) {
        if (!instance->ensureProfilingState(cx, newProfilingEnabled))
            return false;
    }

    profilingEnabled_ = newProfilingEnabled;
    return true;
}

bool
Compartment::profilingEnabled() const
{
    // Profiling can asynchronously interrupt the mutation of the instances_
    // vector which is used by lookupCode() during stack-walking. To handle
    // this rare case, disable profiling during mutation.
    return profilingEnabled_ && !mutatingInstances_;
}

void
Compartment::addSizeOfExcludingThis(MallocSizeOf mallocSizeOf, size_t* compartmentTables)
{
    *compartmentTables += instances_.sizeOfExcludingThis(mallocSizeOf);
}