[fidl][c] V1 <-> V2 Wire Format Transformer

This CL adds a transformer that converts from V1 to V2 wire format bytes
and from V2 to V1 wire format bytes. It relies on the coding tables for
size and offset information.

The transformer is tested by an existing suite of GIDL tests,
with more being added in future CLs.

Change-Id: Ib8b1e6ea1a01d11789f48da71c6c91fc23bfdbe6
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/558223
Reviewed-by: Yifei Teng <yifeit@google.com>
Reviewed-by: Mitchell Kember <mkember@google.com>
Commit-Queue: Benjamin Prosnitz <bprosnitz@google.com>
diff --git a/src/lib/fidl/transformer/transformer.cc b/src/lib/fidl/transformer/transformer.cc
index 2c17edc..b4d19d9 100644
--- a/src/lib/fidl/transformer/transformer.cc
+++ b/src/lib/fidl/transformer/transformer.cc
@@ -4,19 +4,687 @@
 
 #include "src/lib/fidl/transformer/transformer.h"
 
+#include <lib/fidl/internal.h>
+
+#include <algorithm>
 #include <cstdint>
 #include <cstring>
 
+#define SRC_VALUE(v1_value, v2_value) \
+  ((transformation_ == FIDL_TRANSFORMATION_V1_TO_V2) ? (v1_value) : (v2_value))
+#define DST_VALUE(v1_value, v2_value) \
+  ((transformation_ == FIDL_TRANSFORMATION_V1_TO_V2) ? (v2_value) : (v1_value))
+
+namespace {
+
+struct WireEnvelopeV1 {
+  uint32_t num_bytes;
+  uint32_t num_handles;
+  uint64_t presence_marker;
+};
+
+struct WireEnvelopeV2 {
+  union {
+    uint32_t num_bytes;
+    uint8_t inlined_value[4];
+  };
+  uint16_t num_handles;
+  uint16_t flags;
+};
+
+constexpr uint16_t EmptyFlags = 0x00;
+constexpr uint16_t InlinedEnvelopeFlag = 0x01;
+constexpr bool ValidFlags(const WireEnvelopeV2 envelope) {
+  return (envelope.flags & ~InlinedEnvelopeFlag) == 0;
+}
+constexpr bool IsInlined(const WireEnvelopeV2 envelope) {
+  return (envelope.flags & InlinedEnvelopeFlag) != 0;
+}
+
+struct WireTableHeader {
+  uint64_t count;
+  uint64_t presence_marker;
+};
+
+constexpr uint32_t PrimitiveSize(const FidlCodedPrimitiveSubtype primitive) {
+  switch (primitive) {
+    case kFidlCodedPrimitiveSubtype_Bool:
+    case kFidlCodedPrimitiveSubtype_Int8:
+    case kFidlCodedPrimitiveSubtype_Uint8:
+      return 1;
+    case kFidlCodedPrimitiveSubtype_Int16:
+    case kFidlCodedPrimitiveSubtype_Uint16:
+      return 2;
+    case kFidlCodedPrimitiveSubtype_Int32:
+    case kFidlCodedPrimitiveSubtype_Uint32:
+    case kFidlCodedPrimitiveSubtype_Float32:
+      return 4;
+    case kFidlCodedPrimitiveSubtype_Int64:
+    case kFidlCodedPrimitiveSubtype_Uint64:
+    case kFidlCodedPrimitiveSubtype_Float64:
+      return 8;
+  }
+  __builtin_unreachable();
+}
+
+constexpr uint32_t TypeSizeV1(const fidl_type_t* type) {
+  switch (type->type_tag()) {
+    case kFidlTypePrimitive:
+      return PrimitiveSize(type->coded_primitive().type);
+    case kFidlTypeEnum:
+      return PrimitiveSize(type->coded_enum().underlying_type);
+    case kFidlTypeBits:
+      return PrimitiveSize(type->coded_bits().underlying_type);
+    case kFidlTypeStructPointer:
+      return sizeof(uint64_t);
+    case kFidlTypeHandle:
+      return sizeof(zx_handle_t);
+    case kFidlTypeStruct:
+      return type->coded_struct().size_v1;
+    case kFidlTypeTable:
+      return sizeof(fidl_vector_t);
+    case kFidlTypeXUnion:
+      return 24;
+    case kFidlTypeString:
+      return sizeof(fidl_string_t);
+    case kFidlTypeArray:
+      return type->coded_array().array_size_v1;
+    case kFidlTypeVector:
+      return sizeof(fidl_vector_t);
+  }
+  __builtin_unreachable();
+}
+
+constexpr uint32_t TypeSizeV2(const fidl_type_t* type) {
+  switch (type->type_tag()) {
+    case kFidlTypePrimitive:
+      return PrimitiveSize(type->coded_primitive().type);
+    case kFidlTypeEnum:
+      return PrimitiveSize(type->coded_enum().underlying_type);
+    case kFidlTypeBits:
+      return PrimitiveSize(type->coded_bits().underlying_type);
+    case kFidlTypeStructPointer:
+      return sizeof(uint64_t);
+    case kFidlTypeHandle:
+      return sizeof(zx_handle_t);
+    case kFidlTypeStruct:
+      return type->coded_struct().size_v2;
+    case kFidlTypeTable:
+      return sizeof(fidl_vector_t);
+    case kFidlTypeXUnion:
+      return 16;
+    case kFidlTypeString:
+      return sizeof(fidl_string_t);
+    case kFidlTypeArray:
+      return type->coded_array().array_size_v2;
+    case kFidlTypeVector:
+      return sizeof(fidl_vector_t);
+  }
+  __builtin_unreachable();
+}
+
+// Transformer that converts between the V1 and V2 wire formats (and vice versa).
+//
+// The invariant held by Transform* methods is that each Transform* method is responsible for
+// writing "itself" to the output. That is, TransformVector will ensure the vector header
+// and body is converted to the appropriate wire format and copied.
+class Transformer {
+ public:
+  Transformer(fidl_transformation_t transformation, const uint8_t* src_bytes,
+              uint32_t src_num_bytes, uint8_t* dst_bytes, uint32_t dst_num_bytes_capacity)
+      : transformation_(transformation),
+        src_bytes_(src_bytes),
+        dst_bytes_(dst_bytes),
+        src_next_out_of_line_(0),
+        dst_next_out_of_line_(0),
+        src_num_bytes_(src_num_bytes),
+        dst_num_bytes_capacity_(dst_num_bytes_capacity),
+        error(nullptr) {
+    ZX_ASSERT(transformation_ == FIDL_TRANSFORMATION_V1_TO_V2 ||
+              transformation_ == FIDL_TRANSFORMATION_V2_TO_V1);
+    ZX_ASSERT(FidlIsAligned(src_bytes));
+    ZX_ASSERT(FidlIsAligned(dst_bytes));
+  }
+
+  // Performs a transform inside of a primary / new out of line object.
+  zx_status_t Transform(const fidl_type_t* type, uint32_t src_offset, uint32_t dst_offset) {
+    zx_status_t status =
+        IncreaseNextOutOfLineV1V2(FIDL_ALIGN(TypeSizeV1(type)), FIDL_ALIGN(TypeSizeV2(type)));
+    if (status != ZX_OK) {
+      return status;
+    }
+    return TransformInline(type, src_offset, dst_offset);
+  }
+
+  const char* err_msg() { return error; }
+  uint32_t dst_num_bytes() { return dst_next_out_of_line_; }
+
+ private:
+  // Performs a transform inside of an inline object.
+  zx_status_t TransformInline(const fidl_type_t* type, uint32_t src_offset, uint32_t dst_offset) {
+    switch (type->type_tag()) {
+      case kFidlTypePrimitive:
+        return TransformPrimitive(&type->coded_primitive(), src_offset, dst_offset);
+      case kFidlTypeEnum:
+        return TransformEnum(&type->coded_enum(), src_offset, dst_offset);
+      case kFidlTypeBits:
+        return TransformBits(&type->coded_bits(), src_offset, dst_offset);
+      case kFidlTypeHandle:
+        return TransformHandle(&type->coded_handle(), src_offset, dst_offset);
+      case kFidlTypeStruct:
+        return TransformStruct(&type->coded_struct(), src_offset, dst_offset);
+      case kFidlTypeArray:
+        return TransformArray(&type->coded_array(), src_offset, dst_offset);
+      case kFidlTypeStructPointer:
+        return TransformStructPointer(&type->coded_struct_pointer(), src_offset, dst_offset);
+      case kFidlTypeString:
+        return TransformString(&type->coded_string(), src_offset, dst_offset);
+      case kFidlTypeVector:
+        return TransformVector(&type->coded_vector(), src_offset, dst_offset);
+      case kFidlTypeXUnion:
+        return TransformXUnion(&type->coded_xunion(), src_offset, dst_offset);
+      case kFidlTypeTable:
+        return TransformTable(&type->coded_table(), src_offset, dst_offset);
+    }
+    __builtin_unreachable();
+  }
+
+  zx_status_t TransformPrimitive(const FidlCodedPrimitive* coded_primitive, uint32_t src_offset,
+                                 uint32_t dst_offset) {
+    uint32_t size = PrimitiveSize(coded_primitive->type);
+    memcpy(&dst_bytes_[dst_offset], &src_bytes_[src_offset], size);
+    return ZX_OK;
+  }
+
+  zx_status_t TransformEnum(const FidlCodedEnum* coded_enum, uint32_t src_offset,
+                            uint32_t dst_offset) {
+    uint32_t size = PrimitiveSize(coded_enum->underlying_type);
+    memcpy(&dst_bytes_[dst_offset], &src_bytes_[src_offset], size);
+    return ZX_OK;
+  }
+
+  zx_status_t TransformBits(const FidlCodedBits* coded_bits, uint32_t src_offset,
+                            uint32_t dst_offset) {
+    uint32_t size = PrimitiveSize(coded_bits->underlying_type);
+    memcpy(&dst_bytes_[dst_offset], &src_bytes_[src_offset], size);
+    return ZX_OK;
+  }
+
+  zx_status_t TransformHandle(const FidlCodedHandle* coded_handle, uint32_t src_offset,
+                              uint32_t dst_offset) {
+    memcpy(&dst_bytes_[dst_offset], &src_bytes_[src_offset], sizeof(zx_handle_t));
+    return ZX_OK;
+  }
+
+  zx_status_t TransformStruct(const FidlCodedStruct* coded_struct, uint32_t src_offset,
+                              uint32_t dst_offset) {
+    // 1. Copy up to the next non-padding field.
+    // 2. Call the inner transformer for that field.
+    // 3. Repeat 1 & 2 as needed.
+    // 4. Copy up to the end of the struct.
+
+    uint32_t inner_src_offset = 0;
+    uint32_t inner_dst_offset = 0;
+    for (uint32_t i = 0; i < coded_struct->element_count; i++) {
+      const struct FidlStructElement& element = coded_struct->elements[i];
+      switch (element.header.element_type) {
+        case kFidlStructElementType_Field: {
+          uint32_t new_inner_src_offset =
+              SRC_VALUE(element.field.offset_v1, element.field.offset_v2);
+          uint32_t new_inner_dst_offset =
+              DST_VALUE(element.field.offset_v1, element.field.offset_v2);
+
+          ZX_ASSERT(new_inner_src_offset - inner_src_offset ==
+                    new_inner_dst_offset - inner_dst_offset);
+          if (new_inner_src_offset > inner_src_offset) {
+            memcpy(&dst_bytes_[dst_offset + inner_dst_offset],
+                   &src_bytes_[src_offset + inner_src_offset],
+                   new_inner_src_offset - inner_src_offset);
+          }
+
+          zx_status_t status =
+              TransformInline(element.field.field_type, src_offset + new_inner_src_offset,
+                              dst_offset + new_inner_dst_offset);
+          if (status != ZX_OK) {
+            return status;
+          }
+
+          inner_src_offset = new_inner_src_offset + SRC_VALUE(TypeSizeV1(element.field.field_type),
+                                                              TypeSizeV2(element.field.field_type));
+          inner_dst_offset = new_inner_dst_offset + DST_VALUE(TypeSizeV1(element.field.field_type),
+                                                              TypeSizeV2(element.field.field_type));
+        } break;
+        case kFidlStructElementType_Padding64:
+          break;
+        case kFidlStructElementType_Padding32:
+          break;
+        case kFidlStructElementType_Padding16:
+          break;
+      }
+    }
+
+    uint32_t src_size = SRC_VALUE(coded_struct->size_v1, coded_struct->size_v2);
+    uint32_t dst_size = DST_VALUE(coded_struct->size_v1, coded_struct->size_v2);
+    ZX_ASSERT(src_size - inner_src_offset == dst_size - inner_dst_offset);
+    if (src_size > inner_src_offset) {
+      memcpy(&dst_bytes_[dst_offset + inner_dst_offset], &src_bytes_[src_offset + inner_src_offset],
+             src_size - inner_src_offset);
+    }
+    return ZX_OK;
+  }
+
+  zx_status_t TransformArray(const FidlCodedArray* coded_array, uint32_t src_offset,
+                             uint32_t dst_offset) {
+    uint32_t src_element_size =
+        SRC_VALUE(coded_array->element_size_v1, coded_array->element_size_v2);
+    uint32_t dst_element_size =
+        DST_VALUE(coded_array->element_size_v1, coded_array->element_size_v2);
+    uint32_t src_offset_end =
+        src_offset + SRC_VALUE(coded_array->array_size_v1, coded_array->array_size_v2);
+
+    for (; src_offset < src_offset_end;
+         src_offset += src_element_size, dst_offset += dst_element_size) {
+      zx_status_t status = TransformInline(coded_array->element, src_offset, dst_offset);
+      if (status != ZX_OK) {
+        return status;
+      }
+    }
+    return ZX_OK;
+  }
+
+  zx_status_t TransformStructPointer(const FidlCodedStructPointer* coded_struct_pointer,
+                                     uint32_t src_offset, uint32_t dst_offset) {
+    switch (src_u64(src_offset)) {
+      case FIDL_ALLOC_ABSENT:
+        dst_u64(dst_offset) = FIDL_ALLOC_ABSENT;
+        return ZX_OK;
+      case FIDL_ALLOC_PRESENT: {
+        dst_u64(dst_offset) = FIDL_ALLOC_PRESENT;
+
+        uint32_t src_next_offset = src_next_out_of_line_;
+        uint32_t dst_next_offset = dst_next_out_of_line_;
+
+        zx_status_t status =
+            IncreaseNextOutOfLineV1V2(FIDL_ALIGN(coded_struct_pointer->struct_type->size_v1),
+                                      FIDL_ALIGN(coded_struct_pointer->struct_type->size_v2));
+        if (status != ZX_OK) {
+          return status;
+        }
+
+        return TransformStruct(coded_struct_pointer->struct_type, src_next_offset, dst_next_offset);
+      }
+      default:
+        return ZX_ERR_INVALID_ARGS;
+    }
+  }
+
+  zx_status_t TransformString(const FidlCodedString* coded_string, uint32_t src_offset,
+                              uint32_t dst_offset) {
+    // Copy count.
+    uint64_t count = src_u64(src_offset);
+    dst_u64(dst_offset) = count;
+
+    // Copy presence marker.
+    if (count > 0 && src_u64(src_offset + 8) != FIDL_ALLOC_PRESENT) {
+      error = "expected present marker on non-empty string";
+      return ZX_ERR_INVALID_ARGS;
+    }
+    dst_u64(dst_offset + 8) = src_u64(src_offset + 8);
+
+    // Copy body.
+    uint32_t src_body_offset = src_next_out_of_line_;
+    uint32_t dst_body_offset = dst_next_out_of_line_;
+    zx_status_t status = IncreaseNextOutOfLine(FIDL_ALIGN(count), FIDL_ALIGN(count));
+    if (status != ZX_OK) {
+      return status;
+    }
+    memcpy(&dst_bytes_[dst_body_offset], &src_bytes_[src_body_offset], count);
+    return ZX_OK;
+  }
+
+  zx_status_t TransformVector(const FidlCodedVector* coded_vector, uint32_t src_offset,
+                              uint32_t dst_offset) {
+    // Copy count.
+    uint64_t count = src_u64(src_offset);
+    dst_u64(dst_offset) = count;
+
+    // Copy presence marker.
+    if (count > 0 && src_u64(src_offset + 8) != FIDL_ALLOC_PRESENT) {
+      error = "expected present marker on non-empty vector";
+      return ZX_ERR_INVALID_ARGS;
+    }
+    dst_u64(dst_offset + 8) = src_u64(src_offset + 8);
+
+    uint32_t src_element_size =
+        SRC_VALUE(coded_vector->element_size_v1, coded_vector->element_size_v2);
+    uint32_t dst_element_size =
+        DST_VALUE(coded_vector->element_size_v1, coded_vector->element_size_v2);
+
+    uint32_t src_body_offset = src_next_out_of_line_;
+    uint32_t dst_body_offset = dst_next_out_of_line_;
+    zx_status_t status = IncreaseNextOutOfLine(FIDL_ALIGN(count * src_element_size),
+                                               FIDL_ALIGN(count * dst_element_size));
+    if (status != ZX_OK) {
+      return status;
+    }
+    for (uint32_t i = 0; i < count; i++) {
+      status = TransformInline(coded_vector->element, src_body_offset, dst_body_offset);
+      if (status != ZX_OK) {
+        return status;
+      }
+      src_body_offset += src_element_size;
+      dst_body_offset += dst_element_size;
+    }
+    return ZX_OK;
+  }
+
+  zx_status_t TransformEnvelopeV1ToV2(const fidl_type_t* type, uint32_t src_offset,
+                                      uint32_t dst_offset) {
+    WireEnvelopeV1 src_envelope = src<WireEnvelopeV1>(src_offset);
+
+    switch (src_envelope.presence_marker) {
+      case FIDL_ALLOC_PRESENT:
+        if (src_envelope.num_bytes == 0) {
+          error = "envelope is present but num_bytes is 0";
+          return ZX_ERR_INVALID_ARGS;
+        }
+        break;
+      case FIDL_ALLOC_ABSENT:
+        if (src_envelope.num_bytes == 0) {
+          return ZX_OK;
+        } else {
+          error = "envelope is absent but num_bytes is not 0";
+          return ZX_ERR_INVALID_ARGS;
+        }
+        break;
+      default:
+        error = "invalid presence marker";
+        return ZX_ERR_INVALID_ARGS;
+    }
+    if (src_envelope.num_handles > std::numeric_limits<uint16_t>::max()) {
+      error = "num_handles exceeds the maximum value that fits in a uint16_t";
+      return ZX_ERR_INVALID_ARGS;
+    }
+
+    if (type == nullptr) {
+      // Unknown value.
+      if (src_envelope.num_bytes % 8 != 0) {
+        error = "unknown value contained non 8-byte aligned payload";
+        return ZX_ERR_INVALID_ARGS;
+      }
+
+      dst<WireEnvelopeV2>(dst_offset) = WireEnvelopeV2{
+          .num_bytes = src_envelope.num_bytes,
+          .num_handles = static_cast<uint16_t>(src_envelope.num_handles),
+          .flags = EmptyFlags,
+      };
+      memcpy(&dst_bytes_[dst_next_out_of_line_], &src_bytes_[src_next_out_of_line_],
+             src_envelope.num_bytes);
+      return IncreaseNextOutOfLine(src_envelope.num_bytes, src_envelope.num_bytes);
+    }
+
+    if (TypeSizeV2(type) <= 4) {
+      // Inlined value.
+      WireEnvelopeV2 dst_envelope{
+          .num_handles = static_cast<uint16_t>(src_envelope.num_handles),
+          .flags = InlinedEnvelopeFlag,
+      };
+      memcpy(dst_envelope.inlined_value, &src_bytes_[src_next_out_of_line_],
+             sizeof(dst_envelope.inlined_value));
+      dst<WireEnvelopeV2>(dst_offset) = dst_envelope;
+      return IncreaseNextOutOfLine(8, 0);
+    }
+
+    uint32_t checkpoint_dst_next_out_of_line = dst_next_out_of_line_;
+    zx_status_t status = Transform(type, src_next_out_of_line_, dst_next_out_of_line_);
+    if (status != ZX_OK) {
+      return status;
+    }
+    dst<WireEnvelopeV2>(dst_offset) = WireEnvelopeV2{
+        .num_bytes = dst_next_out_of_line_ - checkpoint_dst_next_out_of_line,
+        .num_handles = static_cast<uint16_t>(src_envelope.num_handles),
+        .flags = EmptyFlags,
+    };
+    return ZX_OK;
+  }
+
+  zx_status_t TransformEnvelopeV2ToV1(const fidl_type_t* type, uint32_t src_offset,
+                                      uint32_t dst_offset) {
+    WireEnvelopeV2 src_envelope = src<WireEnvelopeV2>(src_offset);
+
+    if (!ValidFlags(src_envelope)) {
+      error = "invalid inline marker";
+      return ZX_ERR_INVALID_ARGS;
+    }
+
+    if (IsInlined(src_envelope)) {
+      dst<WireEnvelopeV1>(dst_offset) = WireEnvelopeV1{
+          .num_bytes = 8,
+          .num_handles = src_envelope.num_handles,
+          .presence_marker = FIDL_ALLOC_PRESENT,
+      };
+
+      memcpy(&dst_bytes_[dst_next_out_of_line_], src_envelope.inlined_value,
+             sizeof(src_envelope.inlined_value));
+      memset(&dst_bytes_[dst_next_out_of_line_ + 4], 0, 4);
+      return IncreaseNextOutOfLine(0, 8);
+    }
+
+    if (src_envelope.num_bytes > 0 || src_envelope.num_handles > 0) {
+      if (type != nullptr) {
+        uint32_t checkpoint_dst_next_out_of_line = dst_next_out_of_line_;
+        zx_status_t status = Transform(type, src_next_out_of_line_, dst_next_out_of_line_);
+        if (status != ZX_OK) {
+          return status;
+        }
+
+        dst<WireEnvelopeV1>(dst_offset) = WireEnvelopeV1{
+            .num_bytes = dst_next_out_of_line_ - checkpoint_dst_next_out_of_line,
+            .num_handles = src_envelope.num_handles,
+            .presence_marker = FIDL_ALLOC_PRESENT,
+        };
+        return ZX_OK;
+      }
+
+      // Unknown value.
+      if (src_envelope.num_bytes % 8 != 0) {
+        error = "unknown value contained non 8-byte aligned payload";
+        return ZX_ERR_INVALID_ARGS;
+      }
+
+      dst<WireEnvelopeV1>(dst_offset) = WireEnvelopeV1{
+          .num_bytes = src_envelope.num_bytes,
+          .num_handles = src_envelope.num_handles,
+          .presence_marker = FIDL_ALLOC_PRESENT,
+      };
+      memcpy(&dst_bytes_[dst_next_out_of_line_], &src_bytes_[src_next_out_of_line_],
+             src_envelope.num_bytes);
+
+      return IncreaseNextOutOfLine(src_envelope.num_bytes, src_envelope.num_bytes);
+    }
+
+    dst<WireEnvelopeV1>(dst_offset) = WireEnvelopeV1{
+        .num_bytes = 0,
+        .num_handles = src_envelope.num_handles,
+        .presence_marker = FIDL_ALLOC_ABSENT,
+    };
+    return ZX_OK;
+  }
+
+  zx_status_t TransformEnvelope(const fidl_type_t* type, uint32_t src_offset, uint32_t dst_offset) {
+    if (transformation_ == FIDL_TRANSFORMATION_V2_TO_V1) {
+      return TransformEnvelopeV2ToV1(type, src_offset, dst_offset);
+    } else {
+      return TransformEnvelopeV1ToV2(type, src_offset, dst_offset);
+    }
+  }
+
+  zx_status_t TransformXUnion(const FidlCodedXUnion* coded_xunion, uint32_t src_offset,
+                              uint32_t dst_offset) {
+    uint64_t ordinal = src_u64(src_offset);
+    dst_u64(dst_offset) = ordinal;
+    const fidl_type_t* field_type = nullptr;
+    if (ordinal > 0 && ordinal <= coded_xunion->field_count) {
+      field_type = coded_xunion->fields[ordinal - 1].type;
+    }
+    return TransformEnvelope(field_type, src_offset + sizeof(ordinal),
+                             dst_offset + sizeof(ordinal));
+  }
+
+  zx_status_t TransformTable(const FidlCodedTable* coded_table, uint32_t src_offset,
+                             uint32_t dst_offset) {
+    const WireTableHeader& src_table_header =
+        *reinterpret_cast<const WireTableHeader*>(&src_bytes_[src_offset]);
+    WireTableHeader& dst_table_header =
+        *reinterpret_cast<WireTableHeader*>(&dst_bytes_[dst_offset]);
+
+    if (src_table_header.count > std::numeric_limits<uint32_t>::max()) {
+      error = "count exceeds the maximum value that fits in a uint32_t";
+      return ZX_ERR_INVALID_ARGS;
+    }
+    if (src_table_header.count > 0 && src_table_header.presence_marker != FIDL_ALLOC_PRESENT) {
+      error = "expected present marker on non-empty table";
+      return ZX_ERR_INVALID_ARGS;
+    }
+
+    dst_table_header.count = src_table_header.count;
+    dst_table_header.presence_marker = src_table_header.presence_marker;
+
+    uint32_t src_element_size = SRC_VALUE(16, 8);
+    uint32_t dst_element_size = DST_VALUE(16, 8);
+    uint32_t src_body_offset = src_next_out_of_line_;
+    uint32_t dst_body_offset = dst_next_out_of_line_;
+    zx_status_t status =
+        IncreaseNextOutOfLine(static_cast<uint32_t>(src_table_header.count) * src_element_size,
+                              static_cast<uint32_t>(src_table_header.count) * dst_element_size);
+    if (status != ZX_OK) {
+      return status;
+    }
+
+    // Process the table body.
+    for (uint32_t i = 0; i < src_table_header.count; i++) {
+      const fidl_type_t* field_type = nullptr;
+      if (i < coded_table->field_count) {
+        field_type = coded_table->fields[i].type;
+      }
+      zx_status_t status = TransformEnvelope(field_type, src_body_offset, dst_body_offset);
+      if (status != ZX_OK) {
+        return status;
+      }
+      src_body_offset += src_element_size;
+      dst_body_offset += dst_element_size;
+    }
+    return ZX_OK;
+  }
+
+  zx_status_t IncreaseNextOutOfLineV1V2(uint32_t v1_size, uint32_t v2_size) {
+    return IncreaseNextOutOfLine(SRC_VALUE(v1_size, v2_size), DST_VALUE(v1_size, v2_size));
+  }
+
+  zx_status_t IncreaseNextOutOfLine(uint32_t src_size, uint32_t dst_size) {
+    ZX_ASSERT(src_size % 8 == 0);
+    ZX_ASSERT(dst_size % 8 == 0);
+
+    uint32_t new_src_next_out_of_line;
+    if (add_overflow(src_next_out_of_line_, src_size, &new_src_next_out_of_line)) {
+      error = "overflow in src_next_out_of_line";
+      return ZX_ERR_INVALID_ARGS;
+    }
+    if (new_src_next_out_of_line > src_num_bytes_) {
+      error = "exceeded src array size";
+      return ZX_ERR_INVALID_ARGS;
+    }
+    src_next_out_of_line_ = new_src_next_out_of_line;
+
+    uint32_t new_dst_next_out_of_line;
+    if (add_overflow(dst_next_out_of_line_, dst_size, &new_dst_next_out_of_line)) {
+      error = "overflow in dst_next_out_of_line";
+      return ZX_ERR_INVALID_ARGS;
+    }
+    if (new_dst_next_out_of_line > dst_num_bytes_capacity_) {
+      error = "exceeded dst array size";
+      return ZX_ERR_INVALID_ARGS;
+    }
+    dst_next_out_of_line_ = new_dst_next_out_of_line;
+
+    return ZX_OK;
+  }
+
+  template <typename T>
+  T src(uint32_t offset) {
+    ZX_ASSERT(offset % 8 == 0);
+    // Use memcpy rather than reinterpret_cast to avoid issues
+    // due to the strict aliasing rule.
+    T value;
+    memcpy(&value, &src_bytes_[offset], sizeof(T));
+    return value;
+  }
+
+  uint64_t src_u64(uint32_t offset) { return src<uint64_t>(offset); }
+
+  // Target to facillitate assignment with dst() and dst_u64().
+  // e.g. dst(offset) = value;
+  template <typename T>
+  class DstTarget {
+   public:
+    DstTarget(Transformer* transformer, uint32_t offset)
+        : transformer_(transformer), offset_(offset) {}
+
+    DstTarget& operator=(const T& value) {
+      // Use memcpy rather than reinterpret_cast to avoid issues
+      // due to the strict aliasing rule.
+      memcpy(&transformer_->dst_bytes_[offset_], &value, sizeof(T));
+      return *this;
+    }
+
+   private:
+    Transformer* transformer_;
+    uint32_t offset_;
+  };
+
+  template <typename T>
+  DstTarget<T> dst(uint32_t offset) {
+    ZX_ASSERT(offset % 8 == 0);
+    return DstTarget<T>(this, offset);
+  }
+
+  DstTarget<uint64_t> dst_u64(uint32_t offset) { return dst<uint64_t>(offset); }
+
+  fidl_transformation_t transformation_;
+
+  const uint8_t* src_bytes_;
+  uint8_t* dst_bytes_;
+
+  uint32_t src_next_out_of_line_;
+  uint32_t dst_next_out_of_line_;
+
+  uint32_t src_num_bytes_;
+  uint32_t dst_num_bytes_capacity_;
+
+  const char* error;
+};
+
+}  // namespace
+
 zx_status_t fidl_transform(fidl_transformation_t transformation, const fidl_type_t* type,
                            const uint8_t* src_bytes, uint32_t src_num_bytes, uint8_t* dst_bytes,
                            uint32_t dst_num_bytes_capacity, uint32_t* out_dst_num_bytes,
                            const char** out_error_msg) {
-  // TODO(fxbug.dev/79177) Implement transformation between v1 and v2 wire format.
-  if (dst_num_bytes_capacity < src_num_bytes) {
-    *out_error_msg = "exceeded number of destination bytes";
-    return ZX_ERR_INVALID_ARGS;
+  if (transformation == FIDL_TRANSFORMATION_NONE) {
+    // Fast path - directly copy if no transformation needs to be performed.
+    if (dst_num_bytes_capacity < src_num_bytes) {
+      *out_error_msg = "destination capacity too small";
+      return ZX_ERR_INVALID_ARGS;
+    }
+    memcpy(dst_bytes, src_bytes, src_num_bytes);
+    *out_dst_num_bytes = src_num_bytes;
+    return ZX_OK;
   }
-  memcpy(dst_bytes, src_bytes, src_num_bytes);
-  *out_dst_num_bytes = src_num_bytes;
-  return ZX_OK;
+
+  Transformer transformer(transformation, src_bytes, src_num_bytes, dst_bytes,
+                          dst_num_bytes_capacity);
+  zx_status_t status = transformer.Transform(type, 0, 0);
+  *out_error_msg = transformer.err_msg();
+  *out_dst_num_bytes = transformer.dst_num_bytes();
+  return status;
 }
diff --git a/src/tests/fidl/conformance_suite/conformance.gidl b/src/tests/fidl/conformance_suite/conformance.gidl
index c292c79..dd28ccd 100644
--- a/src/tests/fidl/conformance_suite/conformance.gidl
+++ b/src/tests/fidl/conformance_suite/conformance.gidl
@@ -702,8 +702,7 @@
 }
 
 success("InlineXUnionInStruct") {
-    bindings_denylist = [transformer],
-    value = TestInlineXUnionInStruct {
+        value = TestInlineXUnionInStruct {
         before: "before",
         xu: SampleXUnion {
             u: 0xdeadbeef,
@@ -751,8 +750,7 @@
 }
 
 success("OptionalXUnionInStructAbsent") {
-    bindings_denylist = [transformer],
-    value = TestOptionalXUnionInStruct {
+        value = TestOptionalXUnionInStruct {
         before: "before",
         // no SampleXUnion
         after: "after",
@@ -843,8 +841,7 @@
 }
 
 success("OptionalXUnionInStructPresent") {
-    bindings_denylist = [transformer],
-    value = TestOptionalXUnionInStruct {
+        value = TestOptionalXUnionInStruct {
         before: "before",
         xu: SampleXUnion {
             u: 0xdeadbeef,
@@ -1311,8 +1308,7 @@
 }
 
 success("StrictXUnion") {
-    bindings_denylist = [transformer],
-    value = TestStrictXUnionInStruct {
+        value = TestStrictXUnionInStruct {
         xu: SampleStrictXUnion {
             u: 0xdeadbeef,
         },
diff --git a/src/tests/fidl/conformance_suite/golden.gidl b/src/tests/fidl/conformance_suite/golden.gidl
index 8f5b5e9..ce254d1 100644
--- a/src/tests/fidl/conformance_suite/golden.gidl
+++ b/src/tests/fidl/conformance_suite/golden.gidl
@@ -134,7 +134,6 @@
     },
 }
 success("GoldenUnionStruct") {
-    bindings_denylist = [transformer],
     value = GoldenUnionStruct {
         v: GoldenUnion{
             v: 1,
@@ -154,7 +153,6 @@
     },
 }
 success("GoldenNullableUnionStructNonNull") {
-    bindings_denylist = [transformer],
     value = GoldenNullableUnionStruct {
         v: GoldenUnion{
             v: 1,
@@ -174,7 +172,6 @@
     },
 }
 success("GoldenNullableUnionStructNull") {
-    bindings_denylist = [transformer],
     value = GoldenNullableUnionStruct {
         v: null,
     },
diff --git a/src/tests/fidl/conformance_suite/padding.gidl b/src/tests/fidl/conformance_suite/padding.gidl
index 0d33276..cea6c06e 100644
--- a/src/tests/fidl/conformance_suite/padding.gidl
+++ b/src/tests/fidl/conformance_suite/padding.gidl
@@ -1329,8 +1329,7 @@
 }
 
 success("CorrectPaddedUnionStruct") {
-    bindings_denylist = [transformer],
-    value = PaddedUnionStruct {
+        value = PaddedUnionStruct {
         u: PaddedUnion {
             field: 0x01,
         },
diff --git a/src/tests/fidl/conformance_suite/recursive_depth.gidl b/src/tests/fidl/conformance_suite/recursive_depth.gidl
index 9b744d5..7241f1a 100644
--- a/src/tests/fidl/conformance_suite/recursive_depth.gidl
+++ b/src/tests/fidl/conformance_suite/recursive_depth.gidl
@@ -332,8 +332,7 @@
 }
 
 success("RecursiveOptionalAndUnionUnderLimit") {
-    bindings_denylist = [transformer],
-    value = RecursiveOptionalAndUnionStruct { // 0
+        value = RecursiveOptionalAndUnionStruct { // 0
     u: RecursiveOptionalAndUnion { // 0
     recursive_optional: RecursiveOptionalStruct { // 1
     inner: RecursiveOptionalStruct { // 2
diff --git a/src/tests/fidl/conformance_suite/tables.gidl b/src/tests/fidl/conformance_suite/tables.gidl
index 3b2f7d0..f3a22a6 100644
--- a/src/tests/fidl/conformance_suite/tables.gidl
+++ b/src/tests/fidl/conformance_suite/tables.gidl
@@ -5,13 +5,22 @@
 // Tests tables whose fields are inlined into the envelope in the wire
 // format.
 success("TableFieldInlined") {
-    bindings_allowlist = [go,rust],
+    bindings_allowlist = [go,rust,transformer],
     value = TableFieldInlinedStruct {
         t: TableFieldInlined {
             f: 123,
         },
     },
     bytes = {
+        v1 = [
+            num(1):8,
+            repeat(0xff):8,
+
+            num(8):4, num(0):4,
+            repeat(0xff):8,
+
+            num(123):8,
+        ],
         v2 = [
             num(1):8,
             repeat(0xff):8,
@@ -24,7 +33,7 @@
 // Tests a table whose handle field is inlined into the envelope in the wire
 // format.
 success("TableFieldInlinedHandle") {
-    bindings_allowlist = [go,rust],
+    bindings_allowlist = [go,rust,transformer],
     handle_defs = {
         #0 = channel(),
     },
@@ -34,6 +43,15 @@
         },
     },
     bytes = {
+        v1 = [
+            num(1):8,
+            repeat(0xff):8,
+
+            num(8):4, num(1):4,
+            repeat(0xff):8,
+
+            repeat(0xff):4, padding:4,
+        ],
         v2 = [
             num(1):8,
             repeat(0xff):8,
@@ -42,19 +60,28 @@
         ]
     },
     handles = {
-        v2 = [#0],
+        v1, v2 = [#0],
     }
 }
 
 // Tests tables whose fields are stored out of line in the wire format.
 success("TableFieldOutOfLine") {
-    bindings_allowlist = [go,rust],
+    bindings_allowlist = [go,rust,transformer],
     value = TableFieldOutOfLineStruct {
         t: TableFieldOutOfLine {
             f: 123,
         },
     },
     bytes = {
+        v1 = [
+            num(1):8,
+            repeat(0xff):8,
+
+            num(8):4, num(0):4,
+            repeat(0xff):8,
+
+            num(123):8,
+        ],
         v2 = [
             num(1):8,
             repeat(0xff):8,
@@ -68,7 +95,7 @@
 
 // Tests a table in which a value is inlined into an unknown field.
 success("TableFieldUnknownInlined") {
-    bindings_allowlist = [go,rust],
+    bindings_allowlist = [go,rust,transformer],
     value = TableFieldUnknownStruct {
         t: TableFieldUnknown {
             1: {
@@ -88,7 +115,7 @@
 
 // Tests a table in which a handle value is inlined into an unknown field.
 success("TableFieldUnknownInlinedHandle") {
-    bindings_allowlist = [go,rust],
+    bindings_allowlist = [go,rust,transformer],
     handle_defs = {
         #0 = channel(),
     },
@@ -115,7 +142,7 @@
 
 // Tests a table in which a value is stored out of line in an unknown field.
 success("TableFieldUnknownOutOfLine") {
-    bindings_allowlist = [go,rust],
+    bindings_allowlist = [go,rust,transformer],
     value = TableFieldUnknownStruct {
         t: TableFieldUnknown {
             1: {
@@ -124,6 +151,15 @@
         },
     },
     bytes = {
+        v1 = [
+            num(1):8,
+            repeat(0xff):8,
+
+            num(8):4, num(0):4,
+            repeat(0xff):8,
+
+            num(123):8,
+        ],
         v2 = [
             num(1):8,
             repeat(0xff):8,
@@ -138,7 +174,7 @@
 // Tests decode of a value in a table that can be represented inline in the
 // envelope but is incorrectly using the out of line representation.
 decode_failure("TableOutOfLineEnvelopeWhenInlineRequired") {
-    bindings_allowlist = [go],
+    bindings_allowlist = [go,transformer],
     type = TableFieldInlinedStruct,
     bytes = {
         v2 = [
diff --git a/src/tests/fidl/conformance_suite/transformer.gidl b/src/tests/fidl/conformance_suite/transformer.gidl
index a4166ad..fae4b6ec 100644
--- a/src/tests/fidl/conformance_suite/transformer.gidl
+++ b/src/tests/fidl/conformance_suite/transformer.gidl
@@ -32,8 +32,7 @@
 }
 
 success("Sandwich1Case1WithHdr") {
-    bindings_denylist = [transformer],
-    value = Sandwich1Message {
+        value = Sandwich1Message {
         header: TransactionHeader {
             tx_id: 0xf3f2f1f0,
             flags: [0xf4, 0xf5, 0xf6],
@@ -92,8 +91,7 @@
 }
 
 success("Sandwich1WithOptUnionPresent") {
-    bindings_denylist = [transformer],
-    value = Sandwich1WithOptUnion {
+        value = Sandwich1WithOptUnion {
         before: 0x04030201,
         opt_union: UnionSize8Align4 {
             variant: 0x0c0b0a09,
@@ -134,8 +132,7 @@
 }
 
 success("Sandwich1WithOptUnionAbsent") {
-    bindings_denylist = [transformer],
-    value = Sandwich1WithOptUnion {
+        value = Sandwich1WithOptUnion {
         before: 0x04030201,
         after: 0x08070605,
     },
@@ -170,8 +167,7 @@
 }
 
 success("Sandwich2Case1") {
-    bindings_denylist = [transformer],
-    value = Sandwich2 {
+        value = Sandwich2 {
         before: 0x04030201,
         the_union: UnionSize12Align4 {
             variant: [0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5],
@@ -215,8 +211,7 @@
 }
 
 success("Sandwich3Case1") {
-    bindings_denylist = [transformer],
-    value = Sandwich3 {
+        value = Sandwich3 {
         before: 0x04030201,
         the_union: UnionSize24Align8 {
             variant: StructSize16Align8 {
@@ -267,8 +262,7 @@
 }
 
 success("Sandwich4Case1") {
-    bindings_denylist = [transformer],
-    value = Sandwich4 {
+        value = Sandwich4 {
         before: 0x04030201,
         the_union: UnionSize36Align4 {
             variant: [
@@ -333,8 +327,7 @@
 }
 
 success("Sandwich4Case1WithHdr") {
-    bindings_denylist = [transformer],
-    value = Sandwich4Message {
+        value = Sandwich4Message {
         header: TransactionHeader {
             tx_id: 0,
             flags: [0, 0, 0],
@@ -417,8 +410,7 @@
 }
 
 success("Sandwich5Case1") {
-    bindings_denylist = [transformer],
-    value = Sandwich5 {
+        value = Sandwich5 {
         before: 0x04030201,
         union_of_union: UnionOfUnion {
             size8align4: UnionSize8Align4 {
@@ -473,8 +465,7 @@
 }
 
 success("Sandwich5Case1WithHdr") {
-    bindings_denylist = [transformer],
-    value = Sandwich5Message {
+        value = Sandwich5Message {
         header: TransactionHeader {
             tx_id: 0xf3f2f1f0,
             flags: [0xf4, 0xf5, 0xf6],
@@ -547,8 +538,7 @@
 }
 
 success("Sandwich5Case2") {
-    bindings_denylist = [transformer],
-    value = Sandwich5 {
+        value = Sandwich5 {
         before: 0x04030201,
         union_of_union: UnionOfUnion {
             size24align8: UnionSize24Align8 {
@@ -613,8 +603,7 @@
 }
 
 success("Sandwich5Case2WithHdr") {
-    bindings_denylist = [transformer],
-    value = Sandwich5Message {
+        value = Sandwich5Message {
         header: TransactionHeader {
             tx_id: 0xf3f2f1f0,
             flags: [0xf4, 0xf5, 0xf6],
@@ -697,8 +686,7 @@
 }
 
 success("Sandwich6Case1") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             vector_of_uint8: [
@@ -754,8 +742,7 @@
 }
 
 success("Sandwich6Case1AbsentVector") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             vector_of_uint8: []
@@ -803,8 +790,7 @@
 }
 
 success("Sandwich6Case2") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             s: "soft migrations rock!",
@@ -866,8 +852,7 @@
 }
 
 success("Sandwich6Case3") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             vector_s3_a1: [
@@ -935,8 +920,7 @@
 }
 
 success("Sandwich6Case4") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             vector_s3_a2: [
@@ -1007,8 +991,7 @@
 }
 
 success("Sandwich6Case5") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             // TODO(fxbug.dev/36441): Support handles in GIDL.
@@ -1071,8 +1054,7 @@
 }
 
 success("Sandwich6Case6") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             array_s3_a1: [
@@ -1127,8 +1109,7 @@
 }
 
 success("Sandwich6Case7") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             array_s3_a2: [
@@ -1181,8 +1162,7 @@
 }
 
 success("Sandwich6Case8") {
-    bindings_denylist = [transformer],
-    value = Sandwich6 {
+        value = Sandwich6 {
         before: 0x04030201,
         the_union: UnionWithVector {
             vector_union: [
@@ -1249,8 +1229,7 @@
 }
 
 success("Sandwich7Case1") {
-    bindings_denylist = [transformer],
-    value = Sandwich7 {
+        value = Sandwich7 {
         before: 0x14131211,
         opt_sandwich1: Sandwich1 {
             before: 0x04030201,
@@ -1305,8 +1284,7 @@
 }
 
 success("Sandwich7Case1WithHdr") {
-    bindings_denylist = [transformer],
-    value = Sandwich7Message {
+        value = Sandwich7Message {
         header: TransactionHeader {
             tx_id: 0xf3f2f1f0,
             flags: [0xf4, 0xf5, 0xf6],
@@ -1854,8 +1832,7 @@
 }
 
 success("ArrayStruct") {
-    bindings_denylist = [transformer],
-    value = ArrayStruct {
+        value = ArrayStruct {
         unions: [
             StringUnion {
                 s: "one"
@@ -2082,8 +2059,7 @@
 }
 
 success("NoCodingTablesStressor") {
-    bindings_denylist = [transformer],
-    value = NoCodingTablesStressor {
+        value = NoCodingTablesStressor {
         f1: 0x1111111111111111,
         f2: 0x2222222222222222,
         u1: UnionSize36Align4 {
@@ -2279,8 +2255,7 @@
 }
 
 success("OutOfLineSandwich1Case1") {
-    bindings_denylist = [transformer],
-    value = OutOfLineSandwich1 {
+        value = OutOfLineSandwich1 {
         before: "soft migrations rock!",
         v: [
             Sandwich1 {
@@ -2382,8 +2357,7 @@
 }
 
 success("OutOfLineSandwich1WithOptUnionPresent") {
-    bindings_denylist = [transformer],
-    value = OutOfLineSandwich1WithOptUnion {
+        value = OutOfLineSandwich1WithOptUnion {
         before: "soft migrations rock!",
         v: [
             Sandwich1WithOptUnion {
@@ -2486,8 +2460,7 @@
 }
 
 success("OutOfLineSandwich1WithOptUnionAbsent") {
-    bindings_denylist = [transformer],
-    value = OutOfLineSandwich1WithOptUnion {
+        value = OutOfLineSandwich1WithOptUnion {
         before: "soft migrations rock!",
         v: [
             Sandwich1WithOptUnion {
@@ -2757,8 +2730,7 @@
 }
 
 success("Regression8OptUnionSize12Aligned4") {
-    bindings_denylist = [transformer],
-    value = Regression8OptUnionSize12Align4 {
+        value = Regression8OptUnionSize12Align4 {
         opt_union1: UnionSize12Align4 {
             variant: [0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6],
         },
@@ -2831,8 +2803,7 @@
 }
 
 success("Regression8VectorOfOptUnionSize12Aligned4") {
-    bindings_denylist = [transformer],
-    value = Regression8VectorOfOptUnionSize12Align4 {
+        value = Regression8VectorOfOptUnionSize12Align4 {
         value: [
             null,
             UnionSize12Align4 {
@@ -3136,8 +3107,7 @@
 }
 
 success("Sandwich4Align8") {
-    bindings_denylist = [transformer],
-    value = Sandwich4Align8 {
+        value = Sandwich4Align8 {
         sandwich4: Sandwich4 {
             before: 0x04030201,
             the_union: UnionSize36Align4 {
@@ -3211,8 +3181,7 @@
 }
 
 success("Sandwich4Align8WithPointer") {
-    bindings_denylist = [transformer],
-    value = Sandwich4Align8WithPointer {
+        value = Sandwich4Align8WithPointer {
         sandwich4: Sandwich4 {
             before: 0x04030201,
             the_union: UnionSize36Align4 {
@@ -3294,8 +3263,7 @@
 }
 
 success("Sandwich8Case1") {
-    bindings_denylist = [transformer],
-    value = Sandwich8 {
+        value = Sandwich8 {
         before: 0x04030201,
         union_of_union: UnionOfUnion {
             size8align4: UnionSize8Align4 {
@@ -3350,8 +3318,7 @@
 }
 
 success("Sandwich9Case1") {
-    bindings_denylist = [transformer],
-    value = Sandwich9 {
+        value = Sandwich9 {
         before: 0x0201,
         the_union: UnionWithVectorOfVectors {
             v: [[
@@ -3461,8 +3428,7 @@
 }
 
 success("StringUnionVector") {
-    bindings_denylist = [transformer],
-    value = StringUnionVector {
+        value = StringUnionVector {
         the_vector: [
             StringUnion {
                 s: "hello",
@@ -3587,8 +3553,7 @@
 }
 
 success("RegressionNoUnionLauncherCreateComponentRequest") {
-    bindings_denylist = [transformer],
-    value = CreateComponentRequest {
+        value = CreateComponentRequest {
         launch_info: LaunchInfo {
             url: "fuchsia-pkg://fuchsia.com/fidl_compatibility_test_server_rust_write_xunion#meta/fidl_compatibility_test_server_rust_write_xunion.cmx",
             // TODO(fxbug.dev/36441): Support handles in GIDL.
diff --git a/src/tests/fidl/conformance_suite/union.gidl b/src/tests/fidl/conformance_suite/union.gidl
index 92b4050..96a7ee6 100644
--- a/src/tests/fidl/conformance_suite/union.gidl
+++ b/src/tests/fidl/conformance_suite/union.gidl
@@ -3,8 +3,7 @@
 // found in the LICENSE file.
 
 success("UnionWithBoundString") {
-    bindings_denylist = [transformer],
-    value = UnionWithBoundStringStruct {
+        value = UnionWithBoundStringStruct {
         v: UnionWithBoundString {
             boundFiveStr: "abcd",
         },
@@ -47,8 +46,7 @@
 }
 
 success("UnionMigration_SingleVariant") {
-    bindings_denylist = [transformer],
-    value = SingleVariantUnionStruct {
+        value = SingleVariantUnionStruct {
         u: SingleVariantUnion {
             x: 42,
         },
@@ -338,7 +336,6 @@
 }
 
 success("UnionEnvelopeInlining_SmallValue_Inlined") {
-    bindings_denylist = [transformer],
     value = EnvelopeInliningTestUnionStruct {
         u: EnvelopeInliningTestUnion {
             small: 100,
@@ -363,7 +360,7 @@
 
 success("UnionEnvelopeInlining_LargeValue_Outlined") {
     // TODO(fxbug.dev/80658) Determine why this test is failing in the fuzzer.
-    bindings_denylist = [transformer, fuzzer_corpus],
+    bindings_denylist = [fuzzer_corpus],
     value = EnvelopeInliningTestUnionStruct {
         u: EnvelopeInliningTestUnion {
             large: 100,
@@ -390,7 +387,6 @@
 }
 
 success("UnionEnvelopeInlining_HandleValue_Inlined") {
-    bindings_denylist = [transformer],
     handle_defs = {
         #0 = event(),
     },
@@ -768,8 +764,7 @@
 }
 
 success("OptionalFlexibleValueUnionZeroOrdinalNoPayload") {
-    bindings_denylist = [transformer],
-    value = TestOptionalFlexibleXUnionInStruct {
+        value = TestOptionalFlexibleXUnionInStruct {
         xu: null,
     },
     bytes = {
@@ -804,8 +799,7 @@
 }
 
 success("OptionalFlexibleResourceUnionZeroOrdinalNoPayload") {
-    bindings_denylist = [transformer],
-    value = TestOptionalFlexibleResourceXUnionInStruct {
+        value = TestOptionalFlexibleResourceXUnionInStruct {
         xu: null,
     },
     bytes = {