// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.

package com.google.devtools.build.lib.rules.cpp;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * LTOBackendArtifacts represents a set of artifacts for a single ThinLTO backend compile.
 *
 * <p>ThinLTO expands the traditional 2 step compile (N x compile .cc, 1x link (N .o files) into a 4
 * step process:
 *
 * <ul>
 * <li>1. Bitcode generation (N times). This is produces intermediate LLVM bitcode from a source
 *     file. For this product, it reuses the .o extension.
 * <li>2. Indexing (once on N files). This takes all bitcode .o files, and for each .o file, it
 *     decides from which other .o files symbols can be inlined. In addition, it generates an index
 *     for looking up these symbols, and an imports file for identifying new input files for each
 *     step 3 {@link LTOBackendAction}.
 * <li>3. Backend compile (N times). This is the traditional compilation, and uses the same command
 *     line as the Bitcode generation in 1). Since the compiler has many bit code files available,
 *     it can inline functions and propagate constants across .o files. This step is costly, as it
 *     will do traditional optimization. The result is a .lto.o file, a traditional ELF object file.
 * <li>4. Backend link (once). This is the traditional link, and produces the final executable.
 * </ul>
 */
public final class LTOBackendArtifacts {
  // A file containing mapping of symbol => bitcode file containing the symbol.
  private final Artifact index;

  // The bitcode file which is the input of the compile.
  private final Artifact bitcodeFile;

  // A file containing a list of bitcode files necessary to run the backend step. Currently
  // unused.
  private final Artifact imports;

  // The result of executing the above command line, an ELF object file.
  private final Artifact objectFile;

  // A map of all of the bitcode files. This is the universe from which the .imports file
  // distills its lists.  The map is the same across all LTOBackendArtifacts of a given
  // binary.
  private final Map<PathFragment, Artifact> bitcodeFiles;

  // Command line arguments to apply to back-end compile action, typically from
  // the feature configuration and user-provided linkopts.
  private List<String> commandLine;

  LTOBackendArtifacts(
      PathFragment ltoOutputRootPrefix,
      Artifact bitcodeFile,
      Map<PathFragment, Artifact> allBitCodeFiles,
      RuleContext ruleContext,
      BuildConfiguration configuration,
      CppLinkAction.LinkArtifactFactory linkArtifactFactory) {
    this.bitcodeFile = bitcodeFile;
    PathFragment obj = ltoOutputRootPrefix.getRelative(bitcodeFile.getRootRelativePath());

    objectFile = linkArtifactFactory.create(ruleContext, configuration, obj);
    imports = linkArtifactFactory.create(
        ruleContext, configuration, FileSystemUtils.appendExtension(obj, ".imports"));
    index = linkArtifactFactory.create(
        ruleContext, configuration, FileSystemUtils.appendExtension(obj, ".thinlto.bc"));

    bitcodeFiles = allBitCodeFiles;
  }

  public Artifact getObjectFile() {
    return objectFile;
  }

  public Artifact getBitcodeFile() {
    return bitcodeFile;
  }

  public void addIndexingOutputs(ImmutableList.Builder<Artifact> builder) {
    builder.add(imports);
    builder.add(index);
  }

  public void setCommandLine(List<String> cmdLine) {
    commandLine = cmdLine;
  }

  public void scheduleLTOBackendAction(
      RuleContext ruleContext,
      FeatureConfiguration featureConfiguration,
      boolean usePic,
      boolean generateDwo) {
    LTOBackendAction.Builder builder = new LTOBackendAction.Builder();
    builder.addImportsInfo(bitcodeFiles, imports);

    builder.addInput(bitcodeFile);
    builder.addInput(index);
    builder.addTransitiveInputs(CppHelper.getToolchain(ruleContext).getCompile());

    builder.addOutput(objectFile);

    builder.setProgressMessage("LTO Backend Compile " + objectFile.getFilename());
    builder.setMnemonic("CcLtoBackendCompile");

    // The command-line doesn't specify the full path to clang++, so we set it in the
    // environment.
    CppConfiguration cppConfiguration = ruleContext.getFragment(CppConfiguration.class);

    PathFragment compiler = cppConfiguration.getCppExecutable();

    builder.setExecutable(compiler);
    Variables.Builder buildVariablesBuilder = new Variables.Builder();
    buildVariablesBuilder.addStringVariable("thinlto_index", index.getExecPath().toString());
    // The output from the LTO backend step is a native object file.
    buildVariablesBuilder.addStringVariable(
        "thinlto_output_object_file", objectFile.getExecPath().toString());
    // The input to the LTO backend step is the bitcode file.
    buildVariablesBuilder.addStringVariable(
        "thinlto_input_bitcode_file", bitcodeFile.getExecPath().toString());
    Artifact autoFdoProfile = CppHelper.getFdoSupport(ruleContext).buildProfileForLtoBackend(
        featureConfiguration, buildVariablesBuilder, ruleContext);
    if (autoFdoProfile != null) {
      builder.addInput(autoFdoProfile);
    }

    if (generateDwo) {
      Artifact dwoFile = ruleContext.getRelatedArtifact(objectFile.getRootRelativePath(), ".dwo");
      builder.addOutput(dwoFile);
      buildVariablesBuilder.addStringVariable(
          "per_object_debug_info_file", dwoFile.getExecPathString());
    }

    Variables buildVariables = buildVariablesBuilder.build();
    List<String> execArgs = new ArrayList<>();
    execArgs.addAll(featureConfiguration.getCommandLine("lto-backend", buildVariables));
    if (usePic) {
      execArgs.add("-fPIC");
    }
    execArgs.addAll(commandLine);
    builder.addExecutableArguments(execArgs);

    ruleContext.registerAction(builder.build(ruleContext));
  }
}
