/* -*- 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 js_experimental_JSStencil_h
#define js_experimental_JSStencil_h

/* The `JS::Stencil` type holds the output of the JS Parser before it is
 * allocated on the GC heap as a `JSScript`. This form may be serialized as
 * part of building a bytecode cache. This `Stencil` is not associated with any
 * particular Realm and may be generated off-main-thread, making it useful for
 * building script loaders.
 */

#include "mozilla/MemoryReporting.h"  // mozilla::MallocSizeOf
#include "mozilla/RefPtr.h"           // RefPtr, already_AddRefed
#include "mozilla/Utf8.h"             // mozilla::Utf8Unit
#include "mozilla/Vector.h"           // mozilla::Vector

#include <stddef.h>  // size_t

#include "jstypes.h"  // JS_PUBLIC_API

#include "js/CompileOptions.h"  // JS::ReadOnlyCompileOptions, JS::InstantiateOptions, JS::DecodeOptions
#include "js/OffThreadScriptCompilation.h"  // JS::OffThreadCompileCallback
#include "js/SourceText.h"                  // JS::SourceText
#include "js/Transcoding.h"  // JS::TranscodeSources, JS::TranscodeBuffer, JS::TranscodeRange

struct JS_PUBLIC_API JSContext;
class JS_PUBLIC_API JSTracer;

// Underlying opaque type.
namespace js {
struct ParseTask;
class FrontendContext;
namespace frontend {
struct CompilationStencil;
struct CompilationGCOutput;
struct CompilationInput;
}  // namespace frontend
}  // namespace js

// ************************************************************************
//   Types
// ************************************************************************

namespace JS {

struct CompilationStorage;

using Stencil = js::frontend::CompilationStencil;
using FrontendContext = js::FrontendContext;

// Temporary storage used during instantiating Stencil.
//
// Off-thread APIs can allocate this instance off main thread, and pass it back
// to the main thread, in order to reduce the main thread allocation.
struct InstantiationStorage {
 private:
  // Owned CompilationGCOutput.
  //
  // This uses raw pointer instead of UniquePtr because CompilationGCOutput
  // is opaque.
  js::frontend::CompilationGCOutput* gcOutput_ = nullptr;

  friend JS_PUBLIC_API JSScript* InstantiateGlobalStencil(
      JSContext* cx, const InstantiateOptions& options, Stencil* stencil,
      InstantiationStorage* storage);

  friend JS_PUBLIC_API JSObject* InstantiateModuleStencil(
      JSContext* cx, const InstantiateOptions& options, Stencil* stencil,
      InstantiationStorage* storage);

  friend JS_PUBLIC_API bool PrepareForInstantiate(
      JS::FrontendContext* fc, JS::CompilationStorage& compileStorage,
      JS::Stencil& stencil, JS::InstantiationStorage& storage);

  friend struct js::ParseTask;

 public:
  InstantiationStorage() = default;
  InstantiationStorage(InstantiationStorage&& other)
      : gcOutput_(other.gcOutput_) {
    other.gcOutput_ = nullptr;
  }

  ~InstantiationStorage();

 private:
  InstantiationStorage(const InstantiationStorage& other) = delete;
  void operator=(const InstantiationStorage& aOther) = delete;

 public:
  bool isValid() const { return !!gcOutput_; }

  void trace(JSTracer* trc);
};

}  // namespace JS

// ************************************************************************
//   Reference Count
// ************************************************************************

namespace JS {

// These non-member functions let us manipulate the ref counts of the opaque
// Stencil type. The RefPtrTraits below calls these for use when using the
// RefPtr type.
JS_PUBLIC_API void StencilAddRef(Stencil* stencil);
JS_PUBLIC_API void StencilRelease(Stencil* stencil);

}  // namespace JS

namespace mozilla {
template <>
struct RefPtrTraits<JS::Stencil> {
  static void AddRef(JS::Stencil* stencil) { JS::StencilAddRef(stencil); }
  static void Release(JS::Stencil* stencil) { JS::StencilRelease(stencil); }
};
}  // namespace mozilla

// ************************************************************************
//   Properties
// ************************************************************************

namespace JS {

// Return true if the stencil relies on external data as a result of XDR
// decoding.
extern JS_PUBLIC_API bool StencilIsBorrowed(Stencil* stencil);

extern JS_PUBLIC_API size_t SizeOfStencil(Stencil* stencil,
                                          mozilla::MallocSizeOf mallocSizeOf);

}  // namespace JS

// ************************************************************************
//   Compilation
// ************************************************************************

namespace JS {

// Compile the source text into a JS::Stencil using the provided options. The
// resulting stencil may be instantiated into any Realm on the current runtime
// and may be used multiple times.
//
// NOTE: On error, a null will be returned and an exception will be set on the
//       JSContext.
extern JS_PUBLIC_API already_AddRefed<Stencil> CompileGlobalScriptToStencil(
    JSContext* cx, const ReadOnlyCompileOptions& options,
    SourceText<mozilla::Utf8Unit>& srcBuf);
extern JS_PUBLIC_API already_AddRefed<Stencil> CompileGlobalScriptToStencil(
    JSContext* cx, const ReadOnlyCompileOptions& options,
    SourceText<char16_t>& srcBuf);

// Compile the source text into a JS::Stencil using "module" parse goal. The
// ECMAScript spec defines special semantics so we use a seperate entry point
// here for clarity. The result is still a JS::Stencil, but should use the
// appropriate instantiate API below.
extern JS_PUBLIC_API already_AddRefed<Stencil> CompileModuleScriptToStencil(
    JSContext* cx, const ReadOnlyCompileOptions& options,
    SourceText<mozilla::Utf8Unit>& srcBuf);
extern JS_PUBLIC_API already_AddRefed<Stencil> CompileModuleScriptToStencil(
    JSContext* cx, const ReadOnlyCompileOptions& options,
    SourceText<char16_t>& srcBuf);

}  // namespace JS

// ************************************************************************
//   Instantiation
// ************************************************************************

namespace JS {

// Instantiate the Stencil into current Realm and return the JSScript.
extern JS_PUBLIC_API JSScript* InstantiateGlobalStencil(
    JSContext* cx, const InstantiateOptions& options, Stencil* stencil,
    InstantiationStorage* storage = nullptr);

// Instantiate a module Stencil and return the associated object. Inside the
// engine this is a js::ModuleObject.
extern JS_PUBLIC_API JSObject* InstantiateModuleStencil(
    JSContext* cx, const InstantiateOptions& options, Stencil* stencil,
    InstantiationStorage* storage = nullptr);

}  // namespace JS

// ************************************************************************
//   Transcoding
// ************************************************************************

namespace JS {

class OffThreadToken;

// Serialize the Stencil into the transcode buffer.
extern JS_PUBLIC_API TranscodeResult EncodeStencil(JSContext* cx,
                                                   Stencil* stencil,
                                                   TranscodeBuffer& buffer);

// Deserialize data and create a new Stencil.
extern JS_PUBLIC_API TranscodeResult DecodeStencil(JSContext* cx,
                                                   const DecodeOptions& options,
                                                   const TranscodeRange& range,
                                                   Stencil** stencilOut);
extern JS_PUBLIC_API TranscodeResult DecodeStencil(JS::FrontendContext* fc,
                                                   const DecodeOptions& options,
                                                   const TranscodeRange& range,
                                                   Stencil** stencilOut);

// Register an encoder on its script source, such that all functions can be
// encoded as they are delazified.
extern JS_PUBLIC_API bool StartIncrementalEncoding(JSContext* cx,
                                                   RefPtr<Stencil>&& stencil);

}  // namespace JS

// ************************************************************************
//   Off-thread compilation/transcoding
// ************************************************************************

namespace JS {

// Start an off-thread task to compile the source text into a JS::Stencil,
// using the provided options.
extern JS_PUBLIC_API OffThreadToken* CompileToStencilOffThread(
    JSContext* cx, const ReadOnlyCompileOptions& options,
    SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback,
    void* callbackData);

extern JS_PUBLIC_API OffThreadToken* CompileToStencilOffThread(
    JSContext* cx, const ReadOnlyCompileOptions& options,
    SourceText<mozilla::Utf8Unit>& srcBuf, OffThreadCompileCallback callback,
    void* callbackData);

// Start an off-thread task to compile the module source text into a
// JS::Stencil, using the provided options.
extern JS_PUBLIC_API OffThreadToken* CompileModuleToStencilOffThread(
    JSContext* cx, const ReadOnlyCompileOptions& options,
    SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback,
    void* callbackData);

extern JS_PUBLIC_API OffThreadToken* CompileModuleToStencilOffThread(
    JSContext* cx, const ReadOnlyCompileOptions& options,
    SourceText<mozilla::Utf8Unit>& srcBuf, OffThreadCompileCallback callback,
    void* callbackData);

// Start an off-thread task to decode stencil.
//
// The start of `buffer` and `cursor` should meet
// IsTranscodingBytecodeAligned and IsTranscodingBytecodeOffsetAligned.
// (This should be handled while encoding).
//
// `buffer` should be alive until the end of `FinishDecodeStencilOffThread`.
extern JS_PUBLIC_API OffThreadToken* DecodeStencilOffThread(
    JSContext* cx, const DecodeOptions& options, const TranscodeBuffer& buffer,
    size_t cursor, OffThreadCompileCallback callback, void* callbackData);

// The start of `range` should meet IsTranscodingBytecodeAligned and
// AlignTranscodingBytecodeOffset.
// (This should be handled while encoding).
//
// `range` should be alive until the end of `FinishDecodeStencilOffThread`.
extern JS_PUBLIC_API OffThreadToken* DecodeStencilOffThread(
    JSContext* cx, const DecodeOptions& options, const TranscodeRange& range,
    OffThreadCompileCallback callback, void* callbackData);

// Start an off-thread task to decode multiple stencils.
//
// The start of `TranscodeSource.range` in `sources` should meet
// IsTranscodingBytecodeAligned and AlignTranscodingBytecodeOffset
//
// `sources` should be alive until the end of
// `FinishDecodeMultiStencilsOffThread`.
extern JS_PUBLIC_API OffThreadToken* DecodeMultiStencilsOffThread(
    JSContext* cx, const DecodeOptions& options, TranscodeSources& sources,
    OffThreadCompileCallback callback, void* callbackData);

// Finish the off-thread task to compile the source text into a JS::Stencil,
// started by JS::CompileToStencilOffThread, and return the result JS::Stencil.
//
// If `options.allocateInstantiationStorage` was true in
// JS::CompileToStencilOffThread, pre-allocated JS::InstantiationStorage
// is returned as `storage` out parameter.
extern JS_PUBLIC_API already_AddRefed<Stencil> FinishOffThreadStencil(
    JSContext* cx, OffThreadToken* token,
    InstantiationStorage* storage = nullptr);

extern JS_PUBLIC_API bool FinishDecodeMultiStencilsOffThread(
    JSContext* cx, OffThreadToken* token,
    mozilla::Vector<RefPtr<Stencil>>* stencils);

// Cancel the off-thread task to compile/decode.
extern JS_PUBLIC_API void CancelOffThreadToken(JSContext* cx,
                                               OffThreadToken* token);

}  // namespace JS

#endif  // js_experimental_JSStencil_h
