[fidl] Initial support for extensible unions.

Implements extensible unions ("xunions") described in FTP-015. This
depends on the zircon xunion CL
<https://fuchsia-review.googlesource.com/c/zircon/+/237693>.

Test: fx build-zircon -A -H -l && fx build host_x64/fidl_cpp_host_unittests && $FUCHSIA_DIR/out/x64/host_x64/fidl_cpp_host_unittests
Test: fx build garnet/go/src/fidl && $FUCHSIA_DIR/garnet/go/src/fidl/compiler/backend/typestest/regen.sh

Change-Id: I446149066d46fa11c5a29a7daf342bd9bfc22c07
diff --git a/go/src/fidl/compiler/backend/cpp/generator.go b/go/src/fidl/compiler/backend/cpp/generator.go
index dfa9b57..1ff2841 100644
--- a/go/src/fidl/compiler/backend/cpp/generator.go
+++ b/go/src/fidl/compiler/backend/cpp/generator.go
@@ -33,6 +33,7 @@
 	template.Must(tmpls.Parse(templates.Table))
 	template.Must(tmpls.Parse(templates.TestBase))
 	template.Must(tmpls.Parse(templates.Union))
+	template.Must(tmpls.Parse(templates.XUnion))
 	return &FidlGenerator{
 		tmpls: tmpls,
 	}
diff --git a/go/src/fidl/compiler/backend/cpp/ir/ir.go b/go/src/fidl/compiler/backend/cpp/ir/ir.go
index 7f97ad3..a898aa7 100644
--- a/go/src/fidl/compiler/backend/cpp/ir/ir.go
+++ b/go/src/fidl/compiler/backend/cpp/ir/ir.go
@@ -26,6 +26,7 @@
 type structKind struct{}
 type tableKind struct{}
 type unionKind struct{}
+type xunionKind struct{}
 
 var Kinds = struct {
 	Const     constKind
@@ -34,6 +35,7 @@
 	Struct    structKind
 	Table     tableKind
 	Union     unionKind
+	XUnion    xunionKind
 }{}
 
 type Decl interface{}
@@ -89,6 +91,28 @@
 	Offset      int
 }
 
+type XUnion struct {
+	types.Attributes
+	Namespace    string
+	Name         string
+	TableType    string
+	Members      []XUnionMember
+	Size         int
+	MaxHandles   int
+	MaxOutOfLine int
+	Kind         xunionKind
+}
+
+type XUnionMember struct {
+	types.Attributes
+	Ordinal     int
+	Type        Type
+	Name        string
+	StorageName string
+	TagName     string
+	Offset      int
+}
+
 type Table struct {
 	types.Attributes
 	Namespace      string
@@ -113,6 +137,7 @@
 	MethodHasName     string
 	MethodClearName   string
 	ValueUnionName    string
+	ValueXUnionName   string
 }
 
 type Struct struct {
@@ -294,6 +319,7 @@
 	"while":            true,
 	"xor":              true,
 	"xor_eq":           true,
+	"xunion":           true,
 
 	// names used in specific contexts e.g. union accessors
 	"which":           true,
@@ -478,6 +504,8 @@
 		case types.TableDeclType:
 			fallthrough
 		case types.UnionDeclType:
+			fallthrough
+		case types.XUnionDeclType:
 			if val.Nullable {
 				r.Decl = fmt.Sprintf("::std::unique_ptr<%s>", t)
 				r.LLDecl = fmt.Sprintf("%s*", t)
@@ -743,12 +771,12 @@
 func (c *compiler) compileUnionMember(val types.UnionMember) UnionMember {
 	n := changeIfReserved(val.Name, "")
 	return UnionMember{
-		val.Attributes,
-		c.compileType(val.Type),
-		n,
-		changeIfReserved(val.Name, "_"),
-		fmt.Sprintf("k%s", common.ToUpperCamelCase(n)),
-		val.Offset,
+		Attributes:  val.Attributes,
+		Type:        c.compileType(val.Type),
+		Name:        n,
+		StorageName: changeIfReserved(val.Name, "_"),
+		TagName:     fmt.Sprintf("k%s", common.ToUpperCamelCase(n)),
+		Offset:      val.Offset,
 	}
 }
 
@@ -772,6 +800,38 @@
 	return r
 }
 
+func (c *compiler) compileXUnionMember(val types.XUnionMember) XUnionMember {
+	n := changeIfReserved(val.Name, "")
+	return XUnionMember{
+		Attributes:  val.Attributes,
+		Ordinal:     val.Ordinal,
+		Type:        c.compileType(val.Type),
+		Name:        n,
+		StorageName: changeIfReserved(val.Name, "_"),
+		TagName:     fmt.Sprintf("k%s", common.ToUpperCamelCase(n)),
+		Offset:      val.Offset,
+	}
+}
+
+func (c *compiler) compileXUnion(val types.XUnion) XUnion {
+	name := c.compileCompoundIdentifier(val.Name, "")
+	r := XUnion{
+		Attributes:   val.Attributes,
+		Namespace:    c.namespace,
+		Name:         name,
+		TableType:    fmt.Sprintf("%s_%sTable", c.symbolPrefix, name),
+		Size:         val.Size,
+		MaxHandles:   val.MaxHandles,
+		MaxOutOfLine: val.MaxOutOfLine,
+	}
+
+	for _, v := range val.Members {
+		r.Members = append(r.Members, c.compileXUnionMember(v))
+	}
+
+	return r
+}
+
 func Compile(r types.Root) Root {
 	root := Root{}
 	library := types.ParseLibraryName(r.Name)
@@ -825,6 +885,11 @@
 		decls[v.Name] = &d
 	}
 
+	for _, v := range r.XUnions {
+		d := c.compileXUnion(v)
+		decls[v.Name] = &d
+	}
+
 	for _, v := range r.DeclOrder {
 		d := decls[v]
 		if d == nil {
diff --git a/go/src/fidl/compiler/backend/cpp/templates/header.tmpl.go b/go/src/fidl/compiler/backend/cpp/templates/header.tmpl.go
index 58d4e91..cfbe4fe 100644
--- a/go/src/fidl/compiler/backend/cpp/templates/header.tmpl.go
+++ b/go/src/fidl/compiler/backend/cpp/templates/header.tmpl.go
@@ -27,6 +27,7 @@
 {{- if Eq .Kind Kinds.Struct }}{{ template "StructForwardDeclaration" . }}{{- end }}
 {{- if Eq .Kind Kinds.Table }}{{ template "TableForwardDeclaration" . }}{{- end }}
 {{- if Eq .Kind Kinds.Union }}{{ template "UnionForwardDeclaration" . }}{{- end }}
+{{- if Eq .Kind Kinds.XUnion }}{{ template "XUnionForwardDeclaration" . }}{{- end }}
 {{- end }}
 
 {{- range .Decls }}
@@ -35,6 +36,7 @@
 {{- if Eq .Kind Kinds.Struct }}{{ template "StructDeclaration" . }}{{- end }}
 {{- if Eq .Kind Kinds.Table }}{{ template "TableDeclaration" . }}{{- end }}
 {{- if Eq .Kind Kinds.Union }}{{ template "UnionDeclaration" . }}{{- end }}
+{{- if Eq .Kind Kinds.XUnion }}{{ template "XUnionDeclaration" . }}{{- end }}
 {{- end }}
 
 {{- range .LibraryReversed }}
@@ -49,6 +51,7 @@
 {{- if Eq .Kind Kinds.Struct }}{{ template "StructTraits" . }}{{- end }}
 {{- if Eq .Kind Kinds.Table }}{{ template "TableTraits" . }}{{- end }}
 {{- if Eq .Kind Kinds.Union }}{{ template "UnionTraits" . }}{{- end }}
+{{- if Eq .Kind Kinds.XUnion }}{{ template "XUnionTraits" . }}{{- end }}
 {{- end -}}
 
 }  // namespace fidl
diff --git a/go/src/fidl/compiler/backend/cpp/templates/implementation.tmpl.go b/go/src/fidl/compiler/backend/cpp/templates/implementation.tmpl.go
index c0dfd76..d221a60 100644
--- a/go/src/fidl/compiler/backend/cpp/templates/implementation.tmpl.go
+++ b/go/src/fidl/compiler/backend/cpp/templates/implementation.tmpl.go
@@ -22,6 +22,7 @@
 {{- if Eq .Kind Kinds.Interface }}{{ template "InterfaceDefinition" . }}{{- end }}
 {{- if Eq .Kind Kinds.Struct }}{{ template "StructDefinition" . }}{{- end }}
 {{- if Eq .Kind Kinds.Union }}{{ template "UnionDefinition" . }}{{- end }}
+{{- if Eq .Kind Kinds.XUnion }}{{ template "XUnionDefinition" . }}{{- end }}
 {{- if Eq .Kind Kinds.Table }}{{ template "TableDefinition" . }}{{- end }}
 {{- end }}
 
diff --git a/go/src/fidl/compiler/backend/cpp/templates/table.tmpl.go b/go/src/fidl/compiler/backend/cpp/templates/table.tmpl.go
index c56a3b8..c810969 100644
--- a/go/src/fidl/compiler/backend/cpp/templates/table.tmpl.go
+++ b/go/src/fidl/compiler/backend/cpp/templates/table.tmpl.go
@@ -80,6 +80,9 @@
   {{- end }}
 
   {{- range .Members }}
+  {{/* The raw values of a table field are placed inside a union to ensure
+       that they're not initialized (since table fields are optional by
+       default). Placement new must be used to initialize the value. */ -}}
   union {{ .ValueUnionName }} {
     {{ .ValueUnionName }}() {}
     ~{{ .ValueUnionName }}() {}
diff --git a/go/src/fidl/compiler/backend/cpp/templates/xunion.tmpl.go b/go/src/fidl/compiler/backend/cpp/templates/xunion.tmpl.go
new file mode 100644
index 0000000..ae8371f
--- /dev/null
+++ b/go/src/fidl/compiler/backend/cpp/templates/xunion.tmpl.go
@@ -0,0 +1,281 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package templates
+
+const XUnion = `
+{{- define "XUnionForwardDeclaration" }}
+class {{ .Name }};
+{{- end }}
+
+{{- define "XUnionDeclaration" }}
+{{range .DocComments}}
+//{{ . }}
+{{- end}}
+class {{ .Name }} {
+ public:
+ static const fidl_type_t* FidlType;
+
+ {{ .Name }}();
+  ~{{ .Name }}();
+
+  {{ .Name }}({{ .Name }}&&);
+  {{ .Name }}& operator=({{ .Name }}&&);
+
+  enum Tag : fidl_xunion_tag_t {
+    Empty = 0,
+  {{- range .Members }}
+    {{ .TagName }} = {{ .Ordinal }},
+  {{- end }}
+  };
+
+  static inline ::std::unique_ptr<{{ .Name }}> New() { return ::std::make_unique<{{ .Name }}>(); }
+
+  void Encode(::fidl::Encoder* encoder, size_t offset);
+  static void Decode(::fidl::Decoder* decoder, {{ .Name }}* value, size_t offset);
+  zx_status_t Clone({{ .Name }}* result) const;
+
+  bool empty() const { return tag_ == Tag::Empty; }
+
+  {{- range .Members }}
+
+  bool is_{{ .Name }}() const { return tag_ == {{ .Ordinal }}; }
+  {{range .DocComments}}
+  //{{ . }}
+  {{- end}}
+  {{ .Type.Decl }}& {{ .Name }}() {
+    EnsureStorageInitialized({{ .Ordinal }});
+    return {{ .StorageName }};
+  }
+  {{range .DocComments}}
+  //{{ . }}
+  {{- end}}
+  const {{ .Type.Decl }}& {{ .Name }}() const { return {{ .StorageName }}; }
+  void set_{{ .Name }}({{ .Type.Decl }} value);
+  {{- end }}
+
+  Tag Which() const { return Tag(tag_); }
+
+ private:
+  friend bool operator==(const {{ .Name }}& lhs, const {{ .Name }}& rhs);
+  void Destroy();
+  void EnsureStorageInitialized(::fidl_xunion_tag_t tag);
+
+  ::fidl_xunion_tag_t tag_ = Tag::Empty;
+  union {
+  {{- range .Members }}
+    {{ .Type.Decl }} {{ .StorageName }};
+  {{- end }}
+  };
+};
+
+bool operator==(const {{ .Name }}& lhs, const {{ .Name }}& rhs);
+inline bool operator!=(const {{ .Name }}& lhs, const {{ .Name }}& rhs) {
+  return !(lhs == rhs);
+}
+
+inline zx_status_t Clone(const {{ .Namespace }}::{{ .Name }}& value,
+                         {{ .Namespace }}::{{ .Name }}* result) {
+  return value.Clone(result);
+}
+
+using {{ .Name }}Ptr = ::std::unique_ptr<{{ .Name }}>;
+{{- end }}
+
+{{- define "XUnionDefinition" }}
+extern "C" const fidl_type_t {{ .TableType }};
+const fidl_type_t* {{ .Name }}::FidlType = &{{ .TableType }};
+
+{{ .Name }}::{{ .Name }}() {}
+
+{{ .Name }}::~{{ .Name }}() {
+  Destroy();
+}
+
+{{ .Name }}::{{ .Name }}({{ .Name }}&& other) : tag_(other.tag_) {
+  switch (tag_) {
+  {{- range .Members }}
+   case {{ .Ordinal }}:
+    {{- if .Type.Dtor }}
+    new (&{{ .StorageName }}) {{ .Type.Decl }}();
+    {{- end }}
+    {{ .StorageName }} = std::move(other.{{ .StorageName }});
+    break;
+  {{- end }}
+   default:
+    break;
+  }
+}
+
+{{ .Name }}& {{ .Name }}::operator=({{ .Name }}&& other) {
+  if (this != &other) {
+    Destroy();
+    tag_ = other.tag_;
+    switch (tag_) {
+    {{- range .Members }}
+     case {{ .Ordinal }}:
+      {{- if .Type.Dtor }}
+      new (&{{ .StorageName }}) {{ .Type.Decl }}();
+      {{- end }}
+      {{ .StorageName }} = std::move(other.{{ .StorageName }});
+      break;
+    {{- end }}
+     default:
+      break;
+    }
+  }
+  return *this;
+}
+
+void {{ .Name }}::Encode(::fidl::Encoder* encoder, size_t offset) {
+  const size_t length_before = encoder->CurrentLength();
+  const size_t handles_before = encoder->CurrentHandleCount();
+
+  size_t envelope_offset = 0;
+
+  switch (tag_) {
+    {{- range .Members }}
+    case {{ .Ordinal }}: {
+      envelope_offset = encoder->Alloc(::fidl::CodingTraits<{{ .Type.Decl }}>::encoded_size);
+      ::fidl::Encode(encoder, &{{ .StorageName }}, envelope_offset);
+      break;
+    }
+    {{- end }}
+    case Tag::Empty:
+    default:
+       break;
+  }
+
+  {{/* Note that encoder->GetPtr() must be called after every call to
+       encoder->Alloc(), since encoder.bytes_ could be re-sized and moved.
+     */ -}}
+
+  fidl_xunion_t* xunion = encoder->GetPtr<fidl_xunion_t>(offset);
+  assert(xunion->envelope.presence == FIDL_ALLOC_ABSENT);
+
+  if (envelope_offset) {
+    xunion->tag = tag_;
+    xunion->envelope.num_bytes = encoder->CurrentLength() - length_before;
+    xunion->envelope.num_handles = encoder->CurrentHandleCount() - handles_before;
+    xunion->envelope.presence = FIDL_ALLOC_PRESENT;
+  }
+}
+
+void {{ .Name }}::Decode(::fidl::Decoder* decoder, {{ .Name }}* value, size_t offset) {
+  fidl_xunion_t* xunion = decoder->GetPtr<fidl_xunion_t>(offset);
+
+  if (!xunion->envelope.data) {
+    value->EnsureStorageInitialized(Tag::Empty);
+    return;
+  }
+
+  value->EnsureStorageInitialized(xunion->tag);
+
+  const size_t envelope_offset = decoder->GetOffset(xunion->envelope.data);
+
+  switch (value->tag_) {
+  {{- range .Members }}
+   case {{ .Ordinal }}:
+    {{- if .Type.Dtor }}
+    new (&value->{{ .StorageName }}) {{ .Type.Decl }}();
+    {{- end }}
+    ::fidl::Decode(decoder, &value->{{ .StorageName }}, envelope_offset);
+    break;
+  {{- end }}
+   default:
+    {{/* The decoder doesn't have a schema for this tag, so it simply does
+         nothing. The generated code doesn't need to update the offsets to
+         "skip" the secondary object nor claim handles, since BufferWalker does
+         that. */ -}}
+    break;
+  }
+}
+
+zx_status_t {{ .Name }}::Clone({{ .Name }}* result) const {
+  result->Destroy();
+  result->tag_ = tag_;
+  switch (tag_) {
+    {{- range .Members }}
+    case {{ .Ordinal }}:
+      {{- if .Type.Dtor }}
+      new (&result->{{ .StorageName }}) {{ .Type.Decl }}();
+      {{- end }}
+      return ::fidl::Clone({{ .StorageName }}, &result->{{ .StorageName }});
+    {{- end }}
+    default:
+      return ZX_OK;
+  }
+}
+
+bool operator==(const {{ .Name }}& lhs, const {{ .Name }}& rhs) {
+  if (lhs.tag_ != rhs.tag_) {
+    return false;
+  }
+  switch (lhs.tag_) {
+    {{- range .Members }}
+    case {{ .Ordinal }}:
+      return ::fidl::Equals(lhs.{{ .StorageName }}, rhs.{{ .StorageName }});
+    {{- end }}
+    case {{ .Name }}::Tag::Empty:
+      return true;
+    default:
+      return false;
+  }
+}
+
+{{- range $member := .Members }}
+
+void {{ $.Name }}::set_{{ .Name }}({{ .Type.Decl }} value) {
+  EnsureStorageInitialized({{ .Ordinal }});
+  {{ .StorageName }} = std::move(value);
+}
+
+{{- end }}
+
+void {{ .Name }}::Destroy() {
+  switch (tag_) {
+  {{- range .Members }}
+   case {{ .Ordinal }}:
+    {{- if .Type.Dtor }}
+    {{ .StorageName }}.{{ .Type.Dtor }}();
+    {{- end }}
+    break;
+  {{- end }}
+   default:
+    break;
+  }
+  tag_ = Tag::Empty;
+}
+
+void {{ .Name }}::EnsureStorageInitialized(::fidl_xunion_tag_t tag) {
+  if (tag_ != tag) {
+    Destroy();
+    tag_ = tag;
+    switch (tag_) {
+      {{- range .Members }}
+      {{- if .Type.Dtor }}
+      case {{ .Ordinal }}:
+        new (&{{ .StorageName }}) {{ .Type.Decl }}();
+        break;
+      {{- end }}
+      {{- end }}
+      default:
+        break;
+    }
+  }
+}
+
+{{- end }}
+
+{{- define "XUnionTraits" }}
+template <>
+struct CodingTraits<{{ .Namespace }}::{{ .Name }}>
+    : public EncodableCodingTraits<{{ .Namespace }}::{{ .Name }}, {{ .Size }}> {};
+
+inline zx_status_t Clone(const {{ .Namespace }}::{{ .Name }}& value,
+                         {{ .Namespace }}::{{ .Name }}* result) {
+  return {{ .Namespace }}::Clone(value, result);
+}
+{{- end }}
+`
diff --git a/go/src/fidl/compiler/backend/types/types.go b/go/src/fidl/compiler/backend/types/types.go
index a76b8df..4e82e12 100644
--- a/go/src/fidl/compiler/backend/types/types.go
+++ b/go/src/fidl/compiler/backend/types/types.go
@@ -329,6 +329,28 @@
 	MaxOutOfLine int        `json:"max_out_of_line"`
 }
 
+// XUnion represents the declaration of a FIDL extensible union.
+type XUnion struct {
+	Attributes
+	Name         EncodedCompoundIdentifier `json:"name"`
+	Members      []XUnionMember            `json:"members"`
+	Size         int                       `json:"size"`
+	Alignment    int                       `json:"alignment"`
+	MaxHandles   int                       `json:"max_handles"`
+	MaxOutOfLine int                       `json:"max_out_of_line"`
+}
+
+// XUnionMember represents the declaration of a field in a FIDL extensible
+// xunion.
+type XUnionMember struct {
+	Attributes
+	Ordinal      int        `json:"ordinal"`
+	Type         Type       `json:"type"`
+	Name         Identifier `json:"name"`
+	Offset       int        `json:"offset"`
+	MaxOutOfLine int        `json:"max_out_of_line"`
+}
+
 // Table represents a declaration of a FIDL table.
 type Table struct {
 	Attributes
@@ -478,6 +500,7 @@
 	StructDeclType             = "struct"
 	TableDeclType              = "table"
 	UnionDeclType              = "union"
+	XUnionDeclType             = "xunion"
 )
 
 type DeclMap map[EncodedCompoundIdentifier]DeclType
@@ -498,6 +521,7 @@
 	Structs    []Struct                    `json:"struct_declarations,omitempty"`
 	Tables     []Table                     `json:"table_declarations,omitempty"`
 	Unions     []Union                     `json:"union_declarations,omitempty"`
+	XUnions    []XUnion                    `json:"xunion_declarations,omitempty"`
 	DeclOrder  []EncodedCompoundIdentifier `json:"declaration_order,omitempty"`
 	Decls      DeclMap                     `json:"declarations,omitempty"`
 	Libraries  []Library                   `json:"library_dependencies,omitempty"`
diff --git a/go/src/fidl/compiler/backend/typestest/doc_comments.fidl.json b/go/src/fidl/compiler/backend/typestest/doc_comments.fidl.json
index 5a3b1c4..f4916be 100644
--- a/go/src/fidl/compiler/backend/typestest/doc_comments.fidl.json
+++ b/go/src/fidl/compiler/backend/typestest/doc_comments.fidl.json
@@ -160,6 +160,7 @@
       "max_handles": 0
     }
   ],
+  "xunion_declarations": [],
   "declaration_order": [
     "test.name/Interface",
     "test.name/Struct",
diff --git a/go/src/fidl/compiler/backend/typestest/empty_struct.fidl.json b/go/src/fidl/compiler/backend/typestest/empty_struct.fidl.json
index 590debd..0910a82 100644
--- a/go/src/fidl/compiler/backend/typestest/empty_struct.fidl.json
+++ b/go/src/fidl/compiler/backend/typestest/empty_struct.fidl.json
@@ -18,6 +18,7 @@
   ],
   "table_declarations": [],
   "union_declarations": [],
+  "xunion_declarations": [],
   "declaration_order": [
     "test.name/Empty"
   ],
diff --git a/go/src/fidl/compiler/backend/typestest/ordinal_switch.fidl.json b/go/src/fidl/compiler/backend/typestest/ordinal_switch.fidl.json
index 0490203..c254758 100644
--- a/go/src/fidl/compiler/backend/typestest/ordinal_switch.fidl.json
+++ b/go/src/fidl/compiler/backend/typestest/ordinal_switch.fidl.json
@@ -54,6 +54,7 @@
   "struct_declarations": [],
   "table_declarations": [],
   "union_declarations": [],
+  "xunion_declarations": [],
   "declaration_order": [
     "test.name/SwitchingOrdinals"
   ],
diff --git a/go/src/fidl/compiler/backend/typestest/tables.fidl.json b/go/src/fidl/compiler/backend/typestest/tables.fidl.json
index 620ea48..3ea1bd7 100644
--- a/go/src/fidl/compiler/backend/typestest/tables.fidl.json
+++ b/go/src/fidl/compiler/backend/typestest/tables.fidl.json
@@ -146,6 +146,7 @@
     }
   ],
   "union_declarations": [],
+  "xunion_declarations": [],
   "declaration_order": [
     "test.name/OlderSimpleTable",
     "test.name/NewerSimpleTable",
diff --git a/go/src/fidl/compiler/backend/typestest/union.fidl.json b/go/src/fidl/compiler/backend/typestest/union.fidl.json
index 198ceea..f6ffc53 100644
--- a/go/src/fidl/compiler/backend/typestest/union.fidl.json
+++ b/go/src/fidl/compiler/backend/typestest/union.fidl.json
@@ -55,6 +55,7 @@
       "max_handles": 0
     }
   ],
+  "xunion_declarations": [],
   "declaration_order": [
     "test.name/Union"
   ],
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl b/go/src/fidl/compiler/backend/typestest/xunion.fidl
new file mode 100644
index 0000000..53058bb
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl
@@ -0,0 +1,15 @@
+library test.name;
+
+xunion OlderSimpleUnion {
+  int64 i;
+  float32 f;
+};
+
+xunion NewerSimpleUnion {
+  // float f;  // removed
+
+  int64 i;  // unchanged
+
+  string s;  // added
+  vector<string> v;  // added
+};
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl.json b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json
new file mode 100644
index 0000000..e39396a
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json
@@ -0,0 +1,103 @@
+{
+  "version": "0.0.1",
+  "name": "test.name",
+  "library_dependencies": [],
+  "const_declarations": [],
+  "enum_declarations": [],
+  "interface_declarations": [],
+  "struct_declarations": [],
+  "table_declarations": [],
+  "union_declarations": [],
+  "xunion_declarations": [
+    {
+      "name": "test.name/OlderSimpleUnion",
+      "members": [
+        {
+          "ordinal": 1026732503,
+          "type": {
+            "kind": "primitive",
+            "subtype": "int64"
+          },
+          "name": "i",
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "offset": 0
+        },
+        {
+          "ordinal": 1362546558,
+          "type": {
+            "kind": "primitive",
+            "subtype": "float32"
+          },
+          "name": "f",
+          "size": 4,
+          "max_out_of_line": 0,
+          "alignment": 4,
+          "offset": 0
+        }
+      ],
+      "size": 24,
+      "max_out_of_line": 8,
+      "alignment": 8,
+      "max_handles": 0
+    },
+    {
+      "name": "test.name/NewerSimpleUnion",
+      "members": [
+        {
+          "ordinal": 693944286,
+          "type": {
+            "kind": "primitive",
+            "subtype": "int64"
+          },
+          "name": "i",
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "offset": 0
+        },
+        {
+          "ordinal": 473666119,
+          "type": {
+            "kind": "string",
+            "nullable": false
+          },
+          "name": "s",
+          "size": 16,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 0
+        },
+        {
+          "ordinal": 1815655055,
+          "type": {
+            "kind": "vector",
+            "element_type": {
+              "kind": "string",
+              "nullable": false
+            },
+            "nullable": false
+          },
+          "name": "v",
+          "size": 16,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 0
+        }
+      ],
+      "size": 24,
+      "max_out_of_line": 4294967295,
+      "alignment": 8,
+      "max_handles": 0
+    }
+  ],
+  "declaration_order": [
+    "test.name/OlderSimpleUnion",
+    "test.name/NewerSimpleUnion"
+  ],
+  "declarations": {
+    "test.name/OlderSimpleUnion": "xunion",
+    "test.name/NewerSimpleUnion": "xunion"
+  }
+}
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.cc.golden b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.cc.golden
new file mode 100644
index 0000000..21ed401
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.cc.golden
@@ -0,0 +1,351 @@
+// WARNING: This file is machine generated by fidlgen.
+
+#include <xunion.fidl.json.h>
+
+#include "lib/fidl/cpp/internal/implementation.h"
+namespace test {
+namespace name {
+
+extern "C" const fidl_type_t test_name_OlderSimpleUnionTable;
+const fidl_type_t* OlderSimpleUnion::FidlType = &test_name_OlderSimpleUnionTable;
+
+OlderSimpleUnion::OlderSimpleUnion() {}
+
+OlderSimpleUnion::~OlderSimpleUnion() {
+  Destroy();
+}
+
+OlderSimpleUnion::OlderSimpleUnion(OlderSimpleUnion&& other) : tag_(other.tag_) {
+  switch (tag_) {
+   case 1026732503:
+    i_ = std::move(other.i_);
+    break;
+   case 1362546558:
+    f_ = std::move(other.f_);
+    break;
+   default:
+    break;
+  }
+}
+
+OlderSimpleUnion& OlderSimpleUnion::operator=(OlderSimpleUnion&& other) {
+  if (this != &other) {
+    Destroy();
+    tag_ = other.tag_;
+    switch (tag_) {
+     case 1026732503:
+      i_ = std::move(other.i_);
+      break;
+     case 1362546558:
+      f_ = std::move(other.f_);
+      break;
+     default:
+      break;
+    }
+  }
+  return *this;
+}
+
+void OlderSimpleUnion::Encode(::fidl::Encoder* encoder, size_t offset) {
+  const size_t length_before = encoder->CurrentLength();
+  const size_t handles_before = encoder->CurrentHandleCount();
+
+  size_t envelope_offset = 0;
+
+  switch (tag_) {
+    case 1026732503: {
+      envelope_offset = encoder->Alloc(::fidl::CodingTraits<int64_t>::encoded_size);
+      ::fidl::Encode(encoder, &i_, envelope_offset);
+      break;
+    }
+    case 1362546558: {
+      envelope_offset = encoder->Alloc(::fidl::CodingTraits<float>::encoded_size);
+      ::fidl::Encode(encoder, &f_, envelope_offset);
+      break;
+    }
+    case Tag::Empty:
+    default:
+       break;
+  }
+
+  fidl_xunion_t* xunion = encoder->GetPtr<fidl_xunion_t>(offset);
+  assert(xunion->envelope.presence == FIDL_ALLOC_ABSENT);
+
+  if (envelope_offset) {
+    xunion->tag = tag_;
+    xunion->envelope.num_bytes = encoder->CurrentLength() - length_before;
+    xunion->envelope.num_handles = encoder->CurrentHandleCount() - handles_before;
+    xunion->envelope.presence = FIDL_ALLOC_PRESENT;
+  }
+}
+
+void OlderSimpleUnion::Decode(::fidl::Decoder* decoder, OlderSimpleUnion* value, size_t offset) {
+  fidl_xunion_t* xunion = decoder->GetPtr<fidl_xunion_t>(offset);
+
+  if (!xunion->envelope.data) {
+    value->EnsureStorageInitialized(Tag::Empty);
+    return;
+  }
+
+  value->EnsureStorageInitialized(xunion->tag);
+
+  const size_t envelope_offset = decoder->GetOffset(xunion->envelope.data);
+
+  switch (value->tag_) {
+   case 1026732503:
+    ::fidl::Decode(decoder, &value->i_, envelope_offset);
+    break;
+   case 1362546558:
+    ::fidl::Decode(decoder, &value->f_, envelope_offset);
+    break;
+   default:
+    break;
+  }
+}
+
+zx_status_t OlderSimpleUnion::Clone(OlderSimpleUnion* result) const {
+  result->Destroy();
+  result->tag_ = tag_;
+  switch (tag_) {
+    case 1026732503:
+      return ::fidl::Clone(i_, &result->i_);
+    case 1362546558:
+      return ::fidl::Clone(f_, &result->f_);
+    default:
+      return ZX_OK;
+  }
+}
+
+bool operator==(const OlderSimpleUnion& lhs, const OlderSimpleUnion& rhs) {
+  if (lhs.tag_ != rhs.tag_) {
+    return false;
+  }
+  switch (lhs.tag_) {
+    case 1026732503:
+      return ::fidl::Equals(lhs.i_, rhs.i_);
+    case 1362546558:
+      return ::fidl::Equals(lhs.f_, rhs.f_);
+    case OlderSimpleUnion::Tag::Empty:
+      return true;
+    default:
+      return false;
+  }
+}
+
+void OlderSimpleUnion::set_i(int64_t value) {
+  EnsureStorageInitialized(1026732503);
+  i_ = std::move(value);
+}
+
+void OlderSimpleUnion::set_f(float value) {
+  EnsureStorageInitialized(1362546558);
+  f_ = std::move(value);
+}
+
+void OlderSimpleUnion::Destroy() {
+  switch (tag_) {
+   case 1026732503:
+    break;
+   case 1362546558:
+    break;
+   default:
+    break;
+  }
+  tag_ = Tag::Empty;
+}
+
+void OlderSimpleUnion::EnsureStorageInitialized(::fidl_xunion_tag_t tag) {
+  if (tag_ != tag) {
+    Destroy();
+    tag_ = tag;
+    switch (tag_) {
+      default:
+        break;
+    }
+  }
+}
+extern "C" const fidl_type_t test_name_NewerSimpleUnionTable;
+const fidl_type_t* NewerSimpleUnion::FidlType = &test_name_NewerSimpleUnionTable;
+
+NewerSimpleUnion::NewerSimpleUnion() {}
+
+NewerSimpleUnion::~NewerSimpleUnion() {
+  Destroy();
+}
+
+NewerSimpleUnion::NewerSimpleUnion(NewerSimpleUnion&& other) : tag_(other.tag_) {
+  switch (tag_) {
+   case 693944286:
+    i_ = std::move(other.i_);
+    break;
+   case 473666119:
+    s_ = std::move(other.s_);
+    break;
+   case 1815655055:
+    v_ = std::move(other.v_);
+    break;
+   default:
+    break;
+  }
+}
+
+NewerSimpleUnion& NewerSimpleUnion::operator=(NewerSimpleUnion&& other) {
+  if (this != &other) {
+    Destroy();
+    tag_ = other.tag_;
+    switch (tag_) {
+     case 693944286:
+      i_ = std::move(other.i_);
+      break;
+     case 473666119:
+      s_ = std::move(other.s_);
+      break;
+     case 1815655055:
+      v_ = std::move(other.v_);
+      break;
+     default:
+      break;
+    }
+  }
+  return *this;
+}
+
+void NewerSimpleUnion::Encode(::fidl::Encoder* encoder, size_t offset) {
+  const size_t length_before = encoder->CurrentLength();
+  const size_t handles_before = encoder->CurrentHandleCount();
+
+  size_t envelope_offset = 0;
+
+  switch (tag_) {
+    case 693944286: {
+      envelope_offset = encoder->Alloc(::fidl::CodingTraits<int64_t>::encoded_size);
+      ::fidl::Encode(encoder, &i_, envelope_offset);
+      break;
+    }
+    case 473666119: {
+      envelope_offset = encoder->Alloc(::fidl::CodingTraits<::std::string>::encoded_size);
+      ::fidl::Encode(encoder, &s_, envelope_offset);
+      break;
+    }
+    case 1815655055: {
+      envelope_offset = encoder->Alloc(::fidl::CodingTraits<::std::vector<::std::string>>::encoded_size);
+      ::fidl::Encode(encoder, &v_, envelope_offset);
+      break;
+    }
+    case Tag::Empty:
+    default:
+       break;
+  }
+
+  fidl_xunion_t* xunion = encoder->GetPtr<fidl_xunion_t>(offset);
+  assert(xunion->envelope.presence == FIDL_ALLOC_ABSENT);
+
+  if (envelope_offset) {
+    xunion->tag = tag_;
+    xunion->envelope.num_bytes = encoder->CurrentLength() - length_before;
+    xunion->envelope.num_handles = encoder->CurrentHandleCount() - handles_before;
+    xunion->envelope.presence = FIDL_ALLOC_PRESENT;
+  }
+}
+
+void NewerSimpleUnion::Decode(::fidl::Decoder* decoder, NewerSimpleUnion* value, size_t offset) {
+  fidl_xunion_t* xunion = decoder->GetPtr<fidl_xunion_t>(offset);
+
+  if (!xunion->envelope.data) {
+    value->EnsureStorageInitialized(Tag::Empty);
+    return;
+  }
+
+  value->EnsureStorageInitialized(xunion->tag);
+
+  const size_t envelope_offset = decoder->GetOffset(xunion->envelope.data);
+
+  switch (value->tag_) {
+   case 693944286:
+    ::fidl::Decode(decoder, &value->i_, envelope_offset);
+    break;
+   case 473666119:
+    ::fidl::Decode(decoder, &value->s_, envelope_offset);
+    break;
+   case 1815655055:
+    ::fidl::Decode(decoder, &value->v_, envelope_offset);
+    break;
+   default:
+    break;
+  }
+}
+
+zx_status_t NewerSimpleUnion::Clone(NewerSimpleUnion* result) const {
+  result->Destroy();
+  result->tag_ = tag_;
+  switch (tag_) {
+    case 693944286:
+      return ::fidl::Clone(i_, &result->i_);
+    case 473666119:
+      return ::fidl::Clone(s_, &result->s_);
+    case 1815655055:
+      return ::fidl::Clone(v_, &result->v_);
+    default:
+      return ZX_OK;
+  }
+}
+
+bool operator==(const NewerSimpleUnion& lhs, const NewerSimpleUnion& rhs) {
+  if (lhs.tag_ != rhs.tag_) {
+    return false;
+  }
+  switch (lhs.tag_) {
+    case 693944286:
+      return ::fidl::Equals(lhs.i_, rhs.i_);
+    case 473666119:
+      return ::fidl::Equals(lhs.s_, rhs.s_);
+    case 1815655055:
+      return ::fidl::Equals(lhs.v_, rhs.v_);
+    case NewerSimpleUnion::Tag::Empty:
+      return true;
+    default:
+      return false;
+  }
+}
+
+void NewerSimpleUnion::set_i(int64_t value) {
+  EnsureStorageInitialized(693944286);
+  i_ = std::move(value);
+}
+
+void NewerSimpleUnion::set_s(::std::string value) {
+  EnsureStorageInitialized(473666119);
+  s_ = std::move(value);
+}
+
+void NewerSimpleUnion::set_v(::std::vector<::std::string> value) {
+  EnsureStorageInitialized(1815655055);
+  v_ = std::move(value);
+}
+
+void NewerSimpleUnion::Destroy() {
+  switch (tag_) {
+   case 693944286:
+    break;
+   case 473666119:
+    break;
+   case 1815655055:
+    break;
+   default:
+    break;
+  }
+  tag_ = Tag::Empty;
+}
+
+void NewerSimpleUnion::EnsureStorageInitialized(::fidl_xunion_tag_t tag) {
+  if (tag_ != tag) {
+    Destroy();
+    tag_ = tag;
+    switch (tag_) {
+      default:
+        break;
+    }
+  }
+}
+}  // namespace name
+}  // namespace test
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.go.golden b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.go.golden
new file mode 100644
index 0000000..145cd5d
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.go.golden
@@ -0,0 +1,8 @@
+// WARNING: This file is machine generated by fidlgen.
+
+package name
+
+
+
+
+
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.h.golden b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.h.golden
new file mode 100644
index 0000000..1a55ce3
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.h.golden
@@ -0,0 +1,184 @@
+// WARNING: This file is machine generated by fidlgen.
+
+#pragma once
+
+#include "lib/fidl/cpp/internal/header.h"
+
+
+namespace test {
+namespace name {
+
+class OlderSimpleUnion;
+class NewerSimpleUnion;
+
+class OlderSimpleUnion {
+ public:
+ static const fidl_type_t* FidlType;
+
+ OlderSimpleUnion();
+  ~OlderSimpleUnion();
+
+  OlderSimpleUnion(OlderSimpleUnion&&);
+  OlderSimpleUnion& operator=(OlderSimpleUnion&&);
+
+  enum Tag : fidl_xunion_tag_t {
+    Empty = 0,
+    kI = 1026732503,
+    kF = 1362546558,
+  };
+
+  static inline ::std::unique_ptr<OlderSimpleUnion> New() { return ::std::make_unique<OlderSimpleUnion>(); }
+
+  void Encode(::fidl::Encoder* encoder, size_t offset);
+  static void Decode(::fidl::Decoder* decoder, OlderSimpleUnion* value, size_t offset);
+  zx_status_t Clone(OlderSimpleUnion* result) const;
+
+  bool empty() const { return tag_ == Tag::Empty; }
+
+  bool is_i() const { return tag_ == 1026732503; }
+  
+  int64_t& i() {
+    EnsureStorageInitialized(1026732503);
+    return i_;
+  }
+  
+  const int64_t& i() const { return i_; }
+  void set_i(int64_t value);
+
+  bool is_f() const { return tag_ == 1362546558; }
+  
+  float& f() {
+    EnsureStorageInitialized(1362546558);
+    return f_;
+  }
+  
+  const float& f() const { return f_; }
+  void set_f(float value);
+
+  Tag Which() const { return Tag(tag_); }
+
+ private:
+  friend bool operator==(const OlderSimpleUnion& lhs, const OlderSimpleUnion& rhs);
+  void Destroy();
+  void EnsureStorageInitialized(::fidl_xunion_tag_t tag);
+
+  ::fidl_xunion_tag_t tag_ = Tag::Empty;
+  union {
+    int64_t i_;
+    float f_;
+  };
+};
+
+bool operator==(const OlderSimpleUnion& lhs, const OlderSimpleUnion& rhs);
+inline bool operator!=(const OlderSimpleUnion& lhs, const OlderSimpleUnion& rhs) {
+  return !(lhs == rhs);
+}
+
+inline zx_status_t Clone(const ::test::name::OlderSimpleUnion& value,
+                         ::test::name::OlderSimpleUnion* result) {
+  return value.Clone(result);
+}
+
+using OlderSimpleUnionPtr = ::std::unique_ptr<OlderSimpleUnion>;
+
+class NewerSimpleUnion {
+ public:
+ static const fidl_type_t* FidlType;
+
+ NewerSimpleUnion();
+  ~NewerSimpleUnion();
+
+  NewerSimpleUnion(NewerSimpleUnion&&);
+  NewerSimpleUnion& operator=(NewerSimpleUnion&&);
+
+  enum Tag : fidl_xunion_tag_t {
+    Empty = 0,
+    kI = 693944286,
+    kS = 473666119,
+    kV = 1815655055,
+  };
+
+  static inline ::std::unique_ptr<NewerSimpleUnion> New() { return ::std::make_unique<NewerSimpleUnion>(); }
+
+  void Encode(::fidl::Encoder* encoder, size_t offset);
+  static void Decode(::fidl::Decoder* decoder, NewerSimpleUnion* value, size_t offset);
+  zx_status_t Clone(NewerSimpleUnion* result) const;
+
+  bool empty() const { return tag_ == Tag::Empty; }
+
+  bool is_i() const { return tag_ == 693944286; }
+  
+  int64_t& i() {
+    EnsureStorageInitialized(693944286);
+    return i_;
+  }
+  
+  const int64_t& i() const { return i_; }
+  void set_i(int64_t value);
+
+  bool is_s() const { return tag_ == 473666119; }
+  
+  ::std::string& s() {
+    EnsureStorageInitialized(473666119);
+    return s_;
+  }
+  
+  const ::std::string& s() const { return s_; }
+  void set_s(::std::string value);
+
+  bool is_v() const { return tag_ == 1815655055; }
+  
+  ::std::vector<::std::string>& v() {
+    EnsureStorageInitialized(1815655055);
+    return v_;
+  }
+  
+  const ::std::vector<::std::string>& v() const { return v_; }
+  void set_v(::std::vector<::std::string> value);
+
+  Tag Which() const { return Tag(tag_); }
+
+ private:
+  friend bool operator==(const NewerSimpleUnion& lhs, const NewerSimpleUnion& rhs);
+  void Destroy();
+  void EnsureStorageInitialized(::fidl_xunion_tag_t tag);
+
+  ::fidl_xunion_tag_t tag_ = Tag::Empty;
+  union {
+    int64_t i_;
+    ::std::string s_;
+    ::std::vector<::std::string> v_;
+  };
+};
+
+bool operator==(const NewerSimpleUnion& lhs, const NewerSimpleUnion& rhs);
+inline bool operator!=(const NewerSimpleUnion& lhs, const NewerSimpleUnion& rhs) {
+  return !(lhs == rhs);
+}
+
+inline zx_status_t Clone(const ::test::name::NewerSimpleUnion& value,
+                         ::test::name::NewerSimpleUnion* result) {
+  return value.Clone(result);
+}
+
+using NewerSimpleUnionPtr = ::std::unique_ptr<NewerSimpleUnion>;
+}  // namespace name
+}  // namespace test
+namespace fidl {
+
+template <>
+struct CodingTraits<::test::name::OlderSimpleUnion>
+    : public EncodableCodingTraits<::test::name::OlderSimpleUnion, 24> {};
+
+inline zx_status_t Clone(const ::test::name::OlderSimpleUnion& value,
+                         ::test::name::OlderSimpleUnion* result) {
+  return ::test::name::Clone(value, result);
+}
+template <>
+struct CodingTraits<::test::name::NewerSimpleUnion>
+    : public EncodableCodingTraits<::test::name::NewerSimpleUnion, 24> {};
+
+inline zx_status_t Clone(const ::test::name::NewerSimpleUnion& value,
+                         ::test::name::NewerSimpleUnion* result) {
+  return ::test::name::Clone(value, result);
+}}  // namespace fidl
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.llcpp.cpp.golden b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.llcpp.cpp.golden
new file mode 100644
index 0000000..610416c
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.llcpp.cpp.golden
@@ -0,0 +1,9 @@
+// WARNING: This file is machine generated by fidlgen.
+
+#include <xunion.fidl.json.llcpp.h>
+
+namespace test {
+namespace name {
+
+}  // namespace name
+}  // namespace test
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.llcpp.h.golden b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.llcpp.h.golden
new file mode 100644
index 0000000..582c8dd
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.llcpp.h.golden
@@ -0,0 +1,23 @@
+// WARNING: This file is machine generated by fidlgen.
+
+#pragma once
+
+#include <lib/fidl/internal.h>
+#include <lib/fidl/cpp/vector_view.h>
+#include <lib/fidl/cpp/string_view.h>
+#include <lib/fidl/llcpp/array_wrapper.h>
+#include <lib/fidl/llcpp/coding.h>
+#include <lib/fidl/llcpp/traits.h>
+
+#include <zircon/fidl.h>
+
+namespace test {
+namespace name {
+
+
+}  // namespace name
+}  // namespace test
+
+namespace fidl {
+
+}  // namespace fidl
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.rs.golden b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.rs.golden
new file mode 100644
index 0000000..7b9c869
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json.rs.golden
@@ -0,0 +1,15 @@
+// WARNING: This file is machine generated by fidlgen.
+
+#![feature(futures_api, pin, arbitrary_self_types, nll)]
+#![allow(warnings)]
+
+extern crate fuchsia_async;
+extern crate fuchsia_zircon as zx;
+#[macro_use]
+extern crate fidl;
+#[macro_use]
+extern crate futures;
+use fidl::encoding::{Encodable, Decodable};
+use futures::{Future, Stream, StreamExt};
+use std::ops::Deref;
+
diff --git a/go/src/fidl/compiler/backend/typestest/xunion.fidl.json_test_base.h.golden b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json_test_base.h.golden
new file mode 100644
index 0000000..ebc6e21
--- /dev/null
+++ b/go/src/fidl/compiler/backend/typestest/xunion.fidl.json_test_base.h.golden
@@ -0,0 +1,11 @@
+// WARNING: This file is machine generated by fidlgen.
+
+#pragma once
+
+
+namespace test {
+namespace name {
+namespace testing {
+}  // namespace testing
+}  // namespace name
+}  // namespace test
diff --git a/public/lib/fidl/cpp/BUILD.gn b/public/lib/fidl/cpp/BUILD.gn
index 5a88e32..7cb2d53 100644
--- a/public/lib/fidl/cpp/BUILD.gn
+++ b/public/lib/fidl/cpp/BUILD.gn
@@ -241,3 +241,10 @@
     ]
   }
 }
+
+# group("host_tests") {
+#   deps = [
+#     ":fidl_cpp_host_unittests($host_toolchain)",
+#   ]
+#   testonly = true
+# }
diff --git a/public/lib/fidl/cpp/fidl_test.fidl b/public/lib/fidl/cpp/fidl_test.fidl
index bfc37bc..83307c8 100644
--- a/public/lib/fidl/cpp/fidl_test.fidl
+++ b/public/lib/fidl/cpp/fidl_test.fidl
@@ -21,17 +21,17 @@
 };
 
 struct EmptyStructSandwich {
-  string before;
-  Empty e;
-  string after;
+    string before;
+    Empty e;
+    string after;
 };
 
 union SimpleUnion {
-  int32 i32;
-  int64 i64;
-  Int64Struct s;
-  Int64Struct? os;
-  string str;
+    int32 i32;
+    int64 i64;
+    Int64Struct s;
+    Int64Struct? os;
+    string str;
 };
 
 table SimpleTable {
@@ -60,3 +60,15 @@
     6: int64 z;
     7: reserved;
 };
+
+xunion SampleXUnion {
+    int32 i;
+    SimpleUnion su;
+    SimpleTable st;
+};
+
+struct XUnionContainer {
+    string before;
+    SampleXUnion? xu;
+    string after;
+};
diff --git a/public/lib/fidl/cpp/roundtrip_test.cc b/public/lib/fidl/cpp/roundtrip_test.cc
index 3830466..43fa481 100644
--- a/public/lib/fidl/cpp/roundtrip_test.cc
+++ b/public/lib/fidl/cpp/roundtrip_test.cc
@@ -18,14 +18,14 @@
 
 template <class Output, class Input>
 Output RoundTrip(const Input& input) {
-  const ::fidl::FidlField fake_input_interface_fields[] = {
-      ::fidl::FidlField(Input::FidlType, 16),
+  const ::fidl::FidlStructField fake_input_interface_fields[] = {
+      ::fidl::FidlStructField(Input::FidlType, 16),
   };
   const fidl_type_t fake_input_interface_struct{
       ::fidl::FidlCodedStruct(fake_input_interface_fields, 1,
                               16 + CodingTraits<Input>::encoded_size, "Input")};
-  const ::fidl::FidlField fake_output_interface_fields[] = {
-      ::fidl::FidlField(Output::FidlType, 16),
+  const ::fidl::FidlStructField fake_output_interface_fields[] = {
+      ::fidl::FidlStructField(Output::FidlType, 16),
   };
   const fidl_type_t fake_output_interface_struct{::fidl::FidlCodedStruct(
       fake_output_interface_fields, 1, 16 + CodingTraits<Output>::encoded_size,
@@ -35,6 +35,7 @@
   auto ofs = enc.Alloc(CodingTraits<Input>::encoded_size);
   fidl::Clone(input).Encode(&enc, ofs);
   auto msg = enc.GetMessage();
+
   const char* err_msg = nullptr;
   EXPECT_EQ(ZX_OK, msg.Validate(&fake_input_interface_struct, &err_msg))
       << err_msg;
@@ -57,14 +58,14 @@
   for (size_t i = 0; i < actual_size && i < expected_size; i++) {
     if (actual[i] != expected[i]) {
       pass = false;
-      std::cout << "element[" << i << "]: "
-                << "actual=" << +actual[i] << " "
-                << "expected=" << +expected[i] << "\n";
+      std::cout << std::dec << "element[" << i << "]: " << std::hex
+                << "actual=0x" << +actual[i] << " "
+                << "expected=0x" << +expected[i] << "\n";
     }
   }
   if (actual_size != expected_size) {
     pass = false;
-    std::cout << "element[...]: "
+    std::cout << std::dec << "element[...]: "
               << "actual.size=" << +actual_size << " "
               << "expected.size=" << +expected_size << "\n";
   }
@@ -87,36 +88,50 @@
   SimpleTable input;
 
   auto expected = std::vector<uint8_t>{
-    0,   0,   0,   0,   0,   0,   0,   0,    // max ordinal
-    255, 255, 255, 255, 255, 255, 255, 255,  // alloc present
+      0,   0,   0,   0,   0,   0,   0,   0,    // max ordinal
+      255, 255, 255, 255, 255, 255, 255, 255,  // alloc present
   };
 
   EXPECT_TRUE(ValueToBytes(input, expected));
 }
 
+std::vector<uint8_t> kSimpleTable_X_42_Y_67 = std::vector<uint8_t>{
+    5,   0,   0,   0,
+    0,   0,   0,   0,  // max ordinal
+    255, 255, 255, 255,
+    255, 255, 255, 255,  // alloc present
+    8,   0,   0,   0,
+    0,   0,   0,   0,  // envelope 1: num bytes / num handles
+    255, 255, 255, 255,
+    255, 255, 255, 255,  // alloc present
+    0,   0,   0,   0,
+    0,   0,   0,   0,  // envelope 2: num bytes / num handles
+    0,   0,   0,   0,
+    0,   0,   0,   0,  // no alloc
+    0,   0,   0,   0,
+    0,   0,   0,   0,  // envelope 3: num bytes / num handles
+    0,   0,   0,   0,
+    0,   0,   0,   0,  // no alloc
+    0,   0,   0,   0,
+    0,   0,   0,   0,  // envelope 4: num bytes / num handles
+    0,   0,   0,   0,
+    0,   0,   0,   0,  // no alloc
+    8,   0,   0,   0,
+    0,   0,   0,   0,  // envelope 5: num bytes / num handles
+    255, 255, 255, 255,
+    255, 255, 255, 255,  // alloc present
+    42,  0,   0,   0,
+    0,   0,   0,   0,  // field X
+    67,  0,   0,   0,
+    0,   0,   0,   0,  // field Y
+};
+
 TEST(SimpleTable, CheckBytesWithXY) {
   SimpleTable input;
   input.set_x(42);
   input.set_y(67);
 
-  auto expected = std::vector<uint8_t>{
-    5, 0, 0, 0, 0, 0, 0, 0, // max ordinal
-    255, 255, 255, 255, 255, 255, 255, 255, // alloc present
-    8, 0, 0, 0, 0, 0, 0, 0, // envelope 1: num bytes / num handles
-    255, 255, 255, 255, 255, 255, 255, 255, // alloc present
-    0, 0, 0, 0, 0, 0, 0, 0, // envelope 2: num bytes / num handles
-    0, 0, 0, 0, 0, 0, 0, 0, // no alloc
-    0, 0, 0, 0, 0, 0, 0, 0, // envelope 3: num bytes / num handles
-    0, 0, 0, 0, 0, 0, 0, 0, // no alloc
-    0, 0, 0, 0, 0, 0, 0, 0, // envelope 4: num bytes / num handles
-    0, 0, 0, 0, 0, 0, 0, 0, // no alloc
-    8, 0, 0, 0, 0, 0, 0, 0, // envelope 5: num bytes / num handles
-    255, 255, 255, 255, 255, 255, 255, 255, // alloc present
-    42, 0, 0, 0, 0, 0, 0, 0, // field X
-    67, 0, 0, 0, 0, 0, 0, 0, // field Y
-  };
-
-  EXPECT_TRUE(ValueToBytes(input, expected));
+  EXPECT_TRUE(ValueToBytes(input, kSimpleTable_X_42_Y_67));
 }
 
 TEST(SimpleTable, SerializeAndDeserialize) {
@@ -153,41 +168,186 @@
   Empty input;
 
   auto expected = std::vector<uint8_t>{
-    0,  // empty struct zero field
-    0, 0, 0, 0, 0, 0, 0,  // 7 bytes of padding after empty struct, to align to 64 bits
+      0,                       // empty struct zero field
+         0, 0, 0, 0, 0, 0, 0,  // 7 bytes of padding
   };
   EXPECT_TRUE(ValueToBytes(input, expected));
 }
 
 TEST(EmptyStructSandwich, SerializeAndDeserialize) {
   EmptyStructSandwich input{
-    .before = "before",
-    .after = "after",
+      .before = "before",
+      .after = "after",
   };
   EXPECT_EQ(input, RoundTrip<EmptyStructSandwich>(input));
 }
 
 TEST(EmptyStructSandwich, CheckBytes) {
-  EmptyStructSandwich input{
-    .before = "before",
-    .after = "after"
-  };
+  EmptyStructSandwich input{.before = "before", .after = "after"};
 
   auto expected = std::vector<uint8_t>{
-    6,   0,   0,   0,   0,   0,   0,   0,    // length of "before"
-    255, 255, 255, 255, 255, 255, 255, 255,  // "before" is present
-    0,                                       // empty struct zero field
-         0,   0,   0,   0,   0,   0,   0,    // 7 bytes of padding after empty struct, to align to 64 bits
-    5,   0,   0,   0,   0,   0,   0,   0,    // length of "world"
-    255, 255, 255, 255, 255, 255, 255, 255,  // "after" is present
-    'b', 'e', 'f', 'o', 'r', 'e',            // "before" string
-                                  0,   0,    // 2 bytes of padding after "before", to align to 64 bits
-    'a', 'f', 't', 'e', 'r',                 // "after" string
-                             0,   0,   0,    // 3 bytes of padding after "after", to align to 64 bits
+      6,   0,   0,   0,   0,   0,   0,   0,    // length of "before"
+      255, 255, 255, 255, 255, 255, 255, 255,  // "before" is present
+      0,                                       // empty struct zero field
+           0,   0,   0,   0,   0,   0,   0,    // 7 bytes of padding
+      5,   0,   0,   0,   0,   0,   0,   0,    // length of "world"
+      255, 255, 255, 255, 255, 255, 255, 255,  // "after" is present
+      'b', 'e', 'f', 'o', 'r', 'e',            // "before" string
+                                    0,   0,    // 2 bytes of padding
+      'a', 'f', 't', 'e', 'r',                 // "after" string
+                               0,   0,   0,    // 3 bytes of padding
   };
   EXPECT_TRUE(ValueToBytes(input, expected));
 }
 
+TEST(XUnion, Empty) {
+  SampleXUnion input;
+
+  auto expected = std::vector<uint8_t>{
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // xunion discriminator + padding
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // num bytes + num handles
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // envelope data is absent
+  };
+  EXPECT_TRUE(ValueToBytes(input, expected));
+}
+
+TEST(XUnion, Int32) {
+  SampleXUnion input;
+  input.set_i(0xdeadbeef);
+
+  auto expected = std::vector<uint8_t>{
+      0xa5, 0x47, 0xdf, 0x29, 0x00, 0x00, 0x00, 0x00,  // xunion discriminator + padding
+      0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // num bytes + num handles
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // envelope data is present
+      0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00,  // envelope content (0xdeadbeef) + padding
+  };
+  EXPECT_TRUE(ValueToBytes(input, expected));
+}
+
+TEST(XUnion, SimpleUnion) {
+  SimpleUnion su;
+  su.set_str("hello");
+
+  SampleXUnion input;
+  input.set_su(std::move(su));
+
+  auto expected = std::vector<uint8_t>{
+      0x53, 0x76, 0x31, 0x6f, 0x00, 0x00, 0x00, 0x00,  // xunion discriminator + padding
+      0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // num bytes + num handles
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // envelope data is present
+      // secondary object 0
+      0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // union discriminant + padding
+      0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // string size
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // string pointer is present
+      // secondary object 1
+      'h',  'e',  'l',  'l',  'o',  0x00, 0x00, 0x00,  // string: "hello"
+  };
+
+  EXPECT_TRUE(ValueToBytes(input, expected));
+}
+
+TEST(XUnion, SimpleTable) {
+  SimpleTable st;
+  st.set_x(42);
+  st.set_y(67);
+
+  SampleXUnion input;
+  input.set_st(std::move(st));
+
+  auto expected = std::vector<uint8_t>{
+      0xdd, 0x2c, 0x65, 0x30, 0x00, 0x00, 0x00, 0x00,  // xunion discriminator + padding
+      0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // num bytes + num handles
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // envelope data is present
+      // <table data follows>
+  };
+  expected.insert(expected.end(), kSimpleTable_X_42_Y_67.cbegin(),
+                  kSimpleTable_X_42_Y_67.cend());
+
+  EXPECT_TRUE(ValueToBytes(input, expected));
+}
+
+TEST(XUnion, SerializeAndDeserializeEmpty) {
+  SampleXUnion input;
+
+  EXPECT_EQ(input, RoundTrip<SampleXUnion>(input));
+}
+
+TEST(XUnion, SerializeAndDeserializeInt32) {
+  SampleXUnion input;
+  input.set_i(0xdeadbeef);
+
+  EXPECT_EQ(input, RoundTrip<SampleXUnion>(input));
+}
+
+TEST(XUnion, SerializeAndDeserializeSimpleUnion) {
+  SimpleUnion su;
+  su.set_str("hello");
+
+  SampleXUnion input;
+  input.set_su(std::move(su));
+
+  EXPECT_EQ(input, RoundTrip<SampleXUnion>(input));
+}
+
+TEST(XUnion, SerializeAndDeserializeSimpleTable) {
+  SimpleTable st;
+  st.set_x(42);
+  st.set_y(67);
+
+  SampleXUnion input;
+  input.set_st(std::move(st));
+
+  EXPECT_EQ(input, RoundTrip<SampleXUnion>(input));
+}
+
+TEST(XUnionContainer, XUnionPointer) {
+  auto xu = std::make_unique<SampleXUnion>();
+  xu->set_i(0xdeadbeef);
+
+  XUnionContainer input;
+  input.before = "before";
+  input.after = "after";
+  input.xu = std::move(xu);
+
+  auto expected = std::vector<uint8_t>{
+      0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // "before" length
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // "before" presence
+
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // xunion is present
+
+      0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // "after" length
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // "before" presence
+
+      // secondary object 1: "before"
+      'b',  'e',  'f',  'o',  'r',  'e',  0x00, 0x00,
+
+      // secondary object 2: xunion
+      0xa5, 0x47, 0xdf, 0x29, 0x00, 0x00, 0x00, 0x00,  // xunion discriminator + padding
+      0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // num bytes + num handles
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // envelope data is present
+
+      // secondary object 3: xunion content
+      0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00,  // xunion envelope content (0xdeadbeef) + padding
+
+      // secondary object 4: "after"
+      'a',  'f',  't',  'e',  'r',  0x00, 0x00, 0x00,
+  };
+
+  EXPECT_TRUE(ValueToBytes(input, expected));
+}
+
+TEST(XUnionContainer, SerializeAndDeserialize) {
+  auto xu = std::make_unique<SampleXUnion>();
+  xu->set_i(0xdeadbeef);
+
+  XUnionContainer input;
+  input.before = "before";
+  input.after = "after";
+  input.xu = std::move(xu);
+
+  EXPECT_EQ(input, RoundTrip<XUnionContainer>(input));
+}
+
 }  // namespace
 
 }  // namespace misc
diff --git a/public/lib/fidl/cpp/test/fidl_types.cc b/public/lib/fidl/cpp/test/fidl_types.cc
index bfd7699..b73987d 100644
--- a/public/lib/fidl/cpp/test/fidl_types.cc
+++ b/public/lib/fidl/cpp/test/fidl_types.cc
@@ -28,10 +28,10 @@
   alignas(FIDL_ALIGNMENT) char data[6];
 };
 
-static const fidl::FidlField unbounded_nonnullable_string_fields[] = {
-    fidl::FidlField(&unbounded_nonnullable_string,
-                    offsetof(unbounded_nonnullable_string_message_layout,
-                             inline_struct.string)),
+static const fidl::FidlStructField unbounded_nonnullable_string_fields[] = {
+    fidl::FidlStructField(&unbounded_nonnullable_string,
+                          offsetof(unbounded_nonnullable_string_message_layout,
+                                   inline_struct.string)),
 
 };