|  | // Copyright 2019 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 | 
|  |  | 
|  | // IREE source.mlir/mlirbc -> execution output runner. | 
|  | // This is meant to be called from LIT for FileCheck tests or as a developer | 
|  | // tool to emulate what an online compiler does. It tries to match the interface | 
|  | // of iree-compile so it's easy to run tests or approximate an | 
|  | // `iree-compile | iree-run-module` sequence. If you want a more generalized | 
|  | // runner for standalone precompiled IREE modules use iree-run-module instead. | 
|  | // | 
|  | // If there's a single exported function that will be executed and if there are | 
|  | // multiple functions --function= can be used to specify which is executed. | 
|  | // Function inputs can be provided with --input=. Results from the executed | 
|  | // function will be printed to stdout for checking or can be written to files | 
|  | // with --output=. | 
|  | // | 
|  | // Similar to iree-run-module the --device= flag can be used to specify which | 
|  | // drivers and devices should be used to execute the function. The tool will | 
|  | // try to infer which iree-compile flags are required for the devices used but | 
|  | // this can be overridden by passing the --iree-hal-target-device= and related | 
|  | // flags explicitly. Likewise if only the target backend is specified the | 
|  | // devices to use will be inferred unless explicitly specified. | 
|  | // | 
|  | // Example usage to compile and run with CUDA: | 
|  | // $ iree-run-mlir --device=cuda://0 file.mlir | 
|  | // or to compile with the LLVM CPU backend and run with the local-task driver: | 
|  | // $ iree-run-mlir file.mlir \ | 
|  | //       --Xcompiler,iree-hal-target-device=hip --device=hip:0 | 
|  | // | 
|  | // Example usage in a lit test: | 
|  | //   // RUN: iree-run-mlir --device= %s --function=foo --input=2xf32=2,3 | \ | 
|  | //   // RUN:   FileCheck %s | 
|  | //   // CHECK-LABEL: @foo | 
|  | //   // CHECK: 2xf32=[2 3] | 
|  | //   func.func @foo(%arg0: tensor<2xf32>) -> tensor<2xf32> { | 
|  | //     return %arg0 : tensor<2xf32> | 
|  | //   } | 
|  | // | 
|  | // Command line arguments are handled by LLVM's parser by default but -- can be | 
|  | // used to separate the compiler flags from the runtime flags, such as: | 
|  | // $ iree-run-mlir source.mlir --device=local-task -- \ | 
|  | //       --iree-hal-target-device=local \ | 
|  | //       --iree-hal-local-target-device-backends=llvm-cpu | 
|  | // | 
|  | // In addition compiler/runtime flags can be passed in any order by prefixing | 
|  | // them with --Xcompiler or --Xruntime like `--Xruntime,device=local-task` or | 
|  | // `--Xruntime --device=local-task`. | 
|  |  | 
|  | #include <cstdio> | 
|  | #include <cstring> | 
|  | #include <functional> | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <string_view> | 
|  | #include <tuple> | 
|  | #include <type_traits> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "iree/base/api.h" | 
|  | #include "iree/base/internal/flags.h" | 
|  | #include "iree/compiler/embedding_api.h" | 
|  | #include "iree/hal/api.h" | 
|  | #include "iree/tooling/context_util.h" | 
|  | #include "iree/tooling/device_util.h" | 
|  | #include "iree/tooling/run_module.h" | 
|  | #include "iree/vm/api.h" | 
|  |  | 
|  | namespace iree { | 
|  | namespace { | 
|  |  | 
|  | // Polyfill for std::string_view::starts_with, coming in C++ 20. | 
|  | // https://en.cppreference.com/w/cpp/string/basic_string_view/starts_with | 
|  | bool starts_with(std::string_view prefix, std::string_view in_str) { | 
|  | return in_str.size() >= prefix.size() && | 
|  | in_str.compare(0, prefix.size(), prefix) == 0; | 
|  | } | 
|  |  | 
|  | // Tries to guess a default device name from the |target_backend| when possible. | 
|  | // Users are still able to override this by passing in --device= flags. | 
|  | std::string InferDefaultDeviceFromTargetBackend( | 
|  | std::string_view target_backend) { | 
|  | if (target_backend == "" || target_backend == "vmvx-inline") { | 
|  | // Plain VM or vmvx-inline targets do not need a HAL device. | 
|  | return ""; | 
|  | } else if (target_backend == "llvm-cpu" || target_backend == "vmvx") { | 
|  | // Locally-executable targets default to the multithreaded task system | 
|  | // driver; users can override by specifying --device=local-sync instead. | 
|  | return "local-task"; | 
|  | } | 
|  | // Many other backends use the `driver-pipeline` naming like `vulkan-spirv` | 
|  | // and we try that; device creation will fail if it's a bad guess. | 
|  | size_t dash = target_backend.find('-'); | 
|  | if (dash == std::string::npos) { | 
|  | return std::string(target_backend); | 
|  | } else { | 
|  | return std::string(target_backend.substr(0, dash)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Tries to guess a target backend from the given |device_uri| when possible. | 
|  | // Returns empty string if no backend is required or one could not be inferred. | 
|  | std::string InferTargetBackendFromDevice(iree_string_view_t device_uri) { | 
|  | // Get the driver name from URIs in the `driver://...` form. | 
|  | iree_string_view_t driver = iree_string_view_empty(); | 
|  | iree_string_view_split(device_uri, ':', &driver, nullptr); | 
|  | if (iree_string_view_is_empty(driver)) { | 
|  | // Plain VM or vmvx-inline targets do not need a HAL device. | 
|  | return ""; | 
|  | } else if (iree_string_view_starts_with(driver, IREE_SV("local-"))) { | 
|  | // Locally-executable devices default to the llvm-cpu target as that's | 
|  | // usually what people want for CPU execution; users can override by | 
|  | // specifying --iree-hal-local-target-device-backends=vmvx instead. | 
|  | return "llvm-cpu"; | 
|  | } | 
|  | // Many other backends have aliases that allow using the driver name. If there | 
|  | // are multiple pipelines available whatever the compiler defaults to is | 
|  | // chosen. | 
|  | return std::string(driver.data, driver.size); | 
|  | } | 
|  |  | 
|  | // Tries to guess a set of target backends from the |device_flag_values| when | 
|  | // possible. Since multiple target backends can be used for a particular device | 
|  | // (such as llvm-cpu or vmvx for local-sync and local-task) this is just | 
|  | // guesswork. If we can't produce a target backend flag value we bail. | 
|  | // Returns a comma-delimited list of target backends. | 
|  | StatusOr<std::string> InferTargetBackendsFromDevices( | 
|  | iree_string_view_list_t device_uris) { | 
|  | // No-op when no devices specified (probably no HAL). | 
|  | if (device_uris.count == 0) return ""; | 
|  | // If multiple devices were provided we need to target all of them. | 
|  | std::set<std::string> target_backends; | 
|  | for (iree_host_size_t i = 0; i < device_uris.count; ++i) { | 
|  | auto target_backend = InferTargetBackendFromDevice(device_uris.values[i]); | 
|  | if (!target_backend.empty()) { | 
|  | target_backends.insert(std::move(target_backend)); | 
|  | } | 
|  | } | 
|  | // Join all target backends together. | 
|  | std::string result; | 
|  | for (auto& target_backend : target_backends) { | 
|  | if (!result.empty()) result.append(","); | 
|  | result.append(target_backend); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Configures the --iree-hal-target-backends= flag based on the --device= flags | 
|  | // set by the user. Ignored if any target backends are explicitly specified. | 
|  | // Online compilers would want to do some more intelligent device selection on | 
|  | // their own. | 
|  | Status ConfigureTargetBackends(iree_compiler_session_t* session, | 
|  | std::string* out_default_device_uri) { | 
|  | // Query the session for the currently set --iree-hal-target-backends= flag. | 
|  | // It may be empty string. | 
|  | std::string target_backends_flag; | 
|  | ireeCompilerSessionGetFlags( | 
|  | session, /*nonDefaultOnly=*/true, | 
|  | [](const char* flag_str, size_t length, void* user_data) { | 
|  | // NOTE: flag_str has the full `--flag=value` string. | 
|  | std::string_view prefix = "--iree-hal-target-backends="; | 
|  | std::string_view flag = std::string_view(flag_str, length); | 
|  | if (starts_with(prefix, flag)) { | 
|  | flag.remove_prefix(prefix.size()); | 
|  | if (flag.empty()) return;  // ignore empty | 
|  | auto* result = static_cast<std::string*>(user_data); | 
|  | *result = std::string(flag); | 
|  | } | 
|  | }, | 
|  | static_cast<void*>(&target_backends_flag)); | 
|  |  | 
|  | // Query the tooling utils for the --device= flag values. Note that zero or | 
|  | // more devices may be specified. | 
|  | iree_string_view_list_t device_uris = iree_hal_device_flag_list(); | 
|  |  | 
|  | // No-op if no target backends or devices are specified - this can be an | 
|  | // intentional decision as the user may be running a program that doesn't use | 
|  | // the HAL. | 
|  | if (target_backends_flag.empty() && device_uris.count == 0) { | 
|  | return OkStatus(); | 
|  | } | 
|  |  | 
|  | // No-op if both target backends and devices are set as the user has | 
|  | // explicitly specified a configuration. | 
|  | if (!target_backends_flag.empty() && device_uris.count > 0) { | 
|  | return OkStatus(); | 
|  | } | 
|  |  | 
|  | // If target backends are specified then we can infer the runtime devices from | 
|  | // the compiler configuration. This only works if there's a single backend | 
|  | // specified; if the user wants multiple target backends then they must | 
|  | // specify the device(s) to use. | 
|  | if (device_uris.count == 0) { | 
|  | if (target_backends_flag.find(',') != std::string::npos) { | 
|  | return iree_make_status( | 
|  | IREE_STATUS_INVALID_ARGUMENT, | 
|  | "if multiple target backends are specified the device to use must " | 
|  | "also be specified with --device= (have " | 
|  | "`--iree-hal-target-backends=%.*s`)", | 
|  | (int)target_backends_flag.size(), target_backends_flag.data()); | 
|  | } | 
|  | *out_default_device_uri = | 
|  | InferDefaultDeviceFromTargetBackend(target_backends_flag); | 
|  | return OkStatus(); | 
|  | } | 
|  |  | 
|  | // Infer target backends from the runtime device configuration. | 
|  | // This can get arbitrarily complex but for now this simple runner just | 
|  | // guesses. In the future we'll have more ways of configuring the compiler | 
|  | // from available runtime devices (not just the target backend but also | 
|  | // target-specific settings). | 
|  | IREE_ASSIGN_OR_RETURN(auto target_backends, | 
|  | InferTargetBackendsFromDevices(device_uris)); | 
|  | if (!target_backends.empty()) { | 
|  | auto target_backends_flag = | 
|  | std::string("--iree-hal-target-backends=") + target_backends; | 
|  | const char* compiler_argv[1] = { | 
|  | target_backends_flag.c_str(), | 
|  | }; | 
|  | auto error = ireeCompilerSessionSetFlags( | 
|  | session, IREE_ARRAYSIZE(compiler_argv), compiler_argv); | 
|  | if (error) { | 
|  | return iree_make_status( | 
|  | IREE_STATUS_INVALID_ARGUMENT, | 
|  | "unable to set inferred target backend flag to `%.*s`", | 
|  | (int)target_backends_flag.size(), target_backends_flag.data()); | 
|  | } | 
|  | } | 
|  |  | 
|  | return OkStatus(); | 
|  | } | 
|  |  | 
|  | // Runs the given .mlir file based on the current flags. | 
|  | StatusOr<int> CompileAndRunFile(iree_compiler_session_t* session, | 
|  | const char* mlir_filename) { | 
|  | IREE_TRACE_SCOPE_NAMED("CompileAndRunFile"); | 
|  |  | 
|  | // Configure the --iree-hal-target-backends= flag and/or get the default | 
|  | // device to use at runtime if either are not explicitly specified. | 
|  | // Note that target backends and the runtime devices aren't 1:1 and this is | 
|  | // an imperfect guess. In this simple online compiler we assume homogenous | 
|  | // device sets and only a single global target backend but library/hosting | 
|  | // layers can configure heterogenous and per-invocation target configurations. | 
|  | std::string default_device_uri; | 
|  | IREE_RETURN_IF_ERROR(ConfigureTargetBackends(session, &default_device_uri)); | 
|  |  | 
|  | // RAII container for the compiler invocation. | 
|  | struct InvocationState { | 
|  | iree_compiler_invocation_t* invocation = nullptr; | 
|  | iree_compiler_source_t* source = nullptr; | 
|  | iree_compiler_output_t* output = nullptr; | 
|  |  | 
|  | explicit InvocationState(iree_compiler_session_t* session) { | 
|  | invocation = ireeCompilerInvocationCreate(session); | 
|  | } | 
|  |  | 
|  | ~InvocationState() { | 
|  | if (source) ireeCompilerSourceDestroy(source); | 
|  | if (output) ireeCompilerOutputDestroy(output); | 
|  | ireeCompilerInvocationDestroy(invocation); | 
|  | } | 
|  |  | 
|  | Status emitError(iree_compiler_error_t* error, | 
|  | iree_status_code_t status_code, | 
|  | std::string_view while_performing = "") { | 
|  | const char* msg = ireeCompilerErrorGetMessage(error); | 
|  | fprintf(stderr, "error compiling input file: %s\n", msg); | 
|  | iree_status_t status = iree_make_status(status_code, "%s", msg); | 
|  | if (!while_performing.empty()) { | 
|  | status = iree_status_annotate( | 
|  | status, iree_make_string_view(while_performing.data(), | 
|  | while_performing.size())); | 
|  | } | 
|  | ireeCompilerErrorDestroy(error); | 
|  | return status; | 
|  | } | 
|  | } state(session); | 
|  |  | 
|  | // Open the source file on disk or stdin if `-`. | 
|  | if (auto error = | 
|  | ireeCompilerSourceOpenFile(session, mlir_filename, &state.source)) { | 
|  | return state.emitError(error, IREE_STATUS_NOT_FOUND, "opening source file"); | 
|  | } | 
|  |  | 
|  | // Open a writeable memory buffer that we can stream the compilation outputs | 
|  | // into. This may be backed by a memory-mapped file to allow for very large | 
|  | // results. | 
|  | if (auto error = ireeCompilerOutputOpenMembuffer(&state.output)) { | 
|  | return state.emitError(error, IREE_STATUS_INTERNAL, | 
|  | "open output memory buffer"); | 
|  | } | 
|  |  | 
|  | // TODO: make parsing/pipeline execution return an error object. | 
|  | // We could capture diagnostics, stash them on the state, and emit with | 
|  | // ireeCompilerInvocationEnableCallbackDiagnostics. | 
|  | // For now we route all errors to stderr. | 
|  | ireeCompilerInvocationEnableConsoleDiagnostics(state.invocation); | 
|  |  | 
|  | // Parse the source MLIR input and log verbose errors. Syntax errors or | 
|  | // version mismatches will hit here. | 
|  | if (!ireeCompilerInvocationParseSource(state.invocation, state.source)) { | 
|  | return iree_make_status(IREE_STATUS_INVALID_ARGUMENT, | 
|  | "failed to parse input file"); | 
|  | } | 
|  |  | 
|  | // Invoke the standard compilation pipeline to produce the compiled module. | 
|  | if (!ireeCompilerInvocationPipeline(state.invocation, | 
|  | IREE_COMPILER_PIPELINE_STD)) { | 
|  | return iree_make_status(IREE_STATUS_INTERNAL, | 
|  | "failed to invoke main compiler pipeline"); | 
|  | } | 
|  |  | 
|  | // Flush the output to the memory buffer. | 
|  | if (auto error = ireeCompilerInvocationOutputVMBytecode(state.invocation, | 
|  | state.output)) { | 
|  | return state.emitError(error, IREE_STATUS_INTERNAL, | 
|  | "emitting output VM module binary"); | 
|  | } | 
|  |  | 
|  | // Get a raw host pointer to the output that we can pass to the runtime. | 
|  | void* binary_data = nullptr; | 
|  | uint64_t binary_size = 0; | 
|  | if (auto error = ireeCompilerOutputMapMemory(state.output, &binary_data, | 
|  | &binary_size)) { | 
|  | return state.emitError(error, IREE_STATUS_INTERNAL, | 
|  | "mapping output buffer"); | 
|  | } | 
|  |  | 
|  | // Hosting libraries can route all runtime allocations to their own allocator | 
|  | // for statistics, isolation, or efficiency. Here we use the system | 
|  | // malloc/free. | 
|  | iree_allocator_t host_allocator = iree_allocator_system(); | 
|  |  | 
|  | // The same VM instance should be shared across many contexts. Here we only | 
|  | // use this once but a library would want to retain this and the devices it | 
|  | // creates for as long as practical. | 
|  | vm::ref<iree_vm_instance_t> instance; | 
|  | IREE_RETURN_IF_ERROR(iree_tooling_create_instance(host_allocator, &instance), | 
|  | "creating instance"); | 
|  |  | 
|  | // Run the compiled module using the global flags for I/O (if any). | 
|  | // This loads the module, creates a VM context with it and any dependencies, | 
|  | // parses inputs from flags, and routes/verifies outputs as specified. Hosting | 
|  | // libraries should always reuse contexts if possible to amortize loading | 
|  | // costs and carry state (variables/etc) across invocations. | 
|  | // | 
|  | // This returns a process exit code based on the run mode (verifying expected | 
|  | // outputs, etc) that may be non-zero even if the status is success | 
|  | // ("execution completed successfully but values did not match"). | 
|  | int exit_code = EXIT_SUCCESS; | 
|  | IREE_RETURN_IF_ERROR( | 
|  | iree_tooling_run_module_with_data( | 
|  | instance.get(), | 
|  | iree_make_string_view(default_device_uri.data(), | 
|  | default_device_uri.size()), | 
|  | iree_make_const_byte_span(binary_data, (iree_host_size_t)binary_size), | 
|  | host_allocator, &exit_code), | 
|  | "running compiled module"); | 
|  | return exit_code; | 
|  | } | 
|  |  | 
|  | // Parses a combined list of compiler and runtime flags. | 
|  | // Each argument list is stored in canonical argc/argv format with a trailing | 
|  | // NULL string in the storage (excluded from the count). | 
|  | class ArgParser { | 
|  | public: | 
|  | int compiler_argc() { return compiler_args_.size() - 1; } | 
|  | const char** compiler_argv() { | 
|  | return const_cast<const char**>(compiler_args_.data()); | 
|  | } | 
|  |  | 
|  | int runtime_argc() { return runtime_args_.size() - 1; } | 
|  | char** runtime_argv() { return runtime_args_.data(); } | 
|  |  | 
|  | // Parses arguments from a raw command line argc/argv set. | 
|  | // Returns true if parsing was successful. | 
|  | bool Parse(int argc_raw, char** argv_raw) { | 
|  | // Pre-process the arguments with the compiler's argument parser since it | 
|  | // has super-powers on Windows and must work on the default main arguments. | 
|  | ireeCompilerGetProcessCLArgs(&argc_raw, | 
|  | const_cast<const char***>(&argv_raw)); | 
|  |  | 
|  | // Always add the progname to both flag sets. | 
|  | compiler_args_.push_back(argv_raw[0]); | 
|  | runtime_args_.push_back(argv_raw[0]); | 
|  |  | 
|  | // Everything before -- goes to the runtime. | 
|  | // Everything after -- goes to the compiler. | 
|  | // To make it easier to form command lines in scripts we also allow | 
|  | // prefixing flags with -Xcompiler/-Xruntime on either side of the --. | 
|  | bool parsing_runtime_args = true; | 
|  | for (int i = 1; i < argc_raw; ++i) { | 
|  | char* current_arg_cstr = argv_raw[i]; | 
|  | char* next_arg_cstr = | 
|  | argv_raw[i + 1];  // ok because list is NULL-terminated | 
|  | auto current_arg = std::string_view(current_arg_cstr); | 
|  | if (current_arg == "--") { | 
|  | // Switch default parsing to compiler flags. | 
|  | parsing_runtime_args = false; | 
|  | } else if (current_arg == "-Xcompiler" || current_arg == "--Xcompiler") { | 
|  | // Next arg is routed to the compiler. | 
|  | compiler_args_.push_back(next_arg_cstr); | 
|  | } else if (current_arg == "-Xruntime" || current_arg == "--Xruntime") { | 
|  | // Next arg is routed to the runtime. | 
|  | runtime_args_.push_back(next_arg_cstr); | 
|  | } else if (starts_with("-Xcompiler,", current_arg) || | 
|  | starts_with("--Xcompiler,", current_arg)) { | 
|  | // Split and send the rest of the flag to the compiler. | 
|  | AppendPrefixedArg(current_arg, &compiler_args_); | 
|  | } else if (starts_with("-Xruntime,", current_arg) || | 
|  | starts_with("--Xruntime,", current_arg)) { | 
|  | // Split and send the rest of the flag to the runtime. | 
|  | AppendPrefixedArg(current_arg, &runtime_args_); | 
|  | } else { | 
|  | // Route to either runtime or compiler arg sets based on which side of | 
|  | // the -- we are on. | 
|  | if (parsing_runtime_args) { | 
|  | runtime_args_.push_back(current_arg_cstr); | 
|  | } else { | 
|  | compiler_args_.push_back(current_arg_cstr); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Add nullptrs to end to match real argv behavior. | 
|  | compiler_args_.push_back(nullptr); | 
|  | runtime_args_.push_back(nullptr); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | // Drops the prefix from |prefixed_arg| and appends the arg to |out_args|. | 
|  | // Example: --Xcompiler,ab=cd,ef -> --ab=cd,ef | 
|  | void AppendPrefixedArg(std::string_view prefixed_arg, | 
|  | std::vector<char*>* out_args) { | 
|  | std::string_view sub_arg = prefixed_arg.substr(prefixed_arg.find(',') + 1); | 
|  | auto stable_arg = std::make_unique<std::string>("--"); | 
|  | stable_arg->append(sub_arg); | 
|  | temp_strings_.push_back(std::move(stable_arg)); | 
|  | out_args->push_back(temp_strings_.back()->data()); | 
|  | } | 
|  |  | 
|  | std::vector<std::unique_ptr<std::string>> temp_strings_; | 
|  | std::vector<char*> runtime_args_; | 
|  | std::vector<char*> compiler_args_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | static int main(int argc, char** argv) { | 
|  | IREE_TRACE_APP_ENTER(); | 
|  | IREE_TRACE_ZONE_BEGIN_NAMED(z0, "iree-run-mlir"); | 
|  |  | 
|  | // Initialize the compiler once on startup before using any other APIs. | 
|  | ireeCompilerGlobalInitialize(); | 
|  |  | 
|  | // Parse full argument list and split into compiler/runtime flag sets. | 
|  | ArgParser arg_parser; | 
|  | if (!arg_parser.Parse(argc, argv)) { | 
|  | ireeCompilerGlobalShutdown(); | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | IREE_TRACE_APP_EXIT(EXIT_FAILURE); | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  |  | 
|  | // Pass along compiler flags. | 
|  | // Since this is a command line tool we initialize the global compiler | 
|  | // command line environment prior to processing the sources. | 
|  | // In-process/library uses would usually not do this and would set session | 
|  | // specific arguments as needed from whatever configuration mechanisms they | 
|  | // use (kwargs passed to python functions, etc). | 
|  | ireeCompilerSetupGlobalCL(arg_parser.compiler_argc(), | 
|  | arg_parser.compiler_argv(), "iree-run-mlir", | 
|  | /*installSignalHandlers=*/true); | 
|  |  | 
|  | // Pass along runtime flags. | 
|  | // Note that positional args are left in runtime_argv (after progname). | 
|  | // Runtime flags are generally only useful in command line tools where there's | 
|  | // a fixed set of devices, a short lifetime, a single thread, and a single | 
|  | // context/set of modules/etc. Hosting applications can programmatically | 
|  | // do most of what the flags do in a way that avoids the downsides of such | 
|  | // global one-shot configuration. | 
|  | int runtime_argc = arg_parser.runtime_argc(); | 
|  | char** runtime_argv = arg_parser.runtime_argv(); | 
|  | iree_flags_parse_checked(IREE_FLAGS_PARSE_MODE_DEFAULT, &runtime_argc, | 
|  | &runtime_argv); | 
|  |  | 
|  | // Ensure a source file was found. | 
|  | if (runtime_argc != 2) { | 
|  | fprintf(stderr, | 
|  | "ERROR: one source MLIR file must be specified.\n" | 
|  | "Pass either the path to a .mlir/mlirbc file or `-` to read from " | 
|  | "stdin.\n"); | 
|  | fflush(stderr); | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | IREE_TRACE_APP_EXIT(EXIT_FAILURE); | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  | const char* source_filename = runtime_argv[1]; | 
|  |  | 
|  | // Sessions can be reused for many compiler invocations. | 
|  | iree_compiler_session_t* session = ireeCompilerSessionCreate(); | 
|  |  | 
|  | // The process return code is 0 for success and non-zero otherwise. | 
|  | // We don't differentiate between compiler or runtime error codes here but | 
|  | // could if someone found it useful. | 
|  | int exit_code = EXIT_SUCCESS; | 
|  |  | 
|  | // Compile and run the provided source file and get the exit code determined | 
|  | // based on the run mode. | 
|  | auto status_or = CompileAndRunFile(session, source_filename); | 
|  | if (status_or.ok()) { | 
|  | exit_code = status_or.value(); | 
|  | } else { | 
|  | exit_code = 2; | 
|  | iree_status_fprint(stderr, status_or.status().get()); | 
|  | fflush(stderr); | 
|  | } | 
|  |  | 
|  | ireeCompilerSessionDestroy(session); | 
|  |  | 
|  | // No more compiler APIs can be called after this point. | 
|  | ireeCompilerGlobalShutdown(); | 
|  |  | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | IREE_TRACE_APP_EXIT(exit_code); | 
|  | return exit_code; | 
|  | } | 
|  |  | 
|  | }  // namespace iree | 
|  |  | 
|  | int main(int argc, char** argv) { return iree::main(argc, argv); } |