blob: 6ee7cdc73d9ab0a84da57fae478fc22b414bd163 [file] [log] [blame]
// 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
#include "iree/vm/bytecode/archive.h"
#include "iree/vm/bytecode/utils/isa.h"
// ZIP local file header (comes immediately before each file in the archive).
// In order to find the starting offset of the FlatBuffer in a polyglot archive
// we need to parse this given the variable-length nature of it (we want to
// be robust to file name and alignment changes).
//
// NOTE: all fields are little-endian.
// NOTE: we don't care about the actual module size here; since we support
// streaming archives trying to recover it would require much more
// involved processing (we'd need to reference the central directory).
// If we wanted to support users repacking ZIPs we'd probably want to
// rewrite everything as we store offsets in the FlatBuffer that are
// difficult to update after the archive has been produced.
#define ZIP_LOCAL_FILE_HEADER_SIGNATURE 0x04034B50u
#if defined(IREE_COMPILER_MSVC)
#pragma pack(push, 1)
#endif // IREE_COMPILER_MSVC
typedef struct {
uint32_t signature; // ZIP_LOCAL_FILE_HEADER_SIGNATURE
uint16_t version;
uint16_t general_purpose_flag;
uint16_t compression_method;
uint16_t last_modified_time;
uint16_t last_modified_date;
uint32_t crc32; // 0 for us
uint32_t compressed_size; // 0 for us
uint32_t uncompressed_size; // 0 for us
uint16_t file_name_length;
uint16_t extra_field_length;
// file name (variable size)
// extra field (variable size)
} IREE_ATTRIBUTE_PACKED zip_local_file_header_t;
#if defined(IREE_COMPILER_MSVC)
#pragma pack(pop)
#endif // IREE_COMPILER_MSVC
static_assert(sizeof(zip_local_file_header_t) == 30, "bad packing");
#if !defined(IREE_ENDIANNESS_LITTLE) || !IREE_ENDIANNESS_LITTLE
#error "little endian required for zip header parsing"
#endif // IREE_ENDIANNESS_LITTLE
// Strips any ZIP local file header from |contents| and stores the remaining
// range in |out_stripped|.
static iree_status_t iree_vm_bytecode_module_strip_zip_header(
iree_const_byte_span_t contents, iree_const_byte_span_t* out_stripped) {
// Ensure there's at least some bytes we can check for the header.
// Since we're only looking to strip zip stuff here we can check on that.
if (!contents.data ||
contents.data_length < sizeof(zip_local_file_header_t)) {
memmove(out_stripped, &contents, sizeof(contents));
return iree_ok_status();
}
// Check to see if there's a zip local header signature.
// For a compliant zip file this is expected to start at offset 0.
const zip_local_file_header_t* header =
(const zip_local_file_header_t*)contents.data;
if (header->signature != ZIP_LOCAL_FILE_HEADER_SIGNATURE) {
// No signature found, probably not a ZIP.
memmove(out_stripped, &contents, sizeof(contents));
return iree_ok_status();
}
// Compute the starting offset of the file.
// Note that we still don't know (or care) if it's the file we want; actual
// FlatBuffer verification happens later on.
uint32_t offset =
sizeof(*header) + header->file_name_length + header->extra_field_length;
if (offset > contents.data_length) {
// Is a ZIP but doesn't have enough data; error out with something more
// useful than the FlatBuffer verification failing later on given that here
// we know this isn't a FlatBuffer.
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"archive self-reports as a zip but does not have "
"enough data to contain a module");
}
*out_stripped = iree_make_const_byte_span(contents.data + offset,
contents.data_length - offset);
return iree_ok_status();
}
IREE_API_EXPORT iree_status_t iree_vm_bytecode_archive_parse_header(
iree_const_byte_span_t archive_contents,
iree_const_byte_span_t* out_flatbuffer_contents,
iree_host_size_t* out_rodata_offset) {
// Slice off any polyglot zip header we have prior to the base of the module.
iree_const_byte_span_t module_contents = iree_const_byte_span_empty();
IREE_RETURN_IF_ERROR(iree_vm_bytecode_module_strip_zip_header(
archive_contents, &module_contents));
// Verify there's enough data to safely check the FlatBuffer header.
if (!module_contents.data || module_contents.data_length < 16) {
return iree_make_status(
IREE_STATUS_INVALID_ARGUMENT,
"FlatBuffer data is not present or less than 16 bytes (%zu total)",
module_contents.data_length);
}
// Read the size prefix from the head of the module contents; this should be
// a 4 byte value indicating the total size of the FlatBuffer data.
size_t length_prefix = 0;
flatbuffers_read_size_prefix((void*)module_contents.data, &length_prefix);
// Verify the length prefix is within bounds (always <= the remaining module
// bytes).
size_t length_remaining =
module_contents.data_length - sizeof(flatbuffers_uoffset_t);
if (length_prefix > length_remaining) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"FlatBuffer length prefix out of bounds (prefix is "
"%zu but only %zu available)",
length_prefix, length_remaining);
}
// Form the range of bytes containing just the FlatBuffer data.
iree_const_byte_span_t flatbuffer_contents = iree_make_const_byte_span(
module_contents.data + sizeof(flatbuffers_uoffset_t), length_prefix);
if (out_flatbuffer_contents) {
*out_flatbuffer_contents = flatbuffer_contents;
}
if (out_rodata_offset) {
// rodata begins immediately following the FlatBuffer in memory.
iree_host_size_t rodata_offset = iree_host_align(
(iree_host_size_t)(flatbuffer_contents.data - archive_contents.data) +
length_prefix,
IREE_VM_ARCHIVE_SEGMENT_ALIGNMENT);
*out_rodata_offset = rodata_offset;
}
return iree_ok_status();
}