# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

# Pretty-printers for GCCellPtr values.

import gdb

import mozilla.prettyprinters
from mozilla.prettyprinters import pretty_printer

# Forget any printers from previous loads of this module.
mozilla.prettyprinters.clear_module_printers(__name__)

# Cache information about the types for this objfile.


class GCCellPtrTypeCache:
    def __init__(self, cache):
        self.TraceKind_t = gdb.lookup_type("JS::TraceKind")
        self.AllocKind_t = gdb.lookup_type("js::gc::AllocKind")
        self.Arena_t = gdb.lookup_type("js::gc::Arena")
        self.Cell_t = gdb.lookup_type("js::gc::Cell")
        self.TenuredCell_t = gdb.lookup_type("js::gc::TenuredCell")

        trace_kinds = gdb.types.make_enum_dict(self.TraceKind_t)
        alloc_kinds = gdb.types.make_enum_dict(self.AllocKind_t)

        def trace_kind(k):
            return trace_kinds["JS::TraceKind::" + k]

        def alloc_kind(k):
            return alloc_kinds["js::gc::AllocKind::" + k]

        # Build a mapping from TraceKind enum values to the types they denote.
        trace_map = {
            # Inline types.
            "Object": "JSObject",
            "BigInt": "JS::BigInt",
            "String": "JSString",
            "Symbol": "JS::Symbol",
            "Shape": "js::Shape",
            "BaseShape": "js::BaseShape",
            "Null": "std::nullptr_t",
            # Out-of-line types.
            "JitCode": "js::jit::JitCode",
            "Script": "js::BaseScript",
            "Scope": "js::Scope",
            "RegExpShared": "js::RegExpShared",
            "GetterSetter": "js::GetterSetter",
            "PropMap": "js::PropMap",
        }

        # Map from AllocKind to TraceKind for out-of-line types.
        alloc_map = {
            "JITCODE": "JitCode",
            "SCRIPT": "Script",
            "SCOPE": "Scope",
            "REGEXP_SHARED": "RegExpShared",
            "GETTER_SETTER": "GetterSetter",
            "COMPACT_PROP_MAP": "PropMap",
            "NORMAL_PROP_MAP": "PropMap",
            "DICT_PROP_MAP": "PropMap",
        }

        self.trace_kind_to_type = {
            trace_kind(k): gdb.lookup_type(v) for k, v in trace_map.items()
        }
        self.alloc_kind_to_trace_kind = {
            alloc_kind(k): trace_kind(v) for k, v in alloc_map.items()
        }

        self.Null = trace_kind("Null")
        self.tracekind_mask = gdb.parse_and_eval("JS::OutOfLineTraceKindMask")
        self.arena_mask = gdb.parse_and_eval("js::gc::ArenaMask")


@pretty_printer("JS::GCCellPtr")
class GCCellPtr:
    def __init__(self, value, cache):
        self.value = value
        if not cache.mod_GCCellPtr:
            cache.mod_GCCellPtr = GCCellPtrTypeCache(cache)
        self.cache = cache

    def to_string(self):
        ptr = self.value["ptr"]
        kind = ptr & self.cache.mod_GCCellPtr.tracekind_mask
        if kind == self.cache.mod_GCCellPtr.Null:
            return "JS::GCCellPtr(nullptr)"
        if kind == self.cache.mod_GCCellPtr.tracekind_mask:
            # Out-of-line trace kinds.
            #
            # Compute the underlying type for out-of-line kinds by
            # reimplementing the GCCellPtr::outOfLineKind() method.
            #
            # The extra casts below are only present to make it easier to
            # compare this code against the C++ implementation.

            # GCCellPtr::asCell()
            cell_ptr = ptr & ~self.cache.mod_GCCellPtr.tracekind_mask
            cell = cell_ptr.reinterpret_cast(self.cache.mod_GCCellPtr.Cell_t.pointer())

            # Cell::asTenured()
            tenured = cell.cast(self.cache.mod_GCCellPtr.TenuredCell_t.pointer())

            # TenuredCell::arena()
            addr = int(tenured)
            arena_ptr = addr & ~self.cache.mod_GCCellPtr.arena_mask
            arena = arena_ptr.reinterpret_cast(
                self.cache.mod_GCCellPtr.Arena_t.pointer()
            )

            # Arena::getAllocKind()
            alloc_kind = arena["allocKind"].cast(self.cache.mod_GCCellPtr.AllocKind_t)
            alloc_idx = int(
                alloc_kind.cast(self.cache.mod_GCCellPtr.AllocKind_t.target())
            )

            # Map the AllocKind to a TraceKind.
            kind = self.cache.mod_GCCellPtr.alloc_kind_to_trace_kind[alloc_idx]
        type_name = self.cache.mod_GCCellPtr.trace_kind_to_type[int(kind)]
        return f"JS::GCCellPtr(({type_name}*) {ptr.cast(self.cache.void_ptr_t)})"
