/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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/. */

#ifndef jit_Label_h
#define jit_Label_h

#include "mozilla/Assertions.h"

#include <stdint.h>

namespace js {
namespace jit {

struct LabelBase {
 private:
  // We use uint32_t instead of bool to ensure MSVC packs these fields
  // correctly.
  uint32_t bound_ : 1;

  // offset_ < INVALID_OFFSET means that the label is either bound or has
  // incoming uses and needs to be bound.
  uint32_t offset_ : 31;

  void operator=(const LabelBase& label) = delete;

#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \
    defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64)
 public:
#endif
  static const uint32_t INVALID_OFFSET = 0x7fffffff;  // UINT31_MAX.

 public:
  LabelBase() : bound_(false), offset_(INVALID_OFFSET) {}

  // If the label is bound, all incoming edges have been patched and any
  // future incoming edges will be immediately patched.
  bool bound() const { return bound_; }
  int32_t offset() const {
    MOZ_ASSERT(bound() || used());
    return offset_;
  }
  // Returns whether the label is not bound, but has incoming uses.
  bool used() const { return !bound() && offset_ < INVALID_OFFSET; }
  // Binds the label, fixing its final position in the code stream.
  void bind(int32_t offset) {
    MOZ_ASSERT(!bound());
    MOZ_ASSERT(offset >= 0);
    MOZ_ASSERT(uint32_t(offset) < INVALID_OFFSET);
    offset_ = offset;
    bound_ = true;
    MOZ_ASSERT(offset_ == offset, "offset fits in 31 bits");
  }
  // Marks the label as neither bound nor used.
  void reset() {
    offset_ = INVALID_OFFSET;
    bound_ = false;
  }
  // Sets the label's latest used position.
  void use(int32_t offset) {
    MOZ_ASSERT(!bound());
    MOZ_ASSERT(offset >= 0);
    MOZ_ASSERT(uint32_t(offset) < INVALID_OFFSET);
    offset_ = offset;
    MOZ_ASSERT(offset_ == offset, "offset fits in 31 bits");
  }
};

// A label represents a position in an assembly buffer that may or may not have
// already been generated. Labels can either be "bound" or "unbound", the
// former meaning that its position is known and the latter that its position
// is not yet known.
//
// A jump to an unbound label adds that jump to the label's incoming queue. A
// jump to a bound label automatically computes the jump distance. The process
// of binding a label automatically corrects all incoming jumps.
class Label : public LabelBase {
 public:
#ifdef DEBUG
  ~Label();
#endif
};

static_assert(sizeof(Label) == sizeof(uint32_t),
              "Label should have same size as uint32_t");

// Label's destructor asserts that if it has been used it has also been bound.
// In the case long-lived labels, however, failed compilation (e.g. OOM) will
// trigger this failure innocuously. This Label silences the assertion.
class NonAssertingLabel : public Label {
 public:
#ifdef DEBUG
  ~NonAssertingLabel() {
    if (used()) {
      bind(0);
    }
  }
#endif
};

}  // namespace jit
}  // namespace js

#endif  // jit_Label_h
