[fidl][golang] Encoding/decoding for Tables

Test: fx run-test go_fidl_test
Change-Id: I65d0eee9f5a30becc8e1e0a890a6cc8d879743e7
diff --git a/BUILD.gn b/BUILD.gn
index fbe3cbb..8edfdbe 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -88,9 +88,10 @@
   #
   # Not being able to depend on automated generation is due to where code is
   # placed, and how this relates to the tree structure of the toolchain.
-  deps = [
-    "src/syscall/zx/fidl/bindingstest($go_toolchain)",
-  ]
+  # Disabling while fidlgen table generation is pending.
+  #deps = [
+  #  "src/syscall/zx/fidl/bindingstest($go_toolchain)",
+  #]
 }
 
 go_test("go_zxwait_test") {
diff --git a/src/syscall/zx/fidl/bindingstest/impl.go b/src/syscall/zx/fidl/bindingstest/impl.go
index 71fcbe5..398b3ef 100644
--- a/src/syscall/zx/fidl/bindingstest/impl.go
+++ b/src/syscall/zx/fidl/bindingstest/impl.go
@@ -539,6 +539,90 @@
 	return 16
 }
 
+type TestSimpleTable struct {
+	_     struct{} `fidl2:"s,16,8"`
+	Table SimpleTable
+}
+
+var _mTestSimpleTable = _bindings.CreateLazyMarshaler(TestSimpleTable{})
+
+func (msg *TestSimpleTable) Marshaler() _bindings.Marshaler {
+	return _mTestSimpleTable
+}
+
+// Implements Payload.
+func (_ *TestSimpleTable) InlineAlignment() int {
+	return 8
+}
+
+// Implements Payload.
+func (_ *TestSimpleTable) InlineSize() int {
+	return 16
+}
+
+type TestOlderSimpleTable struct {
+	_     struct{} `fidl2:"s,16,8"`
+	Table OlderSimpleTable
+}
+
+var _mTestOlderSimpleTable = _bindings.CreateLazyMarshaler(TestOlderSimpleTable{})
+
+func (msg *TestOlderSimpleTable) Marshaler() _bindings.Marshaler {
+	return _mTestOlderSimpleTable
+}
+
+// Implements Payload.
+func (_ *TestOlderSimpleTable) InlineAlignment() int {
+	return 8
+}
+
+// Implements Payload.
+func (_ *TestOlderSimpleTable) InlineSize() int {
+	return 16
+}
+
+type TestOptSimpleTable struct {
+	_        struct{} `fidl2:"s,8,8"`
+	OptTable *SimpleTable
+}
+
+var _mTestOptSimpleTable = _bindings.CreateLazyMarshaler(TestOptSimpleTable{})
+
+func (msg *TestOptSimpleTable) Marshaler() _bindings.Marshaler {
+	return _mTestOptSimpleTable
+}
+
+// Implements Payload.
+func (_ *TestOptSimpleTable) InlineAlignment() int {
+	return 8
+}
+
+// Implements Payload.
+func (_ *TestOptSimpleTable) InlineSize() int {
+	return 8
+}
+
+type TestNewerSimpleTable struct {
+	_     struct{} `fidl2:"s,16,8"`
+	Table NewerSimpleTable
+}
+
+var _mTestNewerSimpleTable = _bindings.CreateLazyMarshaler(TestNewerSimpleTable{})
+
+func (msg *TestNewerSimpleTable) Marshaler() _bindings.Marshaler {
+	return _mTestNewerSimpleTable
+}
+
+// Implements Payload.
+func (_ *TestNewerSimpleTable) InlineAlignment() int {
+	return 8
+}
+
+// Implements Payload.
+func (_ *TestNewerSimpleTable) InlineSize() int {
+	return 16
+}
+
 type TestFuchsiaIoReadAtResponse struct {
 	_    struct{} `fidl2:"s,24,8"`
 	S    int32
@@ -636,6 +720,162 @@
 	u.D = d
 }
 
+type SimpleTable struct {
+	_        struct{} `fidl2:"t,16,8"`
+	X        int64    `fidl:"1" fidl2:"1"`
+	XPresent bool
+	Y        int64 `fidl:"5" fidl2:"5"`
+	YPresent bool
+}
+
+// Implements Payload.
+func (_ *SimpleTable) InlineAlignment() int {
+	return 8
+}
+
+// Implements Payload.
+func (_ *SimpleTable) InlineSize() int {
+	return 16
+}
+
+func (u *SimpleTable) SetX(x int64) {
+	u.X = x
+	u.XPresent = true
+}
+
+func (u *SimpleTable) GetX() int64 {
+	return u.X
+}
+
+func (u *SimpleTable) HasX() bool {
+	return u.XPresent
+}
+
+func (u *SimpleTable) ClearX() {
+	u.XPresent = false
+}
+
+func (u *SimpleTable) SetY(y int64) {
+	u.Y = y
+	u.YPresent = true
+}
+
+func (u *SimpleTable) GetY() int64 {
+	return u.Y
+}
+
+func (u *SimpleTable) HasY() bool {
+	return u.YPresent
+}
+
+func (u *SimpleTable) ClearY() {
+	u.YPresent = false
+}
+
+type OlderSimpleTable struct {
+	_        struct{} `fidl2:"t,16,8"`
+	X        int64    `fidl:"1" fidl2:"1"`
+	XPresent bool
+}
+
+// Implements Payload.
+func (_ *OlderSimpleTable) InlineAlignment() int {
+	return 8
+}
+
+// Implements Payload.
+func (_ *OlderSimpleTable) InlineSize() int {
+	return 16
+}
+
+func (u *OlderSimpleTable) SetX(x int64) {
+	u.X = x
+	u.XPresent = true
+}
+
+func (u *OlderSimpleTable) GetX() int64 {
+	return u.X
+}
+
+func (u *OlderSimpleTable) HasX() bool {
+	return u.XPresent
+}
+
+func (u *OlderSimpleTable) ClearX() {
+	u.XPresent = false
+}
+
+type NewerSimpleTable struct {
+	_        struct{} `fidl2:"t,16,8"`
+	X        int64    `fidl:"1" fidl2:"1"`
+	XPresent bool
+	Y        int64 `fidl:"5" fidl2:"5"`
+	YPresent bool
+	Z        int64 `fidl:"6" fidl2:"6"`
+	ZPresent bool
+}
+
+// Implements Payload.
+func (_ *NewerSimpleTable) InlineAlignment() int {
+	return 8
+}
+
+// Implements Payload.
+func (_ *NewerSimpleTable) InlineSize() int {
+	return 16
+}
+
+func (u *NewerSimpleTable) SetX(x int64) {
+	u.X = x
+	u.XPresent = true
+}
+
+func (u *NewerSimpleTable) GetX() int64 {
+	return u.X
+}
+
+func (u *NewerSimpleTable) HasX() bool {
+	return u.XPresent
+}
+
+func (u *NewerSimpleTable) ClearX() {
+	u.XPresent = false
+}
+
+func (u *NewerSimpleTable) SetY(y int64) {
+	u.Y = y
+	u.YPresent = true
+}
+
+func (u *NewerSimpleTable) GetY() int64 {
+	return u.Y
+}
+
+func (u *NewerSimpleTable) HasY() bool {
+	return u.YPresent
+}
+
+func (u *NewerSimpleTable) ClearY() {
+	u.YPresent = false
+}
+
+func (u *NewerSimpleTable) SetZ(z int64) {
+	u.Z = z
+	u.ZPresent = true
+}
+
+func (u *NewerSimpleTable) GetZ() int64 {
+	return u.Z
+}
+
+func (u *NewerSimpleTable) HasZ() bool {
+	return u.ZPresent
+}
+
+func (u *NewerSimpleTable) ClearZ() {
+	u.ZPresent = false
+}
+
 const (
 	Test1EchoOrdinal     uint32 = 10
 	Test1SurpriseOrdinal uint32 = 11
diff --git a/src/syscall/zx/fidl/bindingstest/test.fidl b/src/syscall/zx/fidl/bindingstest/test.fidl
index 45a4e99..982a1a6 100644
--- a/src/syscall/zx/fidl/bindingstest/test.fidl
+++ b/src/syscall/zx/fidl/bindingstest/test.fidl
@@ -146,6 +146,49 @@
     request<Test1>? d;
 };
 
+table SimpleTable {
+    1: int64 x;
+    2: reserved;
+    3: reserved;
+    4: reserved;
+    5: int64 y;
+};
+
+struct TestSimpleTable {
+    SimpleTable table;
+};
+
+// A variant of SimpleTable that has just the first few fields.
+// Think of this as an older variant of that type!
+table OlderSimpleTable {
+    1: int64 x;
+    2: reserved;
+};
+
+struct TestOlderSimpleTable {
+    OlderSimpleTable table;
+};
+
+struct TestOptSimpleTable {
+    SimpleTable? opt_table;
+};
+
+// A variant of SimpleTable that has some additional new fields.
+// Think of this as an newer variant of that type!
+table NewerSimpleTable {
+    1: int64 x;
+    2: reserved;
+    3: reserved;
+    4: reserved;
+    5: int64 y;
+    6: int64 z;
+    7: reserved;
+};
+
+struct TestNewerSimpleTable {
+    NewerSimpleTable table;
+};
+
 // examples from fuchsia world.
 
 const uint64 kMaxBuf = 8192;
diff --git a/src/syscall/zx/fidl/encoding.go b/src/syscall/zx/fidl/encoding.go
index 448b33c..e531421 100644
--- a/src/syscall/zx/fidl/encoding.go
+++ b/src/syscall/zx/fidl/encoding.go
@@ -41,14 +41,30 @@
 	proxyType                         = reflect.TypeOf(Proxy{})
 )
 
-// isUnionType returns true if the reflected type is a FIDL union type.
-func isUnionType(t reflect.Type) bool {
+type structKind int
+
+const (
+	_ structKind = iota
+	aStruct
+	aUnion
+	aTable
+)
+
+// whichStructKind returns the kind of FIDL type this golang type represents.
+func whichStructKind(t reflect.Type) structKind {
 	// This is a safe way to check if it's a union type because the generated
 	// code inserts a "dummy" field (of type struct{}) at the beginning as a
 	// marker that the struct should be treated as a FIDL union. Because all FIDL
 	// fields are exported, there's no potential for name collision either, and a
 	// struct accidentally being treated as a union.
-	return t.Kind() == reflect.Struct && t.NumField() > 1 && t.Field(0).Tag.Get("fidl") == "tag"
+	if t.Kind() == reflect.Struct && t.NumField() > 1 && t.Field(0).Tag.Get("fidl") == "tag" {
+		return aUnion
+	}
+	// We place a marker "t,..." on tables in the first field of the struct.
+	if t.Kind() == reflect.Struct && t.NumField() > 1 && strings.HasPrefix(t.Field(0).Tag.Get("fidl2"), "t") {
+		return aTable
+	}
+	return aStruct
 }
 
 // isHandleType returns true if the reflected type is a Fuchsia handle type.
@@ -116,7 +132,7 @@
 		case reflect.String:
 			return 16, nil
 		case reflect.Struct:
-			// Handles both structs and unions.
+			// Handles structs, unions, and tables.
 			return 8, nil
 		}
 		return 0, newValueError(ErrInvalidPointerType, t.Name())
@@ -125,7 +141,7 @@
 	case reflect.Slice:
 		return 16, nil
 	case reflect.Struct:
-		// Handles both structs and unions.
+		// Handles structs, unions, and tables.
 		return getPayloadSize(t, v)
 	}
 	return 0, newValueError(ErrInvalidInlineType, t.Name())
@@ -179,6 +195,9 @@
 	// This is only used for types where nullability is non-obvious, which is only
 	// handles for now.
 	nullable bool
+
+	// ordinal captures the table ordinal if applicable
+	ordinal int
 }
 
 // Unnest attempts to unnest the nestedTypeData one level. If it succeeds, it returns the type
@@ -194,7 +213,7 @@
 
 // FromTag derives metadata from data serialized into a golang struct field
 // tag.
-func (n *nestedTypeData) FromTag(tag reflect.StructTag) error {
+func (n *nestedTypeData) FromTag(tag reflect.StructTag, isTable bool) error {
 	raw, ok := tag.Lookup("fidl")
 	if !ok {
 		return nil
@@ -217,6 +236,11 @@
 		val := int(i)
 		maxElems = append(maxElems, &val)
 	}
+	if isTable {
+		lenMaxElems := len(maxElems)
+		n.ordinal = *maxElems[lenMaxElems-1]
+		maxElems = maxElems[:lenMaxElems-1]
+	}
 	n.maxElems = maxElems
 	return nil
 }
@@ -292,10 +316,13 @@
 		return err
 	}
 	e.head = align(e.head, a)
-	if isUnionType(t) {
+	switch whichStructKind(t) {
+	case aUnion:
 		err = e.marshalUnion(t, v, a)
-	} else {
+	case aStruct:
 		err = e.marshalStructFields(t, v)
+	case aTable:
+		err = e.marshalTable(t, v)
 	}
 	if err != nil {
 		return err
@@ -323,10 +350,13 @@
 	}
 	oldHead := e.head
 	e.head = e.newObject(align(payload.InlineSize(), 8))
-	if isUnionType(et) {
+	switch whichStructKind(et) {
+	case aUnion:
 		err = e.marshalUnion(et, ev, payload.InlineAlignment())
-	} else {
+	case aStruct:
 		err = e.marshalStructFields(et, ev)
+	case aTable:
+		err = e.marshalTable(et, ev)
 	}
 	if err != nil {
 		return err
@@ -348,7 +378,7 @@
 			continue
 		}
 		var n nestedTypeData
-		if err := n.FromTag(f.Tag); err != nil {
+		if err := n.FromTag(f.Tag, false); err != nil {
 			return err
 		}
 		if err := e.marshal(f.Type, v.Field(i), n); err != nil {
@@ -376,7 +406,7 @@
 
 	f := t.Field(fieldIndex)
 	var n nestedTypeData
-	if err := n.FromTag(f.Tag); err != nil {
+	if err := n.FromTag(f.Tag, false); err != nil {
 		return err
 	}
 	// Re-align to the union's alignment before writing its field.
@@ -392,6 +422,14 @@
 	return nil
 }
 
+func (e *encoder) marshalTable(t reflect.Type, v reflect.Value) error {
+	m, err := createMarshaler(t)
+	if err != nil {
+		return err
+	}
+	return m.marshal(v, e)
+}
+
 // marshalArray marshals a FIDL array inline.
 func (e *encoder) marshalArray(t reflect.Type, v reflect.Value, n nestedTypeData) error {
 	elemType := t.Elem()
@@ -684,10 +722,13 @@
 		return err
 	}
 	d.head = align(d.head, a)
-	if isUnionType(t) {
+	switch whichStructKind(t) {
+	case aUnion:
 		err = d.unmarshalUnion(t, v, a)
-	} else {
+	case aStruct:
 		err = d.unmarshalStructFields(t, v)
+	case aTable:
+		err = d.unmarshalTable(t, v)
 	}
 	if err != nil {
 		return err
@@ -720,10 +761,13 @@
 	d.nextObject += align(payload.InlineSize(), 8)
 
 	// Unmarshal the value itself out-of-line.
-	if isUnionType(et) {
+	switch whichStructKind(et) {
+	case aUnion:
 		err = d.unmarshalUnion(et, ev, payload.InlineAlignment())
-	} else {
+	case aStruct:
 		err = d.unmarshalStructFields(et, ev)
+	case aTable:
+		err = d.unmarshalTable(et, ev)
 	}
 	if err != nil {
 		return err
@@ -746,7 +790,7 @@
 			continue
 		}
 		var n nestedTypeData
-		if err := n.FromTag(f.Tag); err != nil {
+		if err := n.FromTag(f.Tag, false); err != nil {
 			return err
 		}
 		if err := d.unmarshal(f.Type, v.Field(i), n); err != nil {
@@ -775,7 +819,7 @@
 
 	f := t.Field(fieldIndex)
 	var n nestedTypeData
-	if err := n.FromTag(f.Tag); err != nil {
+	if err := n.FromTag(f.Tag, false); err != nil {
 		return err
 	}
 	d.head = align(d.head, alignment)
@@ -790,6 +834,14 @@
 	return nil
 }
 
+func (d *decoder) unmarshalTable(t reflect.Type, v reflect.Value) error {
+	m, err := createMarshaler(t)
+	if err != nil {
+		return err
+	}
+	return m.unmarshal(d, v)
+}
+
 // unmarshalArray unmarshals an array inline based on Type t into Value v, taking into account
 // nestedTypeData n, since an array is a container type.
 func (d *decoder) unmarshalArray(t reflect.Type, v reflect.Value, n nestedTypeData) error {
diff --git a/src/syscall/zx/fidl/encoding_new.go b/src/syscall/zx/fidl/encoding_new.go
index 5a84446..e22a560 100644
--- a/src/syscall/zx/fidl/encoding_new.go
+++ b/src/syscall/zx/fidl/encoding_new.go
@@ -147,6 +147,7 @@
 	_                 = iota
 	structTag tagKind = iota
 	unionTag
+	tableTag
 	boundsTag
 )
 
@@ -168,26 +169,37 @@
 }
 
 func createMarshaler(typ reflect.Type) (Marshaler, error) {
+	// field 0 holds the tag
+	marshalerKind, marshalerBounds := readTag(typ.Field(0))
 	var (
-		kind      tagKind
-		size      int
-		alignment int
+		kind      = marshalerKind
+		size      = marshalerBounds[0]
+		alignment = marshalerBounds[1]
 		fields    []mField
+		ordinals  []int
 	)
-	for index := 0; index < typ.NumField(); index++ {
+	// field 1 and up are the actual data fields
+	// - structs and unions have fields one after the other;
+	// - tables have a field, followed by a bool presence indicator, etc.
+	for index := 1; index < typ.NumField(); index++ {
 		field := typ.Field(index)
-		fieldKind, fieldBounds := readTag(field)
-		if fieldKind == structTag || fieldKind == unionTag {
-			kind = fieldKind
-			size = fieldBounds[0]
-			alignment = fieldBounds[1]
-			continue
+		_, fieldBounds := readTag(field)
+		if kind == tableTag {
+			ordinal, actualBounds := fieldBounds.pop()
+			ordinals = append(ordinals, ordinal)
+			fieldBounds = actualBounds
 		}
 		fieldMarshaler, err := createMarshalerForField(field.Type, fieldBounds)
 		if err != nil {
 			return nil, err
 		}
 		fields = append(fields, mField{fieldMarshaler, index})
+		if kind == tableTag {
+			if typ.Field(index+1).Type.Kind() != reflect.Bool {
+				return nil, errors.New("incorrect presence field on " + nicefmt(typ))
+			}
+			index++ // i.e. skip presence field.
+		}
 	}
 
 	switch kind {
@@ -203,6 +215,15 @@
 			size:      size,
 			alignment: alignment,
 		}, nil
+	case tableTag:
+		return mTable{
+			mStruct: mStruct{
+				fields:    fields,
+				size:      size,
+				alignment: alignment,
+			},
+			ordinals: ordinals,
+		}, nil
 	default:
 		return nil, errors.New("missing kind marker on " + nicefmt(typ))
 	}
@@ -211,7 +232,8 @@
 // readTag reads a fidl tag which can be one of
 //
 //     s,size,alignement -- marking a struct, with its size, and alignment
-//     s,size,alignement -- marking a union, with its size, and alignment
+//     u,size,alignement -- marking a union, with its size, and alignment
+//     t,size,alignement -- marking a table, with its size, and alignment
 //     num1, num2, ...   -- recording bounds of the types present on the field
 //
 // When handles or interfaces are present, the bounds indicate nullability,
@@ -227,6 +249,8 @@
 		return structTag, toBounds(elems[1:])
 	case "u":
 		return unionTag, toBounds(elems[1:])
+	case "t":
+		return tableTag, toBounds(elems[1:])
 	default:
 		return boundsTag, toBounds(elems)
 	}
@@ -330,12 +354,17 @@
 	case mVector:
 		return mOptVector(m), nil
 	case mStruct:
-		return mOptStructOrUnion{
+		return mOptStructUnionTable{
 			Marshaler: m,
 			elemTyp:   typ,
 		}, nil
 	case mUnion:
-		return mOptStructOrUnion{
+		return mOptStructUnionTable{
+			Marshaler: m,
+			elemTyp:   typ,
+		}, nil
+	case mTable:
+		return mOptStructUnionTable{
 			Marshaler: m,
 			elemTyp:   typ,
 		}, nil
@@ -360,7 +389,8 @@
 var _ = []Marshaler{
 	mStruct{},
 	mUnion{},
-	mOptStructOrUnion{},
+	mTable{},
+	mOptStructUnionTable{},
 	mArray{},
 	mVector{},
 	mBool{},
@@ -467,16 +497,146 @@
 	return nil
 }
 
-type mOptStructOrUnion struct {
+type mTable struct {
+	mStruct
+	ordinals []int
+}
+
+func (m mTable) marshal(v reflect.Value, out *encoder) error {
+	// Vector of envelopes header.
+	numKnown := len(m.ordinals)
+	maxOrdinal := m.ordinals[numKnown-1]
+	out.writeUint(uint64(maxOrdinal), 8)
+	out.writeUint(allocPresent, 8)
+
+	// Sizing.
+	numPresent := 0
+	for i := 0; i < len(m.fields); i++ {
+		if v.Field(2 * (i + 1)).Bool() {
+			numPresent++
+		}
+	}
+	numBytesPerElement := m.fields[0].Marshaler.getSize()
+
+	// Encode in the out-of-line object.
+	oldHead := out.head
+	out.head = out.newObject(maxOrdinal*16 + numPresent*numBytesPerElement)
+
+	// Envelopes.
+	var (
+		ordinal                          = 1
+		index, fieldIndex, presenceIndex = 0, 1, 2
+		nextFieldHead                    = out.head + maxOrdinal*16
+	)
+	for ordinal <= maxOrdinal {
+		fieldKnown := index < numKnown && ordinal == m.ordinals[index]
+		fieldPresent := fieldKnown && v.Field(presenceIndex).Bool()
+
+		if fieldPresent {
+			numHandles := len(out.handles)
+			savedHead := out.head
+			out.head = nextFieldHead
+			if err := m.fields[index].marshal(v.Field(fieldIndex), out); err != nil {
+				return err
+			}
+			numHandles = len(out.handles) - numHandles
+			out.head = savedHead
+			nextFieldHead += numBytesPerElement
+			out.writeUint(uint64(numBytesPerElement), 4)
+			out.writeUint(uint64(numHandles), 4)
+			out.writeUint(allocPresent, 8)
+		} else {
+			// Write both num bytes and num handles at once.
+			out.writeUint(0, 8)
+			out.writeUint(noAlloc, 8)
+		}
+
+		ordinal++
+		if fieldKnown {
+			index++
+			fieldIndex += 2    // i.e. skip presenece field
+			presenceIndex += 2 // i.e. skip field
+		}
+	}
+
+	// Re-position head.
+	out.head = oldHead
+
+	return nil
+}
+
+func (m mTable) unmarshal(in *decoder, v reflect.Value) error {
+	maxOrdinal := int(in.readUint(8))
+	allocPtr := in.readUint(8)
+	switch allocPtr {
+	case allocPresent:
+		// good
+	case noAlloc:
+		return newValueError(ErrUnexpectedNullRef, v)
+	default:
+		return newValueError(ErrBadRefEncoding, v)
+	}
+
+	// Envelopes.
+	var (
+		numKnown                         = len(m.ordinals)
+		ordinal                          = 1
+		index, fieldIndex, presenceIndex = 0, 1, 2
+		nextFieldHead                    = in.head + maxOrdinal*16
+	)
+	for ordinal <= maxOrdinal {
+		fieldKnown := index < numKnown && ordinal == m.ordinals[index]
+
+		numBytes := int(in.readUint(4))
+		numHandles := int(in.readUint(4))
+		fieldPresent := in.readUint(8)
+		switch fieldPresent {
+		case allocPresent:
+			if fieldKnown {
+				savedHead := in.head
+				in.head = nextFieldHead
+				if err := m.fields[index].unmarshal(in, v.Field(fieldIndex)); err != nil {
+					return err
+				}
+				in.head = savedHead
+				v.Field(presenceIndex).SetBool(true)
+			} else {
+				for i := 0; i < numHandles; i++ {
+					in.handles[0].Close() // best effort
+					in.handles = in.handles[1:]
+				}
+			}
+			nextFieldHead += numBytes
+		case noAlloc:
+			// TODO(FIDL-237): We should check that numBytes and numHandles
+			// are 0, and reject messages where this is not the case. This
+			// requires all other bindings from properly memseting to 0
+			// all bytes of the buffer.
+		default:
+			return newValueError(ErrBadRefEncoding, v)
+		}
+
+		ordinal++
+		if fieldKnown {
+			index++
+			fieldIndex += 2    // i.e skip presence field
+			presenceIndex += 2 // i.e skip field
+		}
+	}
+
+	return nil
+}
+
+type mOptStructUnionTable struct {
 	Marshaler
 	elemTyp reflect.Type
 }
 
-func (m mOptStructOrUnion) getSize() int {
+func (m mOptStructUnionTable) getSize() int {
 	return 8
 }
 
-func (m mOptStructOrUnion) marshal(v reflect.Value, out *encoder) error {
+func (m mOptStructUnionTable) marshal(v reflect.Value, out *encoder) error {
 	// Nil?
 	if v.IsNil() {
 		out.writeUint(noAlloc, 8)
@@ -501,7 +661,7 @@
 	return nil
 }
 
-func (m mOptStructOrUnion) unmarshal(in *decoder, v reflect.Value) error {
+func (m mOptStructUnionTable) unmarshal(in *decoder, v reflect.Value) error {
 	// Nil?
 	ptr, err := in.safeReadUint(8)
 	if err != nil {
diff --git a/src/syscall/zx/fidl/fidl_test/encoding_new_test.go b/src/syscall/zx/fidl/fidl_test/encoding_new_test.go
index 4d84f0c1..424bf17 100644
--- a/src/syscall/zx/fidl/fidl_test/encoding_new_test.go
+++ b/src/syscall/zx/fidl/fidl_test/encoding_new_test.go
@@ -80,6 +80,10 @@
 		panic(fmt.Sprintf("failed to create vmo: %v", err))
 	}
 
+	st1 := SimpleTable{}
+	st1.SetX(42)
+	st1.SetY(67)
+
 	return []example{
 		// simple (to get started)
 		{"simple", &TestSimple{X: 124}, 8, &TestSimple{}},
@@ -202,6 +206,14 @@
 				Channel: zx.Channel(zx.HandleInvalid),
 			}),
 		}, 16, &TestInterface1{}},
+
+		// tables
+		{"table1", &TestSimpleTable{
+			Table: st1,
+		}, 112, &TestSimpleTable{}},
+		{"opt-table1", &TestOptSimpleTable{
+			OptTable: &st1,
+		}, 120, &TestOptSimpleTable{}},
 	}
 }
 
@@ -337,7 +349,168 @@
 	}
 }
 
-func TestNegativeMarshal(t *testing.T) {
+func makeDefault(msg Message) Message {
+	typ := reflect.TypeOf(msg)
+	if typ.Kind() != reflect.Ptr && typ.Elem().Kind() != reflect.Struct {
+		panic("expecting *struct")
+	}
+	return reflect.New(typ.Elem()).Interface().(Message)
+}
+
+// successCase represents a golden test for a success case, where encoding
+// and decoding should succceed.
+type successCase struct {
+	name  string
+	input Message
+	bytes []byte
+}
+
+func (ex successCase) check(t *testing.T) {
+	for _, mFn := range marshalFuncs {
+		for _, uFn := range unmarshalFuncs {
+			t.Run(ex.name+"_"+mFn.name+"_"+uFn.name, func(t *testing.T) {
+				var respb [zx.ChannelMaxMessageBytes]byte
+				var resph [zx.ChannelMaxMessageHandles]zx.Handle
+				nb, nh, err := mFn.fn(ex.input, respb[:], resph[:])
+				if err != nil {
+					t.Fatal(err)
+				}
+				if nh != 0 {
+					t.Fatalf("no handles expected, got %d", nh)
+				}
+				if !reflect.DeepEqual(ex.bytes, respb[:nb]) {
+					t.Fatalf("expected %v, got %v", ex.bytes, respb[:nb])
+				}
+				output := makeDefault(ex.input)
+				if err := uFn.fn(respb[:nb], resph[:nh], output); err != nil {
+					t.Fatalf("unmarshal: failed: %s", err)
+				}
+				if !reflect.DeepEqual(ex.input, output) {
+					t.Fatalf("unmarshal: expected: %v, got: %v", ex.input, output)
+				}
+			})
+		}
+	}
+}
+
+var simpleTableWithXY = []byte{
+	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
+}
+
+var simpleTableWithY = []byte{
+	5, 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, // envelope 1: num bytes / num handles
+	0, 0, 0, 0, 0, 0, 0, 0, // no alloc
+	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
+	67, 0, 0, 0, 0, 0, 0, 0, // field Y
+}
+
+func TestAllSuccessCases(t *testing.T) {
+	st1 := SimpleTable{}
+	st1.SetX(42)
+	st1.SetY(67)
+	successCase{
+		name: "simpletable-x-and-y",
+		input: &TestSimpleTable{
+			Table: st1,
+		},
+		bytes: simpleTableWithXY,
+	}.check(t)
+
+	st2 := SimpleTable{}
+	st2.SetY(67)
+	successCase{
+		name: "simpletable-just-y",
+		input: &TestSimpleTable{
+			Table: st2,
+		},
+		bytes: simpleTableWithY,
+	}.check(t)
+
+	st3 := SimpleTable{}
+	st3.SetX(42)
+	st3.SetY(67)
+	successCase{
+		name: "opt-simpletable-x-and-y",
+		input: &TestOptSimpleTable{
+			OptTable: &st3,
+		},
+		bytes: append([]byte{
+			255, 255, 255, 255, 255, 255, 255, 255, // alloc present,
+		},
+			simpleTableWithXY...),
+	}.check(t)
+}
+
+func TestTableCompatibilityWithXY(t *testing.T) {
+	ost := OlderSimpleTable{}
+	ost.SetX(42)
+	st := SimpleTable{}
+	st.SetX(42)
+	st.SetY(67)
+	nst := NewerSimpleTable{}
+	nst.SetX(42)
+	nst.SetY(67)
+	cases := []Message{
+		&TestOlderSimpleTable{Table: ost},
+		&TestSimpleTable{Table: st},
+		&TestNewerSimpleTable{Table: nst},
+	}
+	for _, expected := range cases {
+		for _, u := range unmarshalFuncs {
+			output := makeDefault(expected)
+			u.fn(simpleTableWithXY, nil, output)
+			if !reflect.DeepEqual(expected, output) {
+				t.Fatalf("unmarshal: expected: %v, got: %v", expected, output)
+			}
+		}
+	}
+}
+
+func TestTableCompatibilityWithY(t *testing.T) {
+	ost := OlderSimpleTable{}
+	st := SimpleTable{}
+	st.SetY(67)
+	nst := NewerSimpleTable{}
+	nst.SetY(67)
+	cases := []Message{
+		&TestOlderSimpleTable{Table: ost},
+		&TestSimpleTable{Table: st},
+		&TestNewerSimpleTable{Table: nst},
+	}
+	for _, expected := range cases {
+		for _, u := range unmarshalFuncs {
+			output := makeDefault(expected)
+			u.fn(simpleTableWithY, nil, output)
+			if !reflect.DeepEqual(expected, output) {
+				t.Fatalf("unmarshal: expected: %v, got: %v", expected, output)
+			}
+		}
+	}
+}
+
+func TestFailuresMarshal(t *testing.T) {
 	v1 := []int64{1, 2, 3}
 	cases := []struct {
 		name      string
@@ -387,7 +560,7 @@
 		})
 	}
 }
-func TestNegativeUnmarshalNoHandles(t *testing.T) {
+func TestFailuresUnmarshalNoHandles(t *testing.T) {
 	cases := []struct {
 		name      string
 		input     []byte
diff --git a/src/syscall/zx/fidl/fidl_test/encoding_test.go b/src/syscall/zx/fidl/fidl_test/encoding_test.go
index 097ce20..54537eb 100644
--- a/src/syscall/zx/fidl/fidl_test/encoding_test.go
+++ b/src/syscall/zx/fidl/fidl_test/encoding_test.go
@@ -200,4 +200,12 @@
 			}),
 		}, 16, &TestInterface1{})
 	})
+	t.Run("Tables", func(t *testing.T) {
+		st := SimpleTable{}
+		st.SetX(5)
+		st.SetY(7)
+		testIdentity(t, &TestSimpleTable{
+			Table: st,
+		}, 112, &TestSimpleTable{})
+	})
 }