| // Copyright 2023 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 | 
 |  | 
 | // Converts one or more parameter files into a single IREE Parameter Archive. | 
 | // Allows for stripping and renaming parameters as basic editing features. | 
 |  | 
 | #include <ctype.h> | 
 | #include <stdio.h> | 
 |  | 
 | #include "iree/base/api.h" | 
 | #include "iree/base/internal/file_io.h" | 
 | #include "iree/base/internal/flags.h" | 
 | #include "iree/hal/api.h" | 
 | #include "iree/io/formats/irpa/irpa_builder.h" | 
 | #include "iree/io/parameter_index.h" | 
 | #include "iree/io/scope_map.h" | 
 | #include "iree/io/stream.h" | 
 |  | 
 | //===----------------------------------------------------------------------===// | 
 | // Parameter builder logic | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | IREE_FLAG_LIST( | 
 |     string, splat, | 
 |     "Declares a splat parameter in the form `name=shapextype=pattern`.\n" | 
 |     "Splat parameters have no storage on disk and can be used to mock real\n" | 
 |     "parameters that besides load-time performance and contents behave the\n" | 
 |     "same as if real parameters had been used. The shape and type are used to\n" | 
 |     "calculate the parameter size in the index and the value is interpreted\n" | 
 |     "based on the type. Note that splat parameters cannot be mutated at\n" | 
 |     "runtime and can only be used for constant values.\n"); | 
 |  | 
 | // TODO(benvanik): support @file.bin syntax, numpy files, etc. | 
 | IREE_FLAG_LIST( | 
 |     string, data, | 
 |     "Declares a data parameter in the form `name=shapextype=pattern`.\n" | 
 |     "Data parameters have a storage reservation in the final archive with\n" | 
 |     "either zeroed contents or a specified repeating pattern value.\n" | 
 |     "The shape and type are used to calculate the parameter size in the index\n" | 
 |     "and the value is interpreted based on the type. Omitting the value\n" | 
 |     "will leave the parameter with zeroed contents on disk."); | 
 |  | 
 | // TODO(benvanik): support external entries (--external=file.bin#128-512). | 
 |  | 
 | IREE_FLAG(int32_t, alignment, IREE_IO_PARAMETER_ARCHIVE_DEFAULT_DATA_ALIGNMENT, | 
 |           "Storage data alignment relative to the header."); | 
 |  | 
 | typedef struct { | 
 |   iree_string_view_t name; | 
 |   uint64_t storage_size; | 
 |   uint64_t element_count; | 
 |   iree_hal_element_type_t element_type; | 
 |   struct { | 
 |     uint8_t pattern[16]; | 
 |     uint8_t pattern_length; | 
 |   } splat; | 
 | } iree_io_parameter_info_t; | 
 |  | 
 | // Parses a `name=shapextype[=value]` flag string. | 
 | static iree_status_t iree_io_parameter_info_from_string( | 
 |     iree_string_view_t parameter_value, iree_io_parameter_info_t* out_info) { | 
 |   memset(out_info, 0, sizeof(*out_info)); | 
 |  | 
 |   iree_string_view_t spec; | 
 |   iree_string_view_split(parameter_value, '=', &out_info->name, &spec); | 
 |  | 
 |   iree_string_view_t shape_type, contents; | 
 |   iree_string_view_split(spec, '=', &shape_type, &contents); | 
 |  | 
 |   iree_host_size_t shape_rank = 0; | 
 |   iree_hal_dim_t shape[16] = {0}; | 
 |   IREE_RETURN_IF_ERROR(iree_hal_parse_shape_and_element_type( | 
 |       shape_type, IREE_ARRAYSIZE(shape), &shape_rank, shape, | 
 |       &out_info->element_type)); | 
 |  | 
 |   if (IREE_UNLIKELY(iree_hal_element_bit_count(out_info->element_type) == 0) || | 
 |       IREE_UNLIKELY( | 
 |           !iree_hal_element_is_byte_aligned(out_info->element_type))) { | 
 |     return iree_make_status( | 
 |         IREE_STATUS_INVALID_ARGUMENT, | 
 |         "opaque and sub-byte aligned element types cannot currently be used as " | 
 |         "splats; use a bitcast type (2xi4=1xi8, etc)"); | 
 |   } | 
 |  | 
 |   out_info->storage_size = | 
 |       iree_hal_element_dense_byte_count(out_info->element_type); | 
 |   out_info->element_count = 1; | 
 |   for (iree_host_size_t i = 0; i < shape_rank; ++i) { | 
 |     out_info->storage_size *= shape[i]; | 
 |     out_info->element_count *= shape[i]; | 
 |   } | 
 |  | 
 |   // TODO(benvanik): support external files and such; for now we just assume | 
 |   // either empty string (0 splat) or a splat value. | 
 |  | 
 |   if (iree_string_view_is_empty(contents)) { | 
 |     out_info->splat.pattern_length = 1; | 
 |     memset(out_info->splat.pattern, 0, sizeof(out_info->splat.pattern)); | 
 |   } else { | 
 |     iree_device_size_t byte_count = | 
 |         iree_hal_element_dense_byte_count(out_info->element_type); | 
 |     if (byte_count > sizeof(out_info->splat.pattern)) { | 
 |       return iree_make_status( | 
 |           IREE_STATUS_OUT_OF_RANGE, | 
 |           "element type size for %.*s out of range of splat patterns", | 
 |           (int)shape_type.size, shape_type.data); | 
 |     } | 
 |     out_info->splat.pattern_length = (uint8_t)byte_count; | 
 |     IREE_RETURN_IF_ERROR(iree_hal_parse_element( | 
 |         contents, out_info->element_type, | 
 |         iree_make_byte_span(out_info->splat.pattern, 16))); | 
 |   } | 
 |   return iree_ok_status(); | 
 | } | 
 |  | 
 | // Declares parameter metadata for all parameters specified by flags. | 
 | static iree_status_t iree_tooling_declare_parameters( | 
 |     iree_io_parameter_archive_builder_t* builder) { | 
 |   IREE_TRACE_ZONE_BEGIN(z0); | 
 |  | 
 |   // Metadata-only parameters first; they have no storage and the fact that they | 
 |   // are not in-order with any data parameters means they don't impact runtime | 
 |   // access locality behavior. | 
 |   for (iree_host_size_t i = 0; i < FLAG_splat_list().count; ++i) { | 
 |     iree_io_parameter_info_t info; | 
 |     IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
 |         z0, | 
 |         iree_io_parameter_info_from_string(FLAG_splat_list().values[i], &info)); | 
 |     IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
 |         z0, | 
 |         iree_io_parameter_archive_builder_add_splat_entry( | 
 |             builder, info.name, /*metadata=*/iree_const_byte_span_empty(), | 
 |             info.splat.pattern, info.splat.pattern_length, info.storage_size)); | 
 |   } | 
 |  | 
 |   // Data parameters follow and will appear in storage in the order they were | 
 |   // declared with flags. | 
 |   for (iree_host_size_t i = 0; i < FLAG_data_list().count; ++i) { | 
 |     iree_io_parameter_info_t info; | 
 |     IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
 |         z0, | 
 |         iree_io_parameter_info_from_string(FLAG_data_list().values[i], &info)); | 
 |     IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
 |         z0, iree_io_parameter_archive_builder_add_data_entry( | 
 |                 builder, info.name, /*metadata=*/iree_const_byte_span_empty(), | 
 |                 FLAG_alignment, info.storage_size)); | 
 |   } | 
 |  | 
 |   IREE_TRACE_ZONE_END(z0); | 
 |   return iree_ok_status(); | 
 | } | 
 |  | 
 | // Defines parameter storage for those that require it. | 
 | static iree_status_t iree_tooling_define_parameters( | 
 |     iree_io_parameter_index_t* target_index, | 
 |     iree_io_physical_offset_t target_file_offset, | 
 |     iree_io_stream_t* target_stream) { | 
 |   IREE_TRACE_ZONE_BEGIN(z0); | 
 |  | 
 |   for (iree_host_size_t i = 0; i < FLAG_data_list().count; ++i) { | 
 |     iree_io_parameter_info_t info; | 
 |     IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
 |         z0, | 
 |         iree_io_parameter_info_from_string(FLAG_data_list().values[i], &info)); | 
 |     const iree_io_parameter_index_entry_t* target_entry = NULL; | 
 |     IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
 |         z0, | 
 |         iree_io_parameter_index_lookup(target_index, info.name, &target_entry)); | 
 |     IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
 |         z0, iree_io_stream_seek( | 
 |                 target_stream, IREE_IO_STREAM_SEEK_SET, | 
 |                 target_file_offset + target_entry->storage.file.offset)); | 
 |     IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
 |         z0, iree_io_stream_fill(target_stream, info.element_count, | 
 |                                 info.splat.pattern, info.splat.pattern_length)); | 
 |   } | 
 |  | 
 |   IREE_TRACE_ZONE_END(z0); | 
 |   return iree_ok_status(); | 
 | } | 
 |  | 
 | //===----------------------------------------------------------------------===// | 
 | // main | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | IREE_FLAG(bool, quiet, false, | 
 |           "Silences additional stdout output when not needed."); | 
 |  | 
 | // TODO(benvanik): add --append= to chain archive headers. | 
 | IREE_FLAG(string, output, "", "Output .irpa file path."); | 
 |  | 
 | static void iree_io_file_handle_release_mapping( | 
 |     void* user_data, iree_io_file_handle_primitive_t handle_primitive) { | 
 |   iree_file_contents_free((iree_file_contents_t*)user_data); | 
 | } | 
 |  | 
 | static iree_status_t iree_tooling_open_output_parameter_file( | 
 |     iree_io_physical_offset_t archive_offset, | 
 |     iree_io_physical_size_t archive_length, iree_allocator_t host_allocator, | 
 |     iree_io_file_handle_t** out_file_handle) { | 
 |   iree_file_contents_t* file_contents = NULL; | 
 |   IREE_RETURN_IF_ERROR(iree_file_create_mapped( | 
 |       FLAG_output, archive_offset + archive_length, archive_offset, | 
 |       (iree_host_size_t)archive_length, host_allocator, &file_contents)); | 
 |   iree_io_file_handle_release_callback_t release_callback = { | 
 |       .fn = iree_io_file_handle_release_mapping, | 
 |       .user_data = file_contents, | 
 |   }; | 
 |   iree_status_t status = iree_io_file_handle_wrap_host_allocation( | 
 |       IREE_IO_FILE_ACCESS_WRITE, file_contents->buffer, release_callback, | 
 |       host_allocator, out_file_handle); | 
 |   if (!iree_status_is_ok(status)) { | 
 |     iree_file_contents_free(file_contents); | 
 |   } | 
 |   return status; | 
 | } | 
 |  | 
 | int main(int argc, char** argv) { | 
 |   IREE_TRACE_APP_ENTER(); | 
 |   IREE_TRACE_ZONE_BEGIN(z0); | 
 |  | 
 |   iree_allocator_t host_allocator = iree_allocator_system(); | 
 |   int exit_code = EXIT_SUCCESS; | 
 |  | 
 |   // Parse command line flags. | 
 |   iree_flags_set_usage( | 
 |       "iree-create-parameters", | 
 |       "Creates IREE Parameter Archive (.irpa) files. Provide zero or more\n" | 
 |       "parameter value declarations and an output file with\n" | 
 |       "`--output=file.irpa` to produce a new file with zeroed or patterned\n" | 
 |       "contents.\n" | 
 |       "\n" | 
 |       "Parameter declarations take a shape and type in order to calculate the\n" | 
 |       "required storage size of the parameter at rest and at runtime. The\n" | 
 |       "shape and type need not match what the consuming program expects so\n" | 
 |       "long as the storage size is equivalent; for example, if the program\n" | 
 |       "expects a parameter of type `tensor<8x2xi4>` the parameter declaration\n" | 
 |       "can be `8xi8`, `1xi64`, `2xf32`, etc.\n" | 
 |       "\n" | 
 |       "Example creating a file with two embedded data parameters that have\n" | 
 |       "zeroed contents and one with a repeating pattern:\n" | 
 |       "  iree-create-parameters \\\n" | 
 |       "    --data=my.zeroed_param_1=4096xf32 \\\n" | 
 |       "    --data=my.zeroed_param_2=2x4096xi16 \\\n" | 
 |       "    --data=my.pattern_param_2=8x2xf32=2.1 \\\n" | 
 |       "    --output=output_with_storage.irpa\n" | 
 |       "\n" | 
 |       "Example creating a file with splatted values (no storage on disk):\n" | 
 |       "  iree-create-parameters \\\n" | 
 |       "    --splat=my.splat_param_1=4096xf32=4.1 \\\n" | 
 |       "    --splat=my.splat_param_2=2x4096xi16=123 \\\n" | 
 |       "    --output=output_without_storage.irpa\n"); | 
 |   iree_flags_parse_checked(IREE_FLAGS_PARSE_MODE_DEFAULT, &argc, &argv); | 
 |  | 
 |   iree_io_parameter_archive_builder_t builder; | 
 |   iree_io_parameter_archive_builder_initialize(host_allocator, &builder); | 
 |  | 
 |   // Declare parameters based on flags, populating the builder with the metadata | 
 |   // for each parameter without yet writing any data. | 
 |   iree_status_t status = iree_tooling_declare_parameters(&builder); | 
 |  | 
 |   // Open a file of sufficient size (now that we know it) for writing. | 
 |   iree_io_physical_offset_t target_file_offset = 0; | 
 |   iree_io_physical_offset_t archive_offset = iree_align_uint64( | 
 |       target_file_offset, IREE_IO_PARAMETER_ARCHIVE_HEADER_ALIGNMENT); | 
 |   iree_io_physical_size_t archive_length = | 
 |       iree_io_parameter_archive_builder_total_size(&builder); | 
 |   iree_io_file_handle_t* target_file_handle = NULL; | 
 |   if (iree_status_is_ok(status)) { | 
 |     status = iree_tooling_open_output_parameter_file( | 
 |         archive_offset, archive_length, host_allocator, &target_file_handle); | 
 |   } | 
 |  | 
 |   // Wrap the target file in a stream. | 
 |   iree_io_stream_t* target_stream = NULL; | 
 |   if (iree_status_is_ok(status)) { | 
 |     status = | 
 |         iree_io_stream_open(IREE_IO_STREAM_MODE_WRITABLE, target_file_handle, | 
 |                             target_file_offset, host_allocator, &target_stream); | 
 |   } | 
 |  | 
 |   // Allocate an index we'll populate during building to allow us to get the | 
 |   // storage ranges of non-metadata parameters. | 
 |   iree_io_parameter_index_t* built_index = NULL; | 
 |   if (iree_status_is_ok(status)) { | 
 |     status = iree_io_parameter_index_create(host_allocator, &built_index); | 
 |   } | 
 |  | 
 |   // Commit the archive header to the file and produce an index referencing it. | 
 |   // This will allow us to know where to copy file contents. | 
 |   if (iree_status_is_ok(status)) { | 
 |     status = iree_io_parameter_archive_builder_write( | 
 |         &builder, target_file_handle, target_file_offset, target_stream, | 
 |         built_index); | 
 |   } | 
 |  | 
 |   // Define non-metadata-only parameters that use the data storage segment. | 
 |   if (iree_status_is_ok(status)) { | 
 |     status = iree_tooling_define_parameters(built_index, target_file_offset, | 
 |                                             target_stream); | 
 |   } | 
 |  | 
 |   // Dump the new index ala iree-dump-parameters to show the final file. | 
 |   if (iree_status_is_ok(status) && !FLAG_quiet) { | 
 |     status = iree_io_parameter_index_fprint(stdout, iree_string_view_empty(), | 
 |                                             built_index); | 
 |   } | 
 |  | 
 |   iree_io_stream_release(target_stream); | 
 |   iree_io_file_handle_release(target_file_handle); | 
 |   iree_io_parameter_archive_builder_deinitialize(&builder); | 
 |   iree_io_parameter_index_release(built_index); | 
 |  | 
 |   fflush(stdout); | 
 |   if (!iree_status_is_ok(status)) { | 
 |     iree_status_fprint(stderr, status); | 
 |     iree_status_free(status); | 
 |     exit_code = EXIT_FAILURE; | 
 |   } | 
 |   fflush(stderr); | 
 |  | 
 |   IREE_TRACE_ZONE_END(z0); | 
 |   IREE_TRACE_APP_EXIT(exit_code); | 
 |   return exit_code; | 
 | } |