| /* |
| * Copyright 2021 Google LLC |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| #ifndef VKR_CS_H |
| #define VKR_CS_H |
| |
| #include "vkr_common.h" |
| |
| /* This is to avoid integer overflows and to catch bogus allocations (e.g., |
| * the guest driver encodes an uninitialized value). In practice, the largest |
| * allocations we've seen are from vkGetPipelineCacheData and are dozens of |
| * MBs. |
| */ |
| #define VKR_CS_DECODER_TEMP_POOL_MAX_SIZE (1u * 1024 * 1024 * 1024) |
| |
| struct iovec; |
| |
| struct vkr_cs_encoder { |
| bool *fatal_error; |
| |
| struct { |
| const struct vkr_resource_attachment *attachment; |
| const struct iovec *iov; |
| int iov_count; |
| size_t offset; |
| size_t size; |
| |
| int cached_index; |
| size_t cached_offset; |
| } stream; |
| |
| size_t remaining_size; |
| int next_iov; |
| uint8_t *cur; |
| const uint8_t *end; |
| }; |
| |
| struct vkr_cs_decoder_saved_state { |
| const uint8_t *cur; |
| const uint8_t *end; |
| |
| uint32_t pool_buffer_count; |
| uint8_t *pool_reset_to; |
| }; |
| |
| /* |
| * We usually need many small allocations during decoding. Those allocations |
| * are suballocated from the temp pool. |
| * |
| * After a command is decoded, vkr_cs_decoder_reset_temp_pool is called to |
| * reset pool->cur. After an entire command stream is decoded, |
| * vkr_cs_decoder_gc_temp_pool is called to garbage collect pool->buffers. |
| */ |
| struct vkr_cs_decoder_temp_pool { |
| uint8_t **buffers; |
| uint32_t buffer_count; |
| uint32_t buffer_max; |
| size_t total_size; |
| |
| uint8_t *reset_to; |
| |
| uint8_t *cur; |
| const uint8_t *end; |
| }; |
| |
| struct vkr_cs_decoder { |
| const struct hash_table *object_table; |
| |
| bool fatal_error; |
| struct vkr_cs_decoder_temp_pool temp_pool; |
| |
| struct vkr_cs_decoder_saved_state saved_states[1]; |
| uint32_t saved_state_count; |
| |
| const uint8_t *cur; |
| const uint8_t *end; |
| }; |
| |
| static inline void |
| vkr_cs_encoder_init(struct vkr_cs_encoder *enc, bool *fatal_error) |
| { |
| memset(enc, 0, sizeof(*enc)); |
| enc->fatal_error = fatal_error; |
| } |
| |
| static inline void |
| vkr_cs_encoder_set_fatal(const struct vkr_cs_encoder *enc) |
| { |
| *enc->fatal_error = true; |
| } |
| |
| void |
| vkr_cs_encoder_set_stream(struct vkr_cs_encoder *enc, |
| const struct vkr_resource_attachment *att, |
| size_t offset, |
| size_t size); |
| |
| void |
| vkr_cs_encoder_seek_stream(struct vkr_cs_encoder *enc, size_t pos); |
| |
| void |
| vkr_cs_encoder_write_internal(struct vkr_cs_encoder *enc, |
| size_t size, |
| const void *val, |
| size_t val_size); |
| |
| static inline void |
| vkr_cs_encoder_write(struct vkr_cs_encoder *enc, |
| size_t size, |
| const void *val, |
| size_t val_size) |
| { |
| assert(val_size <= size); |
| |
| if (unlikely(size > (size_t)(enc->end - enc->cur))) { |
| vkr_cs_encoder_write_internal(enc, size, val, val_size); |
| return; |
| } |
| |
| /* we should not rely on the compiler to optimize away memcpy... */ |
| memcpy(enc->cur, val, val_size); |
| enc->cur += size; |
| } |
| |
| void |
| vkr_cs_decoder_init(struct vkr_cs_decoder *dec, const struct hash_table *object_table); |
| |
| void |
| vkr_cs_decoder_fini(struct vkr_cs_decoder *dec); |
| |
| void |
| vkr_cs_decoder_reset(struct vkr_cs_decoder *dec); |
| |
| static inline void |
| vkr_cs_decoder_set_fatal(const struct vkr_cs_decoder *dec) |
| { |
| ((struct vkr_cs_decoder *)dec)->fatal_error = true; |
| } |
| |
| static inline bool |
| vkr_cs_decoder_get_fatal(const struct vkr_cs_decoder *dec) |
| { |
| return dec->fatal_error; |
| } |
| |
| static inline void |
| vkr_cs_decoder_set_stream(struct vkr_cs_decoder *dec, const void *data, size_t size) |
| { |
| dec->cur = data; |
| dec->end = dec->cur + size; |
| } |
| |
| static inline bool |
| vkr_cs_decoder_has_command(const struct vkr_cs_decoder *dec) |
| { |
| return dec->cur < dec->end; |
| } |
| |
| bool |
| vkr_cs_decoder_push_state(struct vkr_cs_decoder *dec); |
| |
| void |
| vkr_cs_decoder_pop_state(struct vkr_cs_decoder *dec); |
| |
| static inline bool |
| vkr_cs_decoder_peek_internal(const struct vkr_cs_decoder *dec, |
| size_t size, |
| void *val, |
| size_t val_size) |
| { |
| assert(val_size <= size); |
| |
| if (unlikely(size > (size_t)(dec->end - dec->cur))) { |
| vkr_log("failed to peek %zu bytes", size); |
| vkr_cs_decoder_set_fatal(dec); |
| memset(val, 0, val_size); |
| return false; |
| } |
| |
| /* we should not rely on the compiler to optimize away memcpy... */ |
| memcpy(val, dec->cur, val_size); |
| return true; |
| } |
| |
| static inline void |
| vkr_cs_decoder_read(struct vkr_cs_decoder *dec, size_t size, void *val, size_t val_size) |
| { |
| if (vkr_cs_decoder_peek_internal(dec, size, val, val_size)) |
| dec->cur += size; |
| } |
| |
| static inline void |
| vkr_cs_decoder_peek(const struct vkr_cs_decoder *dec, |
| size_t size, |
| void *val, |
| size_t val_size) |
| { |
| vkr_cs_decoder_peek_internal(dec, size, val, val_size); |
| } |
| |
| static inline struct vkr_object * |
| vkr_cs_decoder_lookup_object(const struct vkr_cs_decoder *dec, |
| vkr_object_id id, |
| VkObjectType type) |
| { |
| struct vkr_object *obj; |
| |
| if (!id) |
| return NULL; |
| |
| const struct hash_entry *entry = |
| _mesa_hash_table_search((struct hash_table *)dec->object_table, &id); |
| obj = likely(entry) ? entry->data : NULL; |
| if (unlikely(!obj || obj->type != type)) { |
| if (obj) |
| vkr_log("object %" PRIu64 " has type %d, not %d", id, obj->type, type); |
| else |
| vkr_log("failed to look up object %" PRIu64, id); |
| vkr_cs_decoder_set_fatal(dec); |
| } |
| |
| return obj; |
| } |
| |
| static inline void |
| vkr_cs_decoder_reset_temp_pool(struct vkr_cs_decoder *dec) |
| { |
| struct vkr_cs_decoder_temp_pool *pool = &dec->temp_pool; |
| pool->cur = pool->reset_to; |
| } |
| |
| bool |
| vkr_cs_decoder_alloc_temp_internal(struct vkr_cs_decoder *dec, size_t size); |
| |
| static inline void * |
| vkr_cs_decoder_alloc_temp(struct vkr_cs_decoder *dec, size_t size) |
| { |
| struct vkr_cs_decoder_temp_pool *pool = &dec->temp_pool; |
| |
| if (unlikely(size > (size_t)(pool->end - pool->cur))) { |
| if (!vkr_cs_decoder_alloc_temp_internal(dec, size)) { |
| vkr_log("failed to suballocate %zu bytes from the temp pool", size); |
| vkr_cs_decoder_set_fatal(dec); |
| return NULL; |
| } |
| } |
| |
| /* align to 64-bit after we know size is at most |
| * VKR_CS_DECODER_TEMP_POOL_MAX_SIZE and cannot overflow |
| */ |
| size = align64(size, 8); |
| assert(size <= (size_t)(pool->end - pool->cur)); |
| |
| void *ptr = pool->cur; |
| pool->cur += size; |
| return ptr; |
| } |
| |
| static inline bool |
| vkr_cs_handle_indirect_id(VkObjectType type) |
| { |
| /* Dispatchable handles may or may not have enough bits to store |
| * vkr_object_id. Non-dispatchable handles always have enough bits to |
| * store vkr_object_id. |
| * |
| * This should compile to a constant after inlining. |
| */ |
| switch (type) { |
| case VK_OBJECT_TYPE_INSTANCE: |
| case VK_OBJECT_TYPE_PHYSICAL_DEVICE: |
| case VK_OBJECT_TYPE_DEVICE: |
| case VK_OBJECT_TYPE_QUEUE: |
| case VK_OBJECT_TYPE_COMMAND_BUFFER: |
| return sizeof(VkInstance) < sizeof(vkr_object_id); |
| default: |
| return false; |
| } |
| } |
| |
| static inline vkr_object_id |
| vkr_cs_handle_load_id(const void **handle, VkObjectType type) |
| { |
| const vkr_object_id *p = vkr_cs_handle_indirect_id(type) |
| ? *(const vkr_object_id **)handle |
| : (const vkr_object_id *)handle; |
| return *p; |
| } |
| |
| static inline void |
| vkr_cs_handle_store_id(void **handle, vkr_object_id id, VkObjectType type) |
| { |
| vkr_object_id *p = vkr_cs_handle_indirect_id(type) ? *(vkr_object_id **)handle |
| : (vkr_object_id *)handle; |
| *p = id; |
| } |
| |
| #endif /* VKR_CS_H */ |