// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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 "compiler.h"
#include "parser.h"      // only for generateChildId()
#include <kj/mutex.h>
#include <kj/arena.h>
#include <kj/vector.h>
#include <kj/debug.h>
#include <capnp/message.h>
#include <map>
#include <set>
#include <unordered_map>
#include "node-translator.h"
#include "md5.h"

namespace capnp {
namespace compiler {

class Compiler::Alias {
public:
  Alias(const Node& parent, const DeclName::Reader& targetName)
      : parent(parent), targetName(targetName) {}

  kj::Maybe<const Node&> getTarget() const;

private:
  const Node& parent;
  DeclName::Reader targetName;
  kj::Lazy<kj::Maybe<const Node&>> target;
};

class Compiler::Node: public NodeTranslator::Resolver {
  // Passes through four states:
  // - Stub:  On initial construction, the Node is just a placeholder object.  Its ID has been
  //     determined, and it is placed in its parent's member table as well as the compiler's
  //     nodes-by-ID table.
  // - Expanded:  Nodes have been constructed for all of this Node's nested children.  This happens
  //     the first time a lookup is performed for one of those children.
  // - Bootstrap:  A NodeTranslator has been built and advanced to the bootstrap phase.
  // - Finished:  A final Schema object has been constructed.

public:
  explicit Node(CompiledModule& module);
  // Create a root node representing the given file.  May

  Node(const Node& parent, const Declaration::Reader& declaration);
  // Create a child node.

  Node(kj::StringPtr name, Declaration::Which kind);
  // Create a dummy node representing a built-in declaration, like "Int32" or "true".

  uint64_t getId() const { return id; }
  Declaration::Which getKind() const { return kind; }

  kj::Maybe<const Node&> lookupMember(kj::StringPtr name) const;
  // Find a direct member of this node with the given name.

  kj::Maybe<const Node&> lookupLexical(kj::StringPtr name) const;
  // Look up the given name first as a member of this Node, then in its parent, and so on, until
  // it is found or there are no more parents to search.

  kj::Maybe<const Node&> lookup(const DeclName::Reader& name) const;
  // Resolve an arbitrary DeclName to a Node.

  kj::Maybe<Schema> getBootstrapSchema() const;
  kj::Maybe<schema::Node::Reader> getFinalSchema() const;

  void traverse(uint eagerness, std::unordered_map<const Node*, uint>& seen) const;
  // Get the final schema for this node, and also possibly traverse the node's children and
  // dependencies to ensure that they are loaded, depending on the mode.

  void addError(kj::StringPtr error) const;
  // Report an error on this Node.

  // implements NodeTranslator::Resolver -----------------------------
  kj::Maybe<ResolvedName> resolve(const DeclName::Reader& name) const override;
  kj::Maybe<Schema> resolveBootstrapSchema(uint64_t id) const override;
  kj::Maybe<schema::Node::Reader> resolveFinalSchema(uint64_t id) const override;
  kj::Maybe<uint64_t> resolveImport(kj::StringPtr name) const override;

private:
  const CompiledModule* module;  // null iff isBuiltin is true
  kj::Maybe<const Node&> parent;

  Declaration::Reader declaration;
  // AST of the declaration parsed from the schema file.  May become invalid once the content
  // state has reached FINISHED.

  uint64_t id;
  // The ID of this node, either taken from the AST or computed based on the parent.  Or, a dummy
  // value, if duplicates were detected.

  kj::StringPtr displayName;
  // Fully-qualified display name for this node.  For files, this is just the file name, otherwise
  // it is "filename:Path.To.Decl".

  Declaration::Which kind;
  // Kind of node.

  bool isBuiltin;
  // Whether this is a bulit-in declaration, like "Int32" or "true".

  uint32_t startByte;
  uint32_t endByte;
  // Start and end byte for reporting general errors.

  struct Content {
    inline Content(): state(STUB) {}

    enum State {
      STUB,
      EXPANDED,
      BOOTSTRAP,
      FINISHED
    };
    State state;
    // Indicates which fields below are valid.  Must update with atomic-release semantics.

    inline bool stateHasReached(State minimumState) const {
      return __atomic_load_n(&state, __ATOMIC_ACQUIRE) >= minimumState;
    }
    inline void advanceState(State newState) {
      __atomic_store_n(&state, newState, __ATOMIC_RELEASE);
    }

    // EXPANDED ------------------------------------

    typedef std::multimap<kj::StringPtr, kj::Own<Node>> NestedNodesMap;
    NestedNodesMap nestedNodes;
    kj::Vector<Node*> orderedNestedNodes;
    // Filled in when lookupMember() is first called.  multimap in case of duplicate member names --
    // we still want to compile them, even if it's an error.

    typedef std::multimap<kj::StringPtr, kj::Own<Alias>> AliasMap;
    AliasMap aliases;
    // The "using" declarations.  These are just links to nodes elsewhere.

    // BOOTSTRAP -----------------------------------

    NodeTranslator* translator;
    // Node translator, allocated in the bootstrap arena.

    kj::Maybe<Schema> bootstrapSchema;
    // The schema built in the bootstrap loader.  Null if the bootstrap loader threw an exception.

    // FINISHED ------------------------------------

    kj::Maybe<Schema> finalSchema;
    // The complete schema as loaded by the compiler's main SchemaLoader.  Null if the final
    // loader threw an exception.

    kj::Array<Schema> auxSchemas;
    // Schemas for all auxiliary nodes built by the NodeTranslator.
  };

  kj::MutexGuarded<Content> content;

  // ---------------------------------------------

  static uint64_t generateId(uint64_t parentId, kj::StringPtr declName,
                             Declaration::Id::Reader declId);
  // Extract the ID from the declaration, or if it has none, generate one based on the name and
  // parent ID.

  static kj::StringPtr joinDisplayName(const kj::Arena& arena, const Node& parent,
                                       kj::StringPtr declName);
  // Join the parent's display name with the child's unqualified name to construct the child's
  // display name.

  const Content& getContent(Content::State minimumState) const;
  // Advances the content to at least the given state and returns it.  Does not lock if the content
  // is already at or past the given state.

  void traverseNodeDependencies(const schema::Node::Reader& schemaNode, uint eagerness,
                                std::unordered_map<const Node*, uint>& seen) const;
  void traverseType(const schema::Type::Reader& type, uint eagerness,
                    std::unordered_map<const Node*, uint>& seen) const;
  void traverseAnnotations(const List<schema::Annotation>::Reader& annotations, uint eagerness,
                           std::unordered_map<const Node*, uint>& seen) const;
  // Helpers for traverse().
};

class Compiler::CompiledModule {
public:
  CompiledModule(const Compiler::Impl& compiler, const Module& parserModule);

  const Compiler::Impl& getCompiler() const { return compiler; }

  const ErrorReporter& getErrorReporter() const { return parserModule; }
  ParsedFile::Reader getParsedFile() const { return content.getReader(); }
  const Node& getRootNode() const { return rootNode; }
  kj::StringPtr getSourceName() const { return parserModule.getSourceName(); }

  kj::Maybe<const CompiledModule&> importRelative(kj::StringPtr importPath) const;

  Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>
      getFileImportTable(Orphanage orphanage) const;

private:
  const Compiler::Impl& compiler;
  const Module& parserModule;
  MallocMessageBuilder contentArena;
  Orphan<ParsedFile> content;
  Node rootNode;
};

class Compiler::Impl: public SchemaLoader::LazyLoadCallback {
public:
  explicit Impl(AnnotationFlag annotationFlag);
  virtual ~Impl() noexcept(false);

  uint64_t add(const Module& module) const;
  kj::Maybe<uint64_t> lookup(uint64_t parent, kj::StringPtr childName) const;
  Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>
      getFileImportTable(const Module& module, Orphanage orphanage) const;
  void eagerlyCompile(uint64_t id, uint eagerness) const;
  const CompiledModule& addInternal(const Module& parsedModule) const;

  struct Workspace {
    // Scratch space where stuff can be allocated while working.  The Workspace is available
    // whenever nodes are actively being compiled, then is destroyed once control exits the
    // compiler.  Note that since nodes are compiled lazily, a new Workspace may have to be
    // constructed in order to compile more nodes later.

    MallocMessageBuilder message;
    Orphanage orphanage;
    // Orphanage for allocating temporary Cap'n Proto objects.

    kj::Arena arena;
    // Arena for allocating temporary native objects.  Note that objects in `arena` may contain
    // pointers into `message` that will be manipulated on destruction, so `arena` must be declared
    // after `message`.

    SchemaLoader bootstrapLoader;
    // Loader used to load bootstrap schemas.  The bootstrap schema nodes are similar to the final
    // versions except that any value expressions which depend on knowledge of other types (e.g.
    // default values for struct fields) are left unevaluated (the values in the schema are empty).
    // These bootstrap schemas can then be plugged into the dynamic API and used to evaluate these
    // remaining values.

    inline explicit Workspace(const SchemaLoader::LazyLoadCallback& loaderCallback)
        : orphanage(message.getOrphanage()),
          bootstrapLoader(loaderCallback) {}
  };

  const kj::Arena& getNodeArena() const { return nodeArena; }
  // Arena where nodes and other permanent objects should be allocated.

  const SchemaLoader& getFinalLoader() const { return finalLoader; }
  // Schema loader containing final versions of schemas.

  const Workspace& getWorkspace() const { return workspace.getAlreadyLockedShared(); }
  // Temporary workspace that can be used to construct bootstrap objects.  We assume that the
  // caller already holds the workspace lock somewhere up the stack.

  inline bool shouldCompileAnnotations() const {
    return annotationFlag == AnnotationFlag::COMPILE_ANNOTATIONS;
  }

  void clearWorkspace();
  // Reset the temporary workspace.

  uint64_t addNode(uint64_t desiredId, Node& node) const;
  // Add the given node to the by-ID map under the given ID.  If another node with the same ID
  // already exists, choose a new one arbitrarily and use that instead.  Return the ID that was
  // finally used.

  kj::Maybe<const Node&> findNode(uint64_t id) const;

  kj::Maybe<const Node&> lookupBuiltin(kj::StringPtr name) const;

  void load(const SchemaLoader& loader, uint64_t id) const override;

private:
  AnnotationFlag annotationFlag;

  kj::Arena nodeArena;
  // Arena used to allocate nodes and other permanent objects.

  SchemaLoader finalLoader;
  // The loader where we put final output of the compiler.

  kj::MutexGuarded<Workspace> workspace;
  // The temporary workspace.

  typedef std::unordered_map<const Module*, kj::Own<CompiledModule>> ModuleMap;
  kj::MutexGuarded<ModuleMap> modules;
  // Map of parser modules to compiler modules.

  typedef std::unordered_map<uint64_t, const Node*> NodeMap;
  kj::MutexGuarded<NodeMap> nodesById;
  // Map of nodes by ID.

  std::map<kj::StringPtr, kj::Own<Node>> builtinDecls;
  // Map of built-in declarations, like "Int32" and "List", which make up the global scope.

  mutable uint32_t nextBogusId = 1000;
  // Counter for assigning bogus IDs to nodes whose real ID is a duplicate.  32-bit so that we
  // can atomically increment it on 32-bit machines.  It will never overflow since that would
  // require compiling at least 2^32 nodes in one process.
};

// =======================================================================================

kj::Maybe<const Compiler::Node&> Compiler::Alias::getTarget() const {
  return target.get([this](kj::SpaceFor<kj::Maybe<const Node&>>& space) {
    return space.construct(parent.lookup(targetName));
  });
}

// =======================================================================================

Compiler::Node::Node(CompiledModule& module)
    : module(&module),
      parent(nullptr),
      declaration(module.getParsedFile().getRoot()),
      id(generateId(0, declaration.getName().getValue(), declaration.getId())),
      displayName(module.getSourceName()),
      kind(declaration.which()),
      isBuiltin(false) {
  auto name = declaration.getName();
  if (name.getValue().size() > 0) {
    startByte = name.getStartByte();
    endByte = name.getEndByte();
  } else {
    startByte = declaration.getStartByte();
    endByte = declaration.getEndByte();
  }

  id = module.getCompiler().addNode(id, *this);
}

Compiler::Node::Node(const Node& parent, const Declaration::Reader& declaration)
    : module(parent.module),
      parent(parent),
      declaration(declaration),
      id(generateId(parent.id, declaration.getName().getValue(), declaration.getId())),
      displayName(joinDisplayName(parent.module->getCompiler().getNodeArena(),
                                  parent, declaration.getName().getValue())),
      kind(declaration.which()),
      isBuiltin(false) {
  auto name = declaration.getName();
  if (name.getValue().size() > 0) {
    startByte = name.getStartByte();
    endByte = name.getEndByte();
  } else {
    startByte = declaration.getStartByte();
    endByte = declaration.getEndByte();
  }

  id = module->getCompiler().addNode(id, *this);
}

Compiler::Node::Node(kj::StringPtr name, Declaration::Which kind)
    : module(nullptr),
      parent(nullptr),
      id(0),
      displayName(name),
      kind(kind),
      isBuiltin(true),
      startByte(0),
      endByte(0) {}

uint64_t Compiler::Node::generateId(uint64_t parentId, kj::StringPtr declName,
                                    Declaration::Id::Reader declId) {
  if (declId.isUid()) {
    return declId.getUid().getValue();
  }

  return generateChildId(parentId, declName);
}

kj::StringPtr Compiler::Node::joinDisplayName(
    const kj::Arena& arena, const Node& parent, kj::StringPtr declName) {
  kj::ArrayPtr<char> result = arena.allocateArray<char>(
      parent.displayName.size() + declName.size() + 2);

  size_t separatorPos = parent.displayName.size();
  memcpy(result.begin(), parent.displayName.begin(), separatorPos);
  result[separatorPos] = parent.parent == nullptr ? ':' : '.';
  memcpy(result.begin() + separatorPos + 1, declName.begin(), declName.size());
  result[result.size() - 1] = '\0';
  return kj::StringPtr(result.begin(), result.size() - 1);
}

const Compiler::Node::Content& Compiler::Node::getContent(Content::State minimumState) const {
  KJ_REQUIRE(!isBuiltin, "illegal method call for built-in declaration");

  if (content.getWithoutLock().stateHasReached(minimumState)) {
    return content.getWithoutLock();
  }

  auto locked = content.lockExclusive();

  switch (locked->state) {
    case Content::STUB: {
      if (minimumState <= Content::STUB) break;

      // Expand the child nodes.
      auto& arena = module->getCompiler().getNodeArena();

      for (auto nestedDecl: declaration.getNestedDecls()) {
        switch (nestedDecl.which()) {
          case Declaration::FILE:
          case Declaration::CONST:
          case Declaration::ANNOTATION:
          case Declaration::ENUM:
          case Declaration::STRUCT:
          case Declaration::INTERFACE: {
            kj::Own<Node> subNode = arena.allocateOwn<Node>(*this, nestedDecl);
            kj::StringPtr name = nestedDecl.getName().getValue();
            locked->orderedNestedNodes.add(subNode);
            locked->nestedNodes.insert(std::make_pair(name, kj::mv(subNode)));
            break;
          }

          case Declaration::USING: {
            kj::Own<Alias> alias = arena.allocateOwn<Alias>(
                *this, nestedDecl.getUsing().getTarget());
            kj::StringPtr name = nestedDecl.getName().getValue();
            locked->aliases.insert(std::make_pair(name, kj::mv(alias)));
            break;
          }
          case Declaration::ENUMERANT:
          case Declaration::FIELD:
          case Declaration::UNION:
          case Declaration::GROUP:
          case Declaration::METHOD:
          case Declaration::NAKED_ID:
          case Declaration::NAKED_ANNOTATION:
            // Not a node.  Skip.
            break;
          default:
            KJ_FAIL_ASSERT("unknown declaration type", nestedDecl);
            break;
        }
      }

      locked->advanceState(Content::EXPANDED);
      // no break
    }

    case Content::EXPANDED: {
      if (minimumState <= Content::EXPANDED) break;

      // Construct the NodeTranslator.
      auto& workspace = module->getCompiler().getWorkspace();

      auto schemaNode = workspace.orphanage.newOrphan<schema::Node>();
      auto builder = schemaNode.get();
      builder.setId(id);
      builder.setDisplayName(displayName);
      KJ_IF_MAYBE(p, parent) {
        builder.setScopeId(p->id);
      }

      auto nestedNodes = builder.initNestedNodes(locked->orderedNestedNodes.size());
      auto nestedIter = nestedNodes.begin();
      for (auto node: locked->orderedNestedNodes) {
        nestedIter->setName(node->declaration.getName().getValue());
        nestedIter->setId(node->id);
        ++nestedIter;
      }

      locked->translator = &workspace.arena.allocate<NodeTranslator>(
          *this, module->getErrorReporter(), declaration, kj::mv(schemaNode),
          module->getCompiler().shouldCompileAnnotations());
      KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&](){
        auto nodeSet = locked->translator->getBootstrapNode();
        for (auto& auxNode: nodeSet.auxNodes) {
          workspace.bootstrapLoader.loadOnce(auxNode);
        }
        locked->bootstrapSchema = workspace.bootstrapLoader.loadOnce(nodeSet.node);
      })) {
        locked->bootstrapSchema = nullptr;
        // Only bother to report validation failures if we think we haven't seen any errors.
        // Otherwise we assume that the errors caused the validation failure.
        if (!module->getErrorReporter().hadErrors()) {
          addError(kj::str("Internal compiler bug: Bootstrap schema failed validation:\n",
                           *exception));
        }
      }

      // If the Workspace is destroyed while this Node is still in the BOOTSTRAP state,
      // revert it to the EXPANDED state, because the NodeTranslator is no longer valid in this
      // case.
      Content* contentPtr = locked.get();
      workspace.arena.copy(kj::defer([contentPtr]() {
        contentPtr->bootstrapSchema = nullptr;
        if (contentPtr->state == Content::BOOTSTRAP) {
          contentPtr->state = Content::EXPANDED;
        }
      }));

      locked->advanceState(Content::BOOTSTRAP);
      // no break
    }

    case Content::BOOTSTRAP: {
      if (minimumState <= Content::BOOTSTRAP) break;

      // Create the final schema.
      KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&](){
        auto nodeSet = locked->translator->finish();
        locked->auxSchemas = KJ_MAP(nodeSet.auxNodes, auxNode) {
          return module->getCompiler().getFinalLoader().loadOnce(auxNode);
        };
        locked->finalSchema = module->getCompiler().getFinalLoader().loadOnce(nodeSet.node);
      })) {
        locked->finalSchema = nullptr;

        // Only bother to report validation failures if we think we haven't seen any errors.
        // Otherwise we assume that the errors caused the validation failure.
        if (!module->getErrorReporter().hadErrors()) {
          addError(kj::str("Internal compiler bug: Schema failed validation:\n",
                           *exception));
        }
      }

      locked->advanceState(Content::FINISHED);
      // no break
    }

    case Content::FINISHED:
      break;
  }

  return *locked;
}

kj::Maybe<const Compiler::Node&> Compiler::Node::lookupMember(kj::StringPtr name) const {
  if (isBuiltin) return nullptr;

  auto& content = getContent(Content::EXPANDED);
  {
    auto iter = content.nestedNodes.find(name);
    if (iter != content.nestedNodes.end()) {
      return *iter->second;
    }
  }
  {
    auto iter = content.aliases.find(name);
    if (iter != content.aliases.end()) {
      return iter->second->getTarget();
    }
  }
  return nullptr;
}

kj::Maybe<const Compiler::Node&> Compiler::Node::lookupLexical(kj::StringPtr name) const {
  KJ_REQUIRE(!isBuiltin, "illegal method call for built-in declaration");

  auto result = lookupMember(name);
  if (result == nullptr) {
    KJ_IF_MAYBE(p, parent) {
      result = p->lookupLexical(name);
    } else {
      result = module->getCompiler().lookupBuiltin(name);
    }
  }
  return result;
}

kj::Maybe<const Compiler::Node&> Compiler::Node::lookup(const DeclName::Reader& name) const {
  KJ_REQUIRE(!isBuiltin, "illegal method call for built-in declaration");

  const Node* node = nullptr;

  auto base = name.getBase();
  switch (base.which()) {
    case DeclName::Base::ABSOLUTE_NAME: {
      auto absoluteName = base.getAbsoluteName();
      KJ_IF_MAYBE(n, module->getRootNode().lookupMember(absoluteName.getValue())) {
        node = &*n;
      } else {
        module->getErrorReporter().addErrorOn(
            absoluteName, kj::str("not defined: ", absoluteName.getValue()));
        return nullptr;
      }
      break;
    }
    case DeclName::Base::RELATIVE_NAME: {
      auto relativeName = base.getRelativeName();
      KJ_IF_MAYBE(n, lookupLexical(relativeName.getValue())) {
        node = &*n;
      } else {
        module->getErrorReporter().addErrorOn(
            relativeName, kj::str("not defined: ", relativeName.getValue()));
        return nullptr;
      }
      break;
    }
    case DeclName::Base::IMPORT_NAME: {
      auto importName = base.getImportName();
      KJ_IF_MAYBE(m, module->importRelative(importName.getValue())) {
        node = &m->getRootNode();
      } else {
        module->getErrorReporter().addErrorOn(
            importName, kj::str("import failed: ", importName.getValue()));
        return nullptr;
      }
      break;
    }
  }

  KJ_ASSERT(node != nullptr);

  for (auto partName: name.getMemberPath()) {
    KJ_IF_MAYBE(member, node->lookupMember(partName.getValue())) {
      node = &*member;
    } else {
      module->getErrorReporter().addErrorOn(
          partName, kj::str("no such member: ", partName.getValue()));
      return nullptr;
    }
  }

  return *node;
}

kj::Maybe<Schema> Compiler::Node::getBootstrapSchema() const {
  auto& content = getContent(Content::BOOTSTRAP);

  if (__atomic_load_n(&content.state, __ATOMIC_ACQUIRE) == Content::FINISHED &&
      content.bootstrapSchema == nullptr) {
    // The bootstrap schema was discarded.  Copy it from the final schema.
    // (We can't just return the final schema because using it could trigger schema loader
    // callbacks that would deadlock.)
    KJ_IF_MAYBE(finalSchema, content.finalSchema) {
      return module->getCompiler().getWorkspace().bootstrapLoader.loadOnce(finalSchema->getProto());
    } else {
      return nullptr;
    }
  } else {
    return content.bootstrapSchema;
  }
}
kj::Maybe<schema::Node::Reader> Compiler::Node::getFinalSchema() const {
  return getContent(Content::FINISHED).finalSchema.map(
      [](const Schema& schema) { return schema.getProto(); });
}

void Compiler::Node::traverse(uint eagerness, std::unordered_map<const Node*, uint>& seen) const {
  uint& slot = seen[this];
  if ((slot & eagerness) == eagerness) {
    // We've already covered this node.
    return;
  }
  slot |= eagerness;

  KJ_IF_MAYBE(schema, getFinalSchema()) {
    if (eagerness / DEPENDENCIES != 0) {
      // For traversing dependencies, discard the bits lower than DEPENDENCIES and replace
      // them with the bits above DEPENDENCIES shifted over.
      uint newEagerness = (eagerness & ~(DEPENDENCIES - 1)) | (eagerness / DEPENDENCIES);

      traverseNodeDependencies(*schema, newEagerness, seen);
      for (auto& aux: getContent(Content::FINISHED).auxSchemas) {
        traverseNodeDependencies(aux.getProto(), newEagerness, seen);
      }
    }
  }

  if (eagerness & PARENTS) {
    KJ_IF_MAYBE(p, parent) {
      p->traverse(eagerness, seen);
    }
  }

  if (eagerness & CHILDREN) {
    for (auto& child: getContent(Content::EXPANDED).nestedNodes) {
      child.second->traverse(eagerness, seen);
    }
  }
}

void Compiler::Node::traverseNodeDependencies(
    const schema::Node::Reader& schemaNode, uint eagerness,
    std::unordered_map<const Node*, uint>& seen) const {
  switch (schemaNode.which()) {
    case schema::Node::STRUCT:
      for (auto field: schemaNode.getStruct().getFields()) {
        switch (field.which()) {
          case schema::Field::NON_GROUP:
            traverseType(field.getNonGroup().getType(), eagerness, seen);
            break;
          case schema::Field::GROUP:
            // Aux node will be scanned later.
            break;
        }

        traverseAnnotations(field.getAnnotations(), eagerness, seen);
      }
      break;

    case schema::Node::ENUM:
      for (auto enumerant: schemaNode.getEnum().getEnumerants()) {
        traverseAnnotations(enumerant.getAnnotations(), eagerness, seen);
      }
      break;

    case schema::Node::INTERFACE:
      for (auto method: schemaNode.getInterface().getMethods()) {
        for (auto param: method.getParams()) {
          traverseType(param.getType(), eagerness, seen);
          traverseAnnotations(param.getAnnotations(), eagerness, seen);
        }
        traverseType(method.getReturnType(), eagerness, seen);
        traverseAnnotations(method.getAnnotations(), eagerness, seen);
      }
      break;

    default:
      break;
  }

  traverseAnnotations(schemaNode.getAnnotations(), eagerness, seen);
}

void Compiler::Node::traverseType(const schema::Type::Reader& type, uint eagerness,
                                  std::unordered_map<const Node*, uint>& seen) const {
  uint64_t id = 0;
  switch (type.which()) {
    case schema::Type::STRUCT:
      id = type.getStruct();
      break;
    case schema::Type::ENUM:
      id = type.getEnum();
      break;
    case schema::Type::INTERFACE:
      id = type.getInterface();
      break;
    case schema::Type::LIST:
      traverseType(type.getList(), eagerness, seen);
      return;
    default:
      return;
  }

  KJ_IF_MAYBE(node, module->getCompiler().findNode(id)) {
    node->traverse(eagerness, seen);
  } else {
    KJ_FAIL_ASSERT("Dependency ID not present in compiler?", id);
  }
}

void Compiler::Node::traverseAnnotations(const List<schema::Annotation>::Reader& annotations,
                                         uint eagerness,
                                         std::unordered_map<const Node*, uint>& seen) const {
  for (auto annotation: annotations) {
    KJ_IF_MAYBE(node, module->getCompiler().findNode(annotation.getId())) {
      node->traverse(eagerness, seen);
    }
  }
}


void Compiler::Node::addError(kj::StringPtr error) const {
  module->getErrorReporter().addError(startByte, endByte, error);
}

kj::Maybe<NodeTranslator::Resolver::ResolvedName> Compiler::Node::resolve(
    const DeclName::Reader& name) const {
  return lookup(name).map([](const Node& node) {
    return ResolvedName { node.id, node.kind };
  });
}

kj::Maybe<Schema> Compiler::Node::resolveBootstrapSchema(uint64_t id) const {
  KJ_IF_MAYBE(node, module->getCompiler().findNode(id)) {
    return node->getBootstrapSchema();
  } else {
    KJ_FAIL_REQUIRE("Tried to get schema for ID we haven't seen before.");
  }
}

kj::Maybe<schema::Node::Reader> Compiler::Node::resolveFinalSchema(uint64_t id) const {
  KJ_IF_MAYBE(node, module->getCompiler().findNode(id)) {
    return node->getFinalSchema();
  } else {
    KJ_FAIL_REQUIRE("Tried to get schema for ID we haven't seen before.");
  }
}

kj::Maybe<uint64_t> Compiler::Node::resolveImport(kj::StringPtr name) const {
  KJ_IF_MAYBE(m, module->importRelative(name)) {
    return m->getRootNode().getId();
  } else {
    return nullptr;
  }
}

// =======================================================================================

Compiler::CompiledModule::CompiledModule(
    const Compiler::Impl& compiler, const Module& parserModule)
    : compiler(compiler), parserModule(parserModule),
      content(parserModule.loadContent(contentArena.getOrphanage())),
      rootNode(*this) {}

kj::Maybe<const Compiler::CompiledModule&> Compiler::CompiledModule::importRelative(
    kj::StringPtr importPath) const {
  return parserModule.importRelative(importPath).map(
      [this](const Module& module) -> const Compiler::CompiledModule& {
        return compiler.addInternal(module);
      });
}

static void findImports(DeclName::Reader name, std::set<kj::StringPtr>& output) {
  if (name.getBase().isImportName()) {
    output.insert(name.getBase().getImportName().getValue());
  }
}

static void findImports(TypeExpression::Reader type, std::set<kj::StringPtr>& output) {
  findImports(type.getName(), output);
  for (auto param: type.getParams()) {
    findImports(param, output);
  }
}

static void findImports(Declaration::Reader decl, std::set<kj::StringPtr>& output) {
  switch (decl.which()) {
    case Declaration::USING:
      findImports(decl.getUsing().getTarget(), output);
      break;
    case Declaration::CONST:
      findImports(decl.getConst().getType(), output);
      break;
    case Declaration::FIELD:
      findImports(decl.getField().getType(), output);
      break;
    case Declaration::METHOD: {
      auto method = decl.getMethod();
      for (auto param: method.getParams()) {
        findImports(param.getType(), output);
        for (auto ann: param.getAnnotations()) {
          findImports(ann.getName(), output);
        }
      }
      if (method.getReturnType().isExpression()) {
        findImports(method.getReturnType().getExpression(), output);
      }
      break;
    }
    default:
      break;
  }

  for (auto ann: decl.getAnnotations()) {
    findImports(ann.getName(), output);
  }

  for (auto nested: decl.getNestedDecls()) {
    findImports(nested, output);
  }
}

Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>
    Compiler::CompiledModule::getFileImportTable(Orphanage orphanage) const {
  std::set<kj::StringPtr> importNames;
  findImports(content.getReader().getRoot(), importNames);

  auto result = orphanage.newOrphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>(
      importNames.size());
  auto builder = result.get();

  uint i = 0;
  for (auto name: importNames) {
    // We presumably ran this import before, so it shouldn't throw now.
    auto entry = builder[i];
    entry.setId(KJ_ASSERT_NONNULL(importRelative(name)).rootNode.getId());
    entry.setName(name);
  }

  return result;
}

// =======================================================================================

Compiler::Impl::Impl(AnnotationFlag annotationFlag)
    : annotationFlag(annotationFlag), finalLoader(*this), workspace(*this) {
  // Reflectively interpret the members of Declaration.body.  Any member prefixed by "builtin"
  // defines a builtin declaration visible in the global scope.

  StructSchema declSchema = Schema::from<Declaration>();
  for (auto field: declSchema.getFields()) {
    auto fieldProto = field.getProto();
    if (fieldProto.hasDiscriminantValue()) {
      auto name = fieldProto.getName();
      if (name.startsWith("builtin")) {
        kj::StringPtr symbolName = name.slice(strlen("builtin"));
        builtinDecls[symbolName] = nodeArena.allocateOwn<Node>(
            symbolName, static_cast<Declaration::Which>(fieldProto.getDiscriminantValue()));
      }
    }
  }
}

Compiler::Impl::~Impl() noexcept(false) {}

void Compiler::Impl::clearWorkspace() {
  auto lock = workspace.lockExclusive();

  // Make sure we reconstruct the workspace even if destroying it throws an exception.
  KJ_DEFER(kj::ctor(*lock, *this));
  kj::dtor(*lock);
}

const Compiler::CompiledModule& Compiler::Impl::addInternal(const Module& parsedModule) const {
  auto locked = modules.lockExclusive();

  kj::Own<CompiledModule>& slot = (*locked)[&parsedModule];
  if (slot.get() == nullptr) {
    slot = kj::heap<CompiledModule>(*this, parsedModule);
  }

  return *slot;
}

uint64_t Compiler::Impl::addNode(uint64_t desiredId, Node& node) const {
  auto lock = nodesById.lockExclusive();
  for (;;) {
    auto insertResult = lock->insert(std::make_pair(desiredId, &node));
    if (insertResult.second) {
      return desiredId;
    }

    // Only report an error if this ID is not bogus.  Actual IDs specified in the original source
    // code are required to have the upper bit set.  Anything else must have been manufactured
    // at some point to cover up an error.
    if (desiredId & (1ull << 63)) {
      node.addError(kj::str("Duplicate ID @0x", kj::hex(desiredId), "."));
      insertResult.first->second->addError(
          kj::str("ID @0x", kj::hex(desiredId), " originally used here."));
    }

    // Assign a new bogus ID.
    desiredId = __atomic_fetch_add(&nextBogusId, 1, __ATOMIC_RELAXED);
  }
}

kj::Maybe<const Compiler::Node&> Compiler::Impl::findNode(uint64_t id) const {
  auto lock = nodesById.lockShared();
  auto iter = lock->find(id);
  if (iter == lock->end()) {
    return nullptr;
  } else {
    return *iter->second;
  }
}

kj::Maybe<const Compiler::Node&> Compiler::Impl::lookupBuiltin(kj::StringPtr name) const {
  auto iter = builtinDecls.find(name);
  if (iter == builtinDecls.end()) {
    return nullptr;
  } else {
    return *iter->second;
  }
}

uint64_t Compiler::Impl::add(const Module& module) const {
  return addInternal(module).getRootNode().getId();
}

kj::Maybe<uint64_t> Compiler::Impl::lookup(uint64_t parent, kj::StringPtr childName) const {
  // Looking up members does not use the workspace, so we don't need to lock it.
  KJ_IF_MAYBE(parentNode, findNode(parent)) {
    KJ_IF_MAYBE(child, parentNode->lookupMember(childName)) {
      return child->getId();
    } else {
      return nullptr;
    }
  } else {
    KJ_FAIL_REQUIRE("lookup()s parameter 'parent' must be a known ID.", parent);
  }
}

Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>
    Compiler::Impl::getFileImportTable(const Module& module, Orphanage orphanage) const {
  return addInternal(module).getFileImportTable(orphanage);
}

void Compiler::Impl::eagerlyCompile(uint64_t id, uint eagerness) const {
  KJ_IF_MAYBE(node, findNode(id)) {
    auto lock = this->workspace.lockShared();
    std::unordered_map<const Node*, uint> seen;
    node->traverse(eagerness, seen);
  } else {
    KJ_FAIL_REQUIRE("id did not come from this Compiler.", id);
  }
}

void Compiler::Impl::load(const SchemaLoader& loader, uint64_t id) const {
  KJ_IF_MAYBE(node, findNode(id)) {
    if (&loader == &finalLoader) {
      auto lock = this->workspace.lockShared();
      node->getFinalSchema();
    } else {
      // Must be the bootstrap loader.  Workspace should already be locked.
      this->workspace.getAlreadyLockedShared();
      node->getBootstrapSchema();
    }
  }
}

// =======================================================================================

Compiler::Compiler(AnnotationFlag annotationFlag): impl(kj::heap<Impl>(annotationFlag)) {}
Compiler::~Compiler() noexcept(false) {}

uint64_t Compiler::add(const Module& module) const {
  return impl->add(module);
}

kj::Maybe<uint64_t> Compiler::lookup(uint64_t parent, kj::StringPtr childName) const {
  return impl->lookup(parent, childName);
}

Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>
    Compiler::getFileImportTable(const Module& module, Orphanage orphanage) const {
  return impl->getFileImportTable(module, orphanage);
}

void Compiler::eagerlyCompile(uint64_t id, uint eagerness) const {
  return impl->eagerlyCompile(id, eagerness);
}

const SchemaLoader& Compiler::getLoader() const {
  return impl->getFinalLoader();
}

void Compiler::clearWorkspace() {
  impl->clearWorkspace();
}

}  // namespace compiler
}  // namespace capnp