Add interface method signatures
diff --git a/internal/cgen/cgen.go b/internal/cgen/cgen.go
index df2a8ac..124fac5 100644
--- a/internal/cgen/cgen.go
+++ b/internal/cgen/cgen.go
@@ -325,13 +325,30 @@
 }
 
 func insertInterfaceDeclarations(buf *buffer) error {
+	if err := parseBuiltInInterfaceMethods(); err != nil {
+		return err
+	}
+
+	g := &gen{
+		pkgPrefix: "wuffs_base__",
+		pkgName:   "base",
+		tm:        &builtInTokenMap,
+	}
+
 	buf.writes("// ---------------- Interface Declarations.\n\n")
 	for _, n := range builtin.Interfaces {
-		buf.printf("extern const char* wuffs_base__%s__vtable_name;\n", n)
-		buf.writeb('\n')
+		qid := t.QID{t.IDBase, builtInTokenMap.ByName(n)}
 
-		buf.printf("typedef struct wuffs_base__%s__struct wuffs_base__%s;\n", n, n)
-		buf.writeb('\n')
+		buf.printf("extern const char* wuffs_base__%s__vtable_name;\n\n", n)
+
+		buf.printf("typedef struct wuffs_base__%s__struct wuffs_base__%s;\n\n", n, n)
+
+		for _, f := range builtInInterfaceMethods[qid] {
+			if err := g.writeFuncSignature(buf, f, cppNone); err != nil {
+				return err
+			}
+			buf.writes(";\n\n")
+		}
 
 		buf.writes("#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)\n\n")
 
@@ -343,7 +360,20 @@
 		buf.writes("} private_impl;\n\n")
 
 		buf.writes("\n#ifdef __cplusplus\n\n")
-		// TODO: C++ methods.
+		for _, f := range builtInInterfaceMethods[qid] {
+			if err := g.writeFuncSignature(buf, f, cppInsideStruct); err != nil {
+				return err
+			}
+			buf.writes("{ return ")
+			buf.writes(g.funcCName(f))
+			buf.writes("(this")
+			for _, o := range f.In().Fields() {
+				buf.writeb(',')
+				buf.writes(aPrefix)
+				buf.writes(o.AsField().Name().Str(g.tm))
+			}
+			buf.writes(");}\n\n")
+		}
 		buf.writes("#endif  // __cplusplus\n\n")
 
 		buf.printf("};  // struct wuffs_base__%s__struct\n\n", n)
@@ -354,16 +384,67 @@
 }
 
 func insertInterfaceDefinitions(buf *buffer) error {
+	if err := parseBuiltInInterfaceMethods(); err != nil {
+		return err
+	}
+
+	g := &gen{
+		pkgPrefix: "wuffs_base__",
+		pkgName:   "base",
+		tm:        &builtInTokenMap,
+	}
+
 	buf.writes("// ---------------- Interface Definitions.\n\n")
 	for _, n := range builtin.Interfaces {
+		qid := t.QID{t.IDBase, builtInTokenMap.ByName(n)}
+
 		buf.printf("const char* wuffs_base__%s__vtable_name = "+
-			"\"{vtable}wuffs_base__%s\";\n", n, n)
+			"\"{vtable}wuffs_base__%s\";\n\n", n, n)
+
+		for _, f := range builtInInterfaceMethods[qid] {
+			returnsStatus := f.Effect().Coroutine() ||
+				((f.Out() != nil) && f.Out().IsStatus())
+
+			if err := g.writeFuncSignature(buf, f, cppNone); err != nil {
+				return err
+			}
+			buf.writes("{\n")
+			if err := writeFuncImplSelfMagicCheck(buf, g.tm, f); err != nil {
+				return err
+			}
+
+			// TODO.
+
+			buf.writes("return ")
+			if returnsStatus {
+				buf.writes("wuffs_base__make_status(wuffs_base__error__bad_vtable)")
+			} else if err := writeOutParamZeroValue(buf, g.tm, f.Out()); err != nil {
+				return err
+			}
+			buf.writes(";\n}\n\n")
+		}
 	}
-	buf.writeb('\n')
 
 	return nil
 }
 
+var (
+	builtInTokenMap         = t.Map{}
+	builtInInterfaceMethods = map[t.QID][]*a.Func{}
+)
+
+func parseBuiltInInterfaceMethods() error {
+	if len(builtInInterfaceMethods) != 0 {
+		return nil
+	}
+	return builtin.ParseFuncs(&builtInTokenMap, builtin.InterfaceFuncs, false,
+		func(f *a.Func) error {
+			qid := f.Receiver()
+			builtInInterfaceMethods[qid] = append(builtInInterfaceMethods[qid], f)
+			return nil
+		})
+}
+
 type gen struct {
 	pkgPrefix string // e.g. "wuffs_jpeg__"
 	pkgName   string // e.g. "jpeg"
diff --git a/internal/cgen/func.go b/internal/cgen/func.go
index e754e9f..f3cf09a 100644
--- a/internal/cgen/func.go
+++ b/internal/cgen/func.go
@@ -67,7 +67,7 @@
 		// TODO: this isn't right if r[0] != 0, i.e. the receiver is from a
 		// used package. There might be similar cases elsewhere in this
 		// package.
-		return g.pkgPrefix + r.Str(g.tm) + "__" + n.FuncName().Str(g.tm)
+		return g.pkgPrefix + r[1].Str(g.tm) + "__" + n.FuncName().Str(g.tm)
 	}
 	return g.pkgPrefix + n.FuncName().Str(g.tm)
 }
@@ -121,7 +121,7 @@
 			if n.Effect().Pure() {
 				b.writes("const ")
 			}
-			b.printf("%s%s *self", g.pkgPrefix, r.Str(g.tm))
+			b.printf("%s%s *self", g.pkgPrefix, r[1].Str(g.tm))
 			comma = true
 		}
 	}
@@ -217,7 +217,7 @@
 	return nil
 }
 
-func (g *gen) writeOutParamZeroValue(b *buffer, typ *a.TypeExpr) error {
+func writeOutParamZeroValue(b *buffer, tm *t.Map, typ *a.TypeExpr) error {
 	if typ == nil {
 		b.writes("wuffs_base__make_empty_struct()")
 		return nil
@@ -251,39 +251,47 @@
 			return nil
 		}
 	}
-	return fmt.Errorf("internal error: cannot write the zero value of type %q", typ.Str(g.tm))
+	return fmt.Errorf("internal error: cannot write the zero value of type %q", typ.Str(tm))
+}
+
+func writeFuncImplSelfMagicCheck(b *buffer, tm *t.Map, f *a.Func) error {
+	returnsStatus := f.Effect().Coroutine() ||
+		((f.Out() != nil) && f.Out().IsStatus())
+
+	b.writes("if (!self) { return ")
+	if returnsStatus {
+		b.writes("wuffs_base__make_status(wuffs_base__error__bad_receiver)")
+	} else if err := writeOutParamZeroValue(b, tm, f.Out()); err != nil {
+		return err
+	}
+	b.writes(";}")
+
+	if f.Effect().Pure() {
+		b.writes("if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&")
+		b.writes("    (self->private_impl.magic != WUFFS_BASE__DISABLED)) {")
+	} else {
+		b.writes("if (self->private_impl.magic != WUFFS_BASE__MAGIC) {")
+	}
+	b.writes("return ")
+	if returnsStatus {
+		b.writes("wuffs_base__make_status(" +
+			"(self->private_impl.magic == WUFFS_BASE__DISABLED) " +
+			"? wuffs_base__error__disabled_by_previous_error " +
+			": wuffs_base__error__initialize_not_called)")
+	} else if err := writeOutParamZeroValue(b, tm, f.Out()); err != nil {
+		return err
+	}
+
+	b.writes(";}\n")
+	return nil
 }
 
 func (g *gen) writeFuncImplPrologue(b *buffer) error {
 	// Check the initialized/disabled state and the "self" arg.
 	if g.currFunk.astFunc.Public() && !g.currFunk.astFunc.Receiver().IsZero() {
-		out := g.currFunk.astFunc.Out()
-
-		b.writes("if (!self) { return ")
-		if g.currFunk.returnsStatus {
-			b.writes("wuffs_base__make_status(wuffs_base__error__bad_receiver)")
-		} else if err := g.writeOutParamZeroValue(b, out); err != nil {
+		if err := writeFuncImplSelfMagicCheck(b, g.tm, g.currFunk.astFunc); err != nil {
 			return err
 		}
-		b.writes(";}")
-
-		if g.currFunk.astFunc.Effect().Pure() {
-			b.writes("if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&")
-			b.writes("    (self->private_impl.magic != WUFFS_BASE__DISABLED)) {")
-		} else {
-			b.writes("if (self->private_impl.magic != WUFFS_BASE__MAGIC) {")
-		}
-		b.writes("return ")
-		if g.currFunk.returnsStatus {
-			b.writes("wuffs_base__make_status(" +
-				"(self->private_impl.magic == WUFFS_BASE__DISABLED) " +
-				"? wuffs_base__error__disabled_by_previous_error " +
-				": wuffs_base__error__initialize_not_called)")
-		} else if err := g.writeOutParamZeroValue(b, out); err != nil {
-			return err
-		}
-
-		b.writes(";}\n")
 	}
 
 	// For public functions, check (at runtime) the other args for bounds and
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index 5123e33..3e57af2 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -16,6 +16,11 @@
 package builtin
 
 import (
+	"fmt"
+
+	"github.com/google/wuffs/lang/parse"
+
+	a "github.com/google/wuffs/lang/ast"
 	t "github.com/google/wuffs/lang/token"
 )
 
@@ -41,6 +46,7 @@
 	`"#bad receiver"`,
 	`"#bad restart"`,
 	`"#bad sizeof receiver"`,
+	`"#bad vtable"`,
 	`"#bad workbuf length"`,
 	`"#bad wuffs version"`,
 	`"#cannot return a suspension"`,
@@ -341,6 +347,12 @@
 	"hasher_u32",
 }
 
+var InterfaceFuncs = []string{
+	// ---- hasher_u32
+
+	"hasher_u32.update_u32!(x: slice u8) u32",
+}
+
 // The "T1" and "T2" types here are placeholders for generic "slice T" or
 // "table T" types. After tokenizing (but before parsing) these XxxFunc strings
 // (e.g. in the lang/check package), replace "T1" and "T2" with "†" or "‡"
@@ -367,3 +379,48 @@
 
 	"T2.row(y: u32) T1",
 }
+
+func ParseFuncs(tm *t.Map, ss []string, generic bool, callback func(*a.Func) error) error {
+	buf := []byte(nil)
+	for _, s := range ss {
+		buf = buf[:0]
+		buf = append(buf, "pub func "...)
+		buf = append(buf, s...)
+		buf = append(buf, "{}\n"...)
+
+		const filename = "builtin.wuffs"
+		tokens, _, err := t.Tokenize(tm, filename, buf)
+		if err != nil {
+			return fmt.Errorf("parsing %q: could not tokenize built-in funcs: %v", s, err)
+		}
+		if generic {
+			for i := range tokens {
+				if tokens[i].ID == GenericOldName1 {
+					tokens[i].ID = GenericNewName1
+				} else if tokens[i].ID == GenericOldName2 {
+					tokens[i].ID = GenericNewName2
+				}
+			}
+		}
+		file, err := parse.Parse(tm, filename, tokens, &parse.Options{
+			AllowBuiltInNames: true,
+		})
+		if err != nil {
+			return fmt.Errorf("parsing %q: could not parse built-in funcs: %v", s, err)
+		}
+
+		tlds := file.TopLevelDecls()
+		if len(tlds) != 1 || tlds[0].Kind() != a.KFunc {
+			return fmt.Errorf("parsing %q: got %d top level decls, want %d", s, len(tlds), 1)
+		}
+		f := tlds[0].AsFunc()
+		f.AsNode().AsRaw().SetPackage(tm, t.IDBase)
+
+		if callback != nil {
+			if err := callback(f); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
diff --git a/lang/check/resolve.go b/lang/check/resolve.go
index 0b66ffa..88eeb59 100644
--- a/lang/check/resolve.go
+++ b/lang/check/resolve.go
@@ -18,7 +18,6 @@
 	"fmt"
 
 	"github.com/google/wuffs/lang/builtin"
-	"github.com/google/wuffs/lang/parse"
 
 	a "github.com/google/wuffs/lang/ast"
 	t "github.com/google/wuffs/lang/token"
@@ -117,48 +116,18 @@
 		m = map[t.QQID]*a.Func{}
 	}
 
-	buf := []byte(nil)
-	for _, s := range ss {
-		buf = buf[:0]
-		buf = append(buf, "pub func "...)
-		buf = append(buf, s...)
-		buf = append(buf, "{}\n"...)
-
-		const filename = "builtin.wuffs"
-		tokens, _, err := t.Tokenize(c.tm, filename, buf)
-		if err != nil {
-			return nil, fmt.Errorf("check: parsing %q: could not tokenize built-in funcs: %v", s, err)
-		}
-		if generic {
-			for i := range tokens {
-				if tokens[i].ID == builtin.GenericOldName1 {
-					tokens[i].ID = builtin.GenericNewName1
-				} else if tokens[i].ID == builtin.GenericOldName2 {
-					tokens[i].ID = builtin.GenericNewName2
-				}
-			}
-		}
-		file, err := parse.Parse(c.tm, filename, tokens, &parse.Options{
-			AllowBuiltInNames: true,
-		})
-		if err != nil {
-			return nil, fmt.Errorf("check: parsing %q: could not parse built-in funcs: %v", s, err)
-		}
-
-		tlds := file.TopLevelDecls()
-		if len(tlds) != 1 || tlds[0].Kind() != a.KFunc {
-			return nil, fmt.Errorf("check: parsing %q: got %d top level decls, want %d", s, len(tlds), 1)
-		}
-		f := tlds[0].AsFunc()
-		f.AsNode().AsRaw().SetPackage(c.tm, t.IDBase)
+	if err := builtin.ParseFuncs(c.tm, ss, generic, func(f *a.Func) error {
 		if err := c.checkFuncSignature(f.AsNode()); err != nil {
-			return nil, err
+			return err
 		}
-
 		if m != nil {
 			m[f.QQID()] = f
 		}
+		return nil
+	}); err != nil {
+		return nil, err
 	}
+
 	return m, nil
 }
 
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 866890a..4d23d00 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -187,6 +187,7 @@
 extern const char* wuffs_base__error__bad_receiver;
 extern const char* wuffs_base__error__bad_restart;
 extern const char* wuffs_base__error__bad_sizeof_receiver;
+extern const char* wuffs_base__error__bad_vtable;
 extern const char* wuffs_base__error__bad_workbuf_length;
 extern const char* wuffs_base__error__bad_wuffs_version;
 extern const char* wuffs_base__error__cannot_return_a_suspension;
@@ -2569,6 +2570,10 @@
 
 typedef struct wuffs_base__hasher_u32__struct wuffs_base__hasher_u32;
 
+WUFFS_BASE__MAYBE_STATIC uint32_t  //
+wuffs_base__hasher_u32__update_u32(wuffs_base__hasher_u32* self,
+                                   wuffs_base__slice_u8 a_x);
+
 #if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
 
 struct wuffs_base__hasher_u32__struct {
@@ -2580,6 +2585,11 @@
 
 #ifdef __cplusplus
 
+  inline uint32_t  //
+  update_u32(wuffs_base__slice_u8 a_x) {
+    return wuffs_base__hasher_u32__update_u32(this, a_x);
+  }
+
 #endif  // __cplusplus
 
 };  // struct wuffs_base__hasher_u32__struct
@@ -4565,6 +4575,7 @@
 const char* wuffs_base__error__bad_restart = "#base: bad restart";
 const char* wuffs_base__error__bad_sizeof_receiver =
     "#base: bad sizeof receiver";
+const char* wuffs_base__error__bad_vtable = "#base: bad vtable";
 const char* wuffs_base__error__bad_workbuf_length = "#base: bad workbuf length";
 const char* wuffs_base__error__bad_wuffs_version = "#base: bad wuffs version";
 const char* wuffs_base__error__cannot_return_a_suspension =
@@ -4586,6 +4597,18 @@
 const char* wuffs_base__hasher_u32__vtable_name =
     "{vtable}wuffs_base__hasher_u32";
 
+WUFFS_BASE__MAYBE_STATIC uint32_t  //
+wuffs_base__hasher_u32__update_u32(wuffs_base__hasher_u32* self,
+                                   wuffs_base__slice_u8 a_x) {
+  if (!self) {
+    return 0;
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return 0;
+  }
+  return 0;
+}
+
 // ---------------- Images
 
 const uint32_t wuffs_base__pixel_format__bits_per_channel[16] = {