blob: d9baedce7c0ed152a85c4acd0f86da4b51b4288f [file]
# Lint-as: python3
"""Core compiler interface."""
# Copyright 2020 The IREE Authors
#
# Licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# TODO(#4131) python>=3.7: Use postponed type annotations.
from enum import Enum
import subprocess
from typing import Any, Dict, List, Optional, Sequence, Union
from .debugging import TempFileSaver
from .tools import *
__all__ = [
"DEFAULT_TESTING_BACKENDS",
"compile_file",
"compile_str",
"CompilerOptions",
"OutputFormat",
]
# Default testing backend for invoking the compiler.
# TODO: Remove these. In the absence of default profiles, though, it is better
# to centralize.
DEFAULT_TESTING_BACKENDS = ["dylib-llvm-aot"]
DEFAULT_TESTING_DRIVER = "dylib"
class InputType(Enum):
"""The type of input pipeline to run prior to the core compiler."""
NONE = "none"
MHLO = "mhlo"
TOSA = "tosa"
@staticmethod
def parse(spec: Union[str, "InputType"]) -> "InputType":
"""Parses or returns an InputType.
Args:
spec: An InputType instance or the case-insensitive name of one of the
enum values.
Returns:
An InputType instance.
"""
if isinstance(spec, InputType):
return spec
spec = spec.upper().replace("-", "_")
if spec not in InputType.__members__:
raise ValueError(f"For input_type= argument, expected one of: "
f"{', '.join(InputType.__members__.keys())}")
return InputType[spec]
class OutputFormat(Enum):
"""The output format of the compiler."""
FLATBUFFER_BINARY = "flatbuffer-binary"
FLATBUFFER_TEXT = "flatbuffer-text"
MLIR_TEXT = "mlir-text"
@staticmethod
def parse(spec: Union[str, "OutputFormat"]) -> "OutputFormat":
"""Parses or returns an OutputFormat.
Args:
spec: An OutputFormat instance or the case-insensitive name of one of
the enum values.
Returns:
An OutputFormat instance.
"""
if isinstance(spec, OutputFormat):
return spec
spec = spec.upper().replace("-", "_")
if spec not in OutputFormat.__members__:
raise ValueError(f"For output_format= argument, expected one of: "
f"{', '.join(OutputFormat.__members__.keys())}")
return OutputFormat[spec]
# TODO(#4131) python>=3.7: Consider using a dataclass.
class CompilerOptions:
"""Options to the compiler backend.
Keyword options:
output_file: Optionally save the compiled binary to a file instead of
returning it.
target_backends: List of str names of target backends to compile into
the binary. The resulting binary will run on targets that match one
or more of the compiled backends.
input_type: The type of input legalization to perform prior to full
compilation. Defaults to none.
output_format: Override the output format. See the OutputFormat enum.
Values can either be an enum value or a case-insensitive name of
the option. Typically used for debugging
extra_args: Optional list of additional arguments to pass to the compiler.
Example: ["--print-ir-after-all"]
optimize: Whether to apply some default high level optimizations (default
True).
output_mlir_debuginfo: Include debuginfo (including paths) in any saved or
returned MLIR.
output_generic_mlir: Use the generic (and more portable) MLIR formatting for
any saved or returned MLIR instead of the per-dialect custom assembly.
extended_diagnostics: Outputs extended information on diagnostics,
potentially outputting very verbosely (defaults to False).
strip_debug_ops: Whether to strip high level operations used to aid
debugging.
strip_source_map: Whether to strip source map information (used to generate
better errors).
strip_symbols: Whether to strip extra symbols not needed for execution
(but which may aid debugging).
crash_reproducer_path: File name to output an MLIR crash dump to if there
is a compiler failure.
enable_tflite_bindings: Support the IREE TFLite runtime bindings API shim.
enable_benchmark: Whether to generate instrumented binaries suitable
for benchmarking.
"""
def __init__(self,
*,
output_file: Optional[str] = None,
target_backends: Sequence[str] = (),
input_type: Union[InputType, str] = InputType.NONE,
output_format: Union[OutputFormat,
str] = OutputFormat.FLATBUFFER_BINARY,
extra_args: Sequence[str] = (),
optimize: bool = True,
output_mlir_debuginfo: bool = True,
output_generic_mlir: bool = False,
extended_diagnostics: bool = False,
strip_debug_ops: bool = False,
strip_source_map: bool = False,
strip_symbols: bool = False,
crash_reproducer_path: Optional[str] = None,
enable_tflite_bindings: bool = False,
enable_benchmark: bool = False):
self.output_file = output_file
self.target_backends = target_backends
self.input_type = InputType.parse(input_type)
self.output_format = OutputFormat.parse(output_format)
self.extra_args = extra_args
self.optimize = optimize
self.output_mlir_debuginfo = output_mlir_debuginfo
self.output_generic_mlir = output_generic_mlir
self.extended_diagnostics = extended_diagnostics
self.strip_debug_ops = strip_debug_ops
self.strip_source_map = strip_source_map
self.strip_symbols = strip_symbols
self.crash_reproducer_path = crash_reproducer_path
self.enable_tflite_bindings = enable_tflite_bindings
self.enable_benchmark = enable_benchmark
def build_compile_command_line(input_file: str, tfs: TempFileSaver,
options: CompilerOptions) -> List[str]:
"""Builds a command line for invoking the compiler.
Args:
input_file: The input file name.
tfs: TempFileSaver.
options: Compiler options.
Returns:
List of strings of command line.
"""
iree_translate = find_tool("iree-translate")
if not options.target_backends:
raise ValueError("Expected a non-empty list for 'target_backends'")
cl = [
iree_translate,
input_file,
f"--iree-input-type={options.input_type.value}",
f"--iree-vm-bytecode-module-output-format={options.output_format.value}",
]
for target_backend in options.target_backends:
cl.append(f"--iree-hal-target-backends={target_backend}")
# Output file.
output_file = tfs.alloc_optional("core-output.bin",
export_as=options.output_file)
if output_file:
cl.append(f"-o={output_file}")
# Translation to perform.
cl.append("--iree-mlir-to-vm-bytecode-module")
# MLIR flags.
if options.output_mlir_debuginfo:
cl.append("--mlir-print-debuginfo")
if options.output_generic_mlir:
cl.append("--mlir-print-op-generic")
if options.extended_diagnostics:
# Note that different tools have different defaults, so be explicit.
cl.append("--mlir-print-op-on-diagnostic=true")
else:
cl.append("--mlir-print-op-on-diagnostic=false")
# Other options to set if specified.
if options.strip_debug_ops:
cl.append("--iree-vm-bytecode-module-strip-debug-ops")
if options.strip_source_map:
cl.append("--iree-vm-bytecode-module-strip-source-map")
if options.strip_symbols:
cl.append("--iree-vm-bytecode-module-strip-symbols")
crash_reproducer_path = tfs.alloc_optional(
"core-reproducer.mlir", export_as=options.crash_reproducer_path)
if crash_reproducer_path:
cl.append(f"--pass-pipeline-crash-reproducer={crash_reproducer_path}")
if options.enable_tflite_bindings:
cl.append("--iree-tflite-bindings-support")
if options.enable_benchmark:
cl.append("--iree-flow-export-benchmark-funcs")
cl.extend(options.extra_args)
return cl
def compile_file(input_file: str, **kwargs):
"""Invokes the IREE compiler on an input file.
Args:
input_file: File containing MLIR assembly to compile.
**kwargs: Keyword arguments corresponding to CompilerOptions.
Returns:
Either a byte buffer of the compiled content or None if output_file
was specified in the options.
"""
with TempFileSaver.implicit() as tfs:
options = CompilerOptions(**kwargs)
cl = build_compile_command_line(input_file, tfs, options)
result = invoke_immediate(cl)
if options.output_file:
return None
return result
def compile_str(input_str: Union[str, bytes], **kwargs):
"""Invokes the IREE compiler with an input string.
Args:
input_str: MLIR assembly to parse/compile (str or bytes).
**kwargs: Keyword arguments corresponding to CompilerOptions.
Returns:
Either a byte buffer of the compiled content or None if output_file
was specified in the options.
"""
with TempFileSaver.implicit() as tfs:
options = CompilerOptions(**kwargs)
cl = build_compile_command_line("-", tfs, options)
input_bytes = input_str.encode("utf-8") if isinstance(input_str,
str) else input_str
result = invoke_immediate(cl, immediate_input=input_bytes)
if options.output_file:
return None
return result