Implement io_limit
diff --git a/internal/cgen/base/io-private.h b/internal/cgen/base/io-private.h
index 851732b..61a247b 100644
--- a/internal/cgen/base/io-private.h
+++ b/internal/cgen/base/io-private.h
@@ -34,6 +34,15 @@
 
 // --------
 
+static inline void  //
+wuffs_base__io_reader__limit(const uint8_t** ptr_io2_r,
+                             const uint8_t* iop_r,
+                             uint64_t limit) {
+  if (((uint64_t)(*ptr_io2_r - iop_r)) > limit) {
+    *ptr_io2_r = iop_r + limit;
+  }
+}
+
 static inline uint32_t  //
 wuffs_base__io_reader__limited_copy_u32_to_slice(const uint8_t** ptr_iop_r,
                                                  const uint8_t* io2_r,
@@ -131,6 +140,15 @@
   return (uint64_t)(n);
 }
 
+static inline void  //
+wuffs_base__io_writer__limit(uint8_t** ptr_io2_w,
+                             uint8_t* iop_w,
+                             uint64_t limit) {
+  if (((uint64_t)(*ptr_io2_w - iop_w)) > limit) {
+    *ptr_io2_w = iop_w + limit;
+  }
+}
+
 static inline uint32_t  //
 wuffs_base__io_writer__limited_copy_u32_from_history(uint8_t** ptr_iop_w,
                                                      uint8_t* io1_w,
diff --git a/internal/cgen/data/data.go b/internal/cgen/data/data.go
index 2b6b7be..f992c1f 100644
--- a/internal/cgen/data/data.go
+++ b/internal/cgen/data/data.go
@@ -185,15 +185,15 @@
 const BaseIOPrivateH = "" +
 	"// ---------------- I/O\n\nstatic inline uint64_t  //\nwuffs_base__io__count_since(uint64_t mark, uint64_t index) {\n  if (index >= mark) {\n    return index - mark;\n  }\n  return 0;\n}\n\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__io__since(uint64_t mark, uint64_t index, uint8_t* ptr) {\n  if (index >= mark) {\n    return wuffs_base__make_slice_u8(ptr + mark, index - mark);\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n" +
 	"" +
-	"// --------\n\nstatic inline uint32_t  //\nwuffs_base__io_reader__limited_copy_u32_to_slice(const uint8_t** ptr_iop_r,\n                                                 const uint8_t* io2_r,\n                                                 uint32_t length,\n                                                 wuffs_base__slice_u8 dst) {\n  const uint8_t* iop_r = *ptr_iop_r;\n  size_t n = dst.len;\n  if (n > length) {\n    n = length;\n  }\n  if (n > ((size_t)(io2_r - iop_r))) {\n    n = (size_t)(io2_r - iop_r);\n  }\n  if (n > 0) {\n    memmove(dst.ptr, iop_r, n);\n    *ptr_iop_r += n;\n  }\n  return (uint32_t)(n);\n}\n\n// wuffs_base__io_reader__match7 returns whether the io_reader's upcoming bytes\n// start with the given prefix (up to 7 bytes long). It is peek-like, not\n// read-like, in that there are no side-effects.\n//\n// The low 3 bits of a hold the prefix length, n.\n//\n// The high 56 bits of a hold the prefix itself, in little-endian order. The\n// first prefix byte is in bits 8..=15, the second prefix byte is in bits\n// 16..=23" +
-	", etc. The high (8 * (7 - n)) bits are ignored.\n//\n// There are three possible return values:\n//  - 0 means success.\n//  - 1 means inconclusive, equivalent to \"$short read\".\n//  - 2 means failure.\nstatic inline uint32_t  //\nwuffs_base__io_reader__match7(const uint8_t* iop_r,\n                              const uint8_t* io2_r,\n                              wuffs_base__io_buffer* r,\n                              uint64_t a) {\n  uint32_t n = a & 7;\n  a >>= 8;\n  if ((io2_r - iop_r) >= 8) {\n    uint64_t x = wuffs_base__load_u64le__no_bounds_check(iop_r);\n    uint32_t shift = 8 * (8 - n);\n    return ((a << shift) == (x << shift)) ? 0 : 2;\n  }\n  for (; n > 0; n--) {\n    if (iop_r >= io2_r) {\n      return (r && r->meta.closed) ? 2 : 1;\n    } else if (*iop_r != ((uint8_t)(a))) {\n      return 2;\n    }\n    iop_r++;\n    a >>= 8;\n  }\n  return 0;\n}\n\nstatic inline wuffs_base__io_buffer*  //\nwuffs_base__io_reader__set(wuffs_base__io_buffer* b,\n                           const uint8_t** ptr_iop_r,\n                           c" +
-	"onst uint8_t** ptr_io0_r,\n                           const uint8_t** ptr_io1_r,\n                           const uint8_t** ptr_io2_r,\n                           wuffs_base__slice_u8 data) {\n  b->data = data;\n  b->meta.wi = data.len;\n  b->meta.ri = 0;\n  b->meta.pos = 0;\n  b->meta.closed = false;\n\n  *ptr_iop_r = data.ptr;\n  *ptr_io0_r = data.ptr;\n  *ptr_io1_r = data.ptr;\n  *ptr_io2_r = data.ptr + data.len;\n\n  return b;\n}\n\n" +
+	"// --------\n\nstatic inline void  //\nwuffs_base__io_reader__limit(const uint8_t** ptr_io2_r,\n                             const uint8_t* iop_r,\n                             uint64_t limit) {\n  if (((uint64_t)(*ptr_io2_r - iop_r)) > limit) {\n    *ptr_io2_r = iop_r + limit;\n  }\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_reader__limited_copy_u32_to_slice(const uint8_t** ptr_iop_r,\n                                                 const uint8_t* io2_r,\n                                                 uint32_t length,\n                                                 wuffs_base__slice_u8 dst) {\n  const uint8_t* iop_r = *ptr_iop_r;\n  size_t n = dst.len;\n  if (n > length) {\n    n = length;\n  }\n  if (n > ((size_t)(io2_r - iop_r))) {\n    n = (size_t)(io2_r - iop_r);\n  }\n  if (n > 0) {\n    memmove(dst.ptr, iop_r, n);\n    *ptr_iop_r += n;\n  }\n  return (uint32_t)(n);\n}\n\n// wuffs_base__io_reader__match7 returns whether the io_reader's upcoming bytes\n// start with the given prefix (up to 7 bytes long). It is peek-like, not\n" +
+	"// read-like, in that there are no side-effects.\n//\n// The low 3 bits of a hold the prefix length, n.\n//\n// The high 56 bits of a hold the prefix itself, in little-endian order. The\n// first prefix byte is in bits 8..=15, the second prefix byte is in bits\n// 16..=23, etc. The high (8 * (7 - n)) bits are ignored.\n//\n// There are three possible return values:\n//  - 0 means success.\n//  - 1 means inconclusive, equivalent to \"$short read\".\n//  - 2 means failure.\nstatic inline uint32_t  //\nwuffs_base__io_reader__match7(const uint8_t* iop_r,\n                              const uint8_t* io2_r,\n                              wuffs_base__io_buffer* r,\n                              uint64_t a) {\n  uint32_t n = a & 7;\n  a >>= 8;\n  if ((io2_r - iop_r) >= 8) {\n    uint64_t x = wuffs_base__load_u64le__no_bounds_check(iop_r);\n    uint32_t shift = 8 * (8 - n);\n    return ((a << shift) == (x << shift)) ? 0 : 2;\n  }\n  for (; n > 0; n--) {\n    if (iop_r >= io2_r) {\n      return (r && r->meta.closed) ? 2 : 1;\n    } else if (*iop_" +
+	"r != ((uint8_t)(a))) {\n      return 2;\n    }\n    iop_r++;\n    a >>= 8;\n  }\n  return 0;\n}\n\nstatic inline wuffs_base__io_buffer*  //\nwuffs_base__io_reader__set(wuffs_base__io_buffer* b,\n                           const uint8_t** ptr_iop_r,\n                           const uint8_t** ptr_io0_r,\n                           const uint8_t** ptr_io1_r,\n                           const uint8_t** ptr_io2_r,\n                           wuffs_base__slice_u8 data) {\n  b->data = data;\n  b->meta.wi = data.len;\n  b->meta.ri = 0;\n  b->meta.pos = 0;\n  b->meta.closed = false;\n\n  *ptr_iop_r = data.ptr;\n  *ptr_io0_r = data.ptr;\n  *ptr_io1_r = data.ptr;\n  *ptr_io2_r = data.ptr + data.len;\n\n  return b;\n}\n\n" +
 	"" +
-	"// --------\n\nstatic inline uint64_t  //\nwuffs_base__io_writer__copy_from_slice(uint8_t** ptr_iop_w,\n                                       uint8_t* io2_w,\n                                       wuffs_base__slice_u8 src) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = src.len;\n  if (n > ((size_t)(io2_w - iop_w))) {\n    n = (size_t)(io2_w - iop_w);\n  }\n  if (n > 0) {\n    memmove(iop_w, src.ptr, n);\n    *ptr_iop_w += n;\n  }\n  return (uint64_t)(n);\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__limited_copy_u32_from_history(uint8_t** ptr_iop_w,\n                                                     uint8_t* io1_w,\n                                                     uint8_t* io2_w,\n                                                     uint32_t length,\n                                                     uint32_t distance) {\n  if (!distance) {\n    return 0;\n  }\n  uint8_t* p = *ptr_iop_w;\n  if ((size_t)(p - io1_w) < (size_t)(distance)) {\n    return 0;\n  }\n  uint8_t* q = p - distance;\n  size_t n = (size_t)(io2_w - " +
-	"p);\n  if ((size_t)(length) > n) {\n    length = (uint32_t)(n);\n  } else {\n    n = (size_t)(length);\n  }\n  // TODO: unrolling by 3 seems best for the std/deflate benchmarks, but that\n  // is mostly because 3 is the minimum length for the deflate format. This\n  // function implementation shouldn't overfit to that one format. Perhaps the\n  // limited_copy_u32_from_history Wuffs method should also take an unroll hint\n  // argument, and the cgen can look if that argument is the constant\n  // expression '3'.\n  //\n  // See also wuffs_base__io_writer__limited_copy_u32_from_history_fast below.\n  //\n  // Alternatively or additionally, have a sloppy_limited_copy_u32_from_history\n  // method that copies 8 bytes at a time, which can more than length bytes?\n  for (; n >= 3; n -= 3) {\n    *p++ = *q++;\n    *p++ = *q++;\n    *p++ = *q++;\n  }\n  for (; n; n--) {\n    *p++ = *q++;\n  }\n  *ptr_iop_w = p;\n  return length;\n}\n\n// wuffs_base__io_writer__limited_copy_u32_from_history_fast is like the\n// wuffs_base__io_writer__limited_copy" +
-	"_u32_from_history function above, but has\n// stronger pre-conditions. The caller needs to prove that:\n//  - distance >  0\n//  - distance <= (*ptr_iop_w - io1_w)\n//  - length   <= (io2_w      - *ptr_iop_w)\nstatic inline uint32_t  //\nwuffs_base__io_writer__limited_copy_u32_from_history_fast(uint8_t** ptr_iop_w,\n                                                          uint8_t* io1_w,\n                                                          uint8_t* io2_w,\n                                                          uint32_t length,\n                                                          uint32_t distance) {\n  uint8_t* p = *ptr_iop_w;\n  uint8_t* q = p - distance;\n  uint32_t n = length;\n  for (; n >= 3; n -= 3) {\n    *p++ = *q++;\n    *p++ = *q++;\n    *p++ = *q++;\n  }\n  for (; n; n--) {\n    *p++ = *q++;\n  }\n  *ptr_iop_w = p;\n  return length;\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__limited_copy_u32_from_reader(uint8_t** ptr_iop_w,\n                                                    uint8_t* io2_w,\n    " +
-	"                                                uint32_t length,\n                                                    const uint8_t** ptr_iop_r,\n                                                    const uint8_t* io2_r) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = length;\n  if (n > ((size_t)(io2_w - iop_w))) {\n    n = (size_t)(io2_w - iop_w);\n  }\n  const uint8_t* iop_r = *ptr_iop_r;\n  if (n > ((size_t)(io2_r - iop_r))) {\n    n = (size_t)(io2_r - iop_r);\n  }\n  if (n > 0) {\n    memmove(iop_w, iop_r, n);\n    *ptr_iop_w += n;\n    *ptr_iop_r += n;\n  }\n  return (uint32_t)(n);\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__limited_copy_u32_from_slice(uint8_t** ptr_iop_w,\n                                                   uint8_t* io2_w,\n                                                   uint32_t length,\n                                                   wuffs_base__slice_u8 src) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = src.len;\n  if (n > length) {\n    n = length;\n  }\n  if (n > ((size_t)(io2_w - iop_w))) {\n" +
-	"    n = (size_t)(io2_w - iop_w);\n  }\n  if (n > 0) {\n    memmove(iop_w, src.ptr, n);\n    *ptr_iop_w += n;\n  }\n  return (uint32_t)(n);\n}\n\nstatic inline wuffs_base__io_buffer*  //\nwuffs_base__io_writer__set(wuffs_base__io_buffer* b,\n                           uint8_t** ptr_iop_w,\n                           uint8_t** ptr_io0_w,\n                           uint8_t** ptr_io1_w,\n                           uint8_t** ptr_io2_w,\n                           wuffs_base__slice_u8 data) {\n  b->data = data;\n  b->meta.wi = 0;\n  b->meta.ri = 0;\n  b->meta.pos = 0;\n  b->meta.closed = false;\n\n  *ptr_iop_w = data.ptr;\n  *ptr_io0_w = data.ptr;\n  *ptr_io1_w = data.ptr;\n  *ptr_io2_w = data.ptr + data.len;\n\n  return b;\n}\n\n" +
+	"// --------\n\nstatic inline uint64_t  //\nwuffs_base__io_writer__copy_from_slice(uint8_t** ptr_iop_w,\n                                       uint8_t* io2_w,\n                                       wuffs_base__slice_u8 src) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = src.len;\n  if (n > ((size_t)(io2_w - iop_w))) {\n    n = (size_t)(io2_w - iop_w);\n  }\n  if (n > 0) {\n    memmove(iop_w, src.ptr, n);\n    *ptr_iop_w += n;\n  }\n  return (uint64_t)(n);\n}\n\nstatic inline void  //\nwuffs_base__io_writer__limit(uint8_t** ptr_io2_w,\n                             uint8_t* iop_w,\n                             uint64_t limit) {\n  if (((uint64_t)(*ptr_io2_w - iop_w)) > limit) {\n    *ptr_io2_w = iop_w + limit;\n  }\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__limited_copy_u32_from_history(uint8_t** ptr_iop_w,\n                                                     uint8_t* io1_w,\n                                                     uint8_t* io2_w,\n                                                     uint32_t length,\n           " +
+	"                                          uint32_t distance) {\n  if (!distance) {\n    return 0;\n  }\n  uint8_t* p = *ptr_iop_w;\n  if ((size_t)(p - io1_w) < (size_t)(distance)) {\n    return 0;\n  }\n  uint8_t* q = p - distance;\n  size_t n = (size_t)(io2_w - p);\n  if ((size_t)(length) > n) {\n    length = (uint32_t)(n);\n  } else {\n    n = (size_t)(length);\n  }\n  // TODO: unrolling by 3 seems best for the std/deflate benchmarks, but that\n  // is mostly because 3 is the minimum length for the deflate format. This\n  // function implementation shouldn't overfit to that one format. Perhaps the\n  // limited_copy_u32_from_history Wuffs method should also take an unroll hint\n  // argument, and the cgen can look if that argument is the constant\n  // expression '3'.\n  //\n  // See also wuffs_base__io_writer__limited_copy_u32_from_history_fast below.\n  //\n  // Alternatively or additionally, have a sloppy_limited_copy_u32_from_history\n  // method that copies 8 bytes at a time, which can more than length bytes?\n  for (; n >= 3; " +
+	"n -= 3) {\n    *p++ = *q++;\n    *p++ = *q++;\n    *p++ = *q++;\n  }\n  for (; n; n--) {\n    *p++ = *q++;\n  }\n  *ptr_iop_w = p;\n  return length;\n}\n\n// wuffs_base__io_writer__limited_copy_u32_from_history_fast is like the\n// wuffs_base__io_writer__limited_copy_u32_from_history function above, but has\n// stronger pre-conditions. The caller needs to prove that:\n//  - distance >  0\n//  - distance <= (*ptr_iop_w - io1_w)\n//  - length   <= (io2_w      - *ptr_iop_w)\nstatic inline uint32_t  //\nwuffs_base__io_writer__limited_copy_u32_from_history_fast(uint8_t** ptr_iop_w,\n                                                          uint8_t* io1_w,\n                                                          uint8_t* io2_w,\n                                                          uint32_t length,\n                                                          uint32_t distance) {\n  uint8_t* p = *ptr_iop_w;\n  uint8_t* q = p - distance;\n  uint32_t n = length;\n  for (; n >= 3; n -= 3) {\n    *p++ = *q++;\n    *p++ = *q++;\n    *p++ = *q++;\n" +
+	"  }\n  for (; n; n--) {\n    *p++ = *q++;\n  }\n  *ptr_iop_w = p;\n  return length;\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__limited_copy_u32_from_reader(uint8_t** ptr_iop_w,\n                                                    uint8_t* io2_w,\n                                                    uint32_t length,\n                                                    const uint8_t** ptr_iop_r,\n                                                    const uint8_t* io2_r) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = length;\n  if (n > ((size_t)(io2_w - iop_w))) {\n    n = (size_t)(io2_w - iop_w);\n  }\n  const uint8_t* iop_r = *ptr_iop_r;\n  if (n > ((size_t)(io2_r - iop_r))) {\n    n = (size_t)(io2_r - iop_r);\n  }\n  if (n > 0) {\n    memmove(iop_w, iop_r, n);\n    *ptr_iop_w += n;\n    *ptr_iop_r += n;\n  }\n  return (uint32_t)(n);\n}\n\nstatic inline uint32_t  //\nwuffs_base__io_writer__limited_copy_u32_from_slice(uint8_t** ptr_iop_w,\n                                                   uint8_t* io2_w,\n                         " +
+	"                          uint32_t length,\n                                                   wuffs_base__slice_u8 src) {\n  uint8_t* iop_w = *ptr_iop_w;\n  size_t n = src.len;\n  if (n > length) {\n    n = length;\n  }\n  if (n > ((size_t)(io2_w - iop_w))) {\n    n = (size_t)(io2_w - iop_w);\n  }\n  if (n > 0) {\n    memmove(iop_w, src.ptr, n);\n    *ptr_iop_w += n;\n  }\n  return (uint32_t)(n);\n}\n\nstatic inline wuffs_base__io_buffer*  //\nwuffs_base__io_writer__set(wuffs_base__io_buffer* b,\n                           uint8_t** ptr_iop_w,\n                           uint8_t** ptr_io0_w,\n                           uint8_t** ptr_io1_w,\n                           uint8_t** ptr_io2_w,\n                           wuffs_base__slice_u8 data) {\n  b->data = data;\n  b->meta.wi = 0;\n  b->meta.ri = 0;\n  b->meta.pos = 0;\n  b->meta.closed = false;\n\n  *ptr_iop_w = data.ptr;\n  *ptr_io0_w = data.ptr;\n  *ptr_io1_w = data.ptr;\n  *ptr_io2_w = data.ptr + data.len;\n\n  return b;\n}\n\n" +
 	"" +
 	"// ---------------- I/O (Utility)\n\n#define wuffs_base__utility__empty_io_reader wuffs_base__empty_io_reader\n#define wuffs_base__utility__empty_io_writer wuffs_base__empty_io_writer\n" +
 	""
diff --git a/internal/cgen/statement.go b/internal/cgen/statement.go
index 7ff3efe..487f663 100644
--- a/internal/cgen/statement.go
+++ b/internal/cgen/statement.go
@@ -245,34 +245,47 @@
 			cTyp, qualifier = "writer", ""
 		}
 		name := e.Ident().Str(g.tm)
-		b.printf("wuffs_base__io_buffer* %s%d_%s%s = %s%s;\n",
-			oPrefix, ioBindNum, prefix, name, prefix, name)
-
-		// TODO: save / restore all iop vars, not just for local IO vars? How
-		// does this work if the io_bind body advances these pointers, either
-		// directly or by calling other funcs?
-		if e.Operator() == 0 {
-			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
-				qualifier, oPrefix, ioBindNum, iopPrefix, prefix, name, iopPrefix, prefix, name)
-			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
-				qualifier, oPrefix, ioBindNum, io0Prefix, prefix, name, io0Prefix, prefix, name)
-			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
-				qualifier, oPrefix, ioBindNum, io1Prefix, prefix, name, io1Prefix, prefix, name)
-			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
-				qualifier, oPrefix, ioBindNum, io2Prefix, prefix, name, io2Prefix, prefix, name)
-		}
 
 		if n.Keyword() == t.IDIOBind {
-			b.printf("%s%s = wuffs_base__io_%s__set(\n&%s%s,\n&%s%s%s,\n&%s%s%s,\n&%s%s%s,\n&%s%s%s,\n",
-				prefix, name, cTyp, uPrefix, name, iopPrefix, prefix, name,
-				io0Prefix, prefix, name, io1Prefix, prefix, name, io2Prefix, prefix, name)
+			b.printf("wuffs_base__io_buffer* %s%d_%s%s = %s%s;\n",
+				oPrefix, ioBindNum, prefix, name,
+				prefix, name)
+			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
+				qualifier, oPrefix, ioBindNum, iopPrefix, prefix, name,
+				iopPrefix, prefix, name)
+			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
+				qualifier, oPrefix, ioBindNum, io0Prefix, prefix, name,
+				io0Prefix, prefix, name)
+			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
+				qualifier, oPrefix, ioBindNum, io1Prefix, prefix, name,
+				io1Prefix, prefix, name)
+			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
+				qualifier, oPrefix, ioBindNum, io2Prefix, prefix, name,
+				io2Prefix, prefix, name)
+			b.printf("%s%s = wuffs_base__io_%s__set("+
+				"\n&%s%s,\n&%s%s%s,\n&%s%s%s,\n&%s%s%s,\n&%s%s%s,\n",
+				prefix, name, cTyp,
+				uPrefix, name,
+				iopPrefix, prefix, name,
+				io0Prefix, prefix, name,
+				io1Prefix, prefix, name,
+				io2Prefix, prefix, name)
 			if err := g.writeExpr(b, n.Arg1(), 0); err != nil {
 				return err
 			}
 			b.writes(");\n")
 
 		} else {
-			return fmt.Errorf("TODO: implement io_limit (or remove it from the parser)")
+			b.printf("%suint8_t *%s%d_%s%s%s = %s%s%s;\n",
+				qualifier, oPrefix, ioBindNum, io2Prefix, prefix, name, io2Prefix, prefix, name)
+			b.printf("wuffs_base__io_%s__limit(&%s%s%s, %s%s%s,\n",
+				cTyp,
+				io2Prefix, prefix, name,
+				iopPrefix, prefix, name)
+			if err := g.writeExpr(b, n.Arg1(), 0); err != nil {
+				return err
+			}
+			b.writes(");\n")
 		}
 	}
 
@@ -289,18 +302,24 @@
 			prefix = aPrefix
 		}
 		name := e.Ident().Str(g.tm)
-		b.printf("%s%s = %s%d_%s%s;\n",
-			prefix, name, oPrefix, ioBindNum, prefix, name)
-		if e.Operator() == 0 {
+
+		if n.Keyword() == t.IDIOBind {
+			b.printf("%s%s = %s%d_%s%s;\n",
+				prefix, name,
+				oPrefix, ioBindNum, prefix, name)
 			b.printf("%s%s%s = %s%d_%s%s%s;\n",
-				iopPrefix, prefix, name, oPrefix, ioBindNum, iopPrefix, prefix, name)
+				iopPrefix, prefix, name,
+				oPrefix, ioBindNum, iopPrefix, prefix, name)
 			b.printf("%s%s%s = %s%d_%s%s%s;\n",
-				io0Prefix, prefix, name, oPrefix, ioBindNum, io0Prefix, prefix, name)
+				io0Prefix, prefix, name,
+				oPrefix, ioBindNum, io0Prefix, prefix, name)
 			b.printf("%s%s%s = %s%d_%s%s%s;\n",
-				io1Prefix, prefix, name, oPrefix, ioBindNum, io1Prefix, prefix, name)
-			b.printf("%s%s%s = %s%d_%s%s%s;\n",
-				io2Prefix, prefix, name, oPrefix, ioBindNum, io2Prefix, prefix, name)
+				io1Prefix, prefix, name,
+				oPrefix, ioBindNum, io1Prefix, prefix, name)
 		}
+		b.printf("%s%s%s = %s%d_%s%s%s;\n",
+			io2Prefix, prefix, name,
+			oPrefix, ioBindNum, io2Prefix, prefix, name)
 	}
 	b.writes("}\n")
 	return nil
diff --git a/internal/cgen/var.go b/internal/cgen/var.go
index 6bdd391..f59e651 100644
--- a/internal/cgen/var.go
+++ b/internal/cgen/var.go
@@ -42,25 +42,30 @@
 	}
 	for _, o := range g.currFunk.astFunc.Body() {
 		err := o.Walk(func(p *a.Node) error {
-			// Look for p matching "args.name.etc(etc)".
-			if p.Kind() != a.KExpr {
-				return nil
-			}
-			recv, meth, args := p.AsExpr().IsMethodCall()
-			if recv == nil {
-				return nil
-			}
-			if recv.IsArgsDotFoo() == name {
-				return errNeedDerivedVar
-			}
-			// Some built-in methods will also need a derived var for their
-			// arguments.
-			//
-			// TODO: use a comprehensive list of such methods.
-			switch meth {
-			case t.IDLimitedSwizzleU32InterleavedFromReader,
-				t.IDSwizzleInterleavedFromReader:
-				if recv.MType().Eq(typeExprPixelSwizzler) && argsContainsArgsDotFoo(args, name) {
+			switch p.Kind() {
+			case a.KExpr:
+				// Look for p matching "args.name.etc(etc)".
+				recv, meth, args := p.AsExpr().IsMethodCall()
+				if recv == nil {
+					return nil
+				}
+				if recv.IsArgsDotFoo() == name {
+					return errNeedDerivedVar
+				}
+				// Some built-in methods will also need a derived var for their
+				// arguments.
+				//
+				// TODO: use a comprehensive list of such methods.
+				switch meth {
+				case t.IDLimitedSwizzleU32InterleavedFromReader,
+					t.IDSwizzleInterleavedFromReader:
+					if recv.MType().Eq(typeExprPixelSwizzler) && argsContainsArgsDotFoo(args, name) {
+						return errNeedDerivedVar
+					}
+				}
+
+			case a.KIOBind:
+				if p.AsIOBind().IO().IsArgsDotFoo() == name {
 					return errNeedDerivedVar
 				}
 			}
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index b7ac21c..e61e476 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -9307,6 +9307,15 @@
 
 // --------
 
+static inline void  //
+wuffs_base__io_reader__limit(const uint8_t** ptr_io2_r,
+                             const uint8_t* iop_r,
+                             uint64_t limit) {
+  if (((uint64_t)(*ptr_io2_r - iop_r)) > limit) {
+    *ptr_io2_r = iop_r + limit;
+  }
+}
+
 static inline uint32_t  //
 wuffs_base__io_reader__limited_copy_u32_to_slice(const uint8_t** ptr_iop_r,
                                                  const uint8_t* io2_r,
@@ -9404,6 +9413,15 @@
   return (uint64_t)(n);
 }
 
+static inline void  //
+wuffs_base__io_writer__limit(uint8_t** ptr_io2_w,
+                             uint8_t* iop_w,
+                             uint64_t limit) {
+  if (((uint64_t)(*ptr_io2_w - iop_w)) > limit) {
+    *ptr_io2_w = iop_w + limit;
+  }
+}
+
 static inline uint32_t  //
 wuffs_base__io_writer__limited_copy_u32_from_history(uint8_t** ptr_iop_w,
                                                      uint8_t* io1_w,
@@ -29993,13 +30011,30 @@
   self->private_impl.active_coroutine = 0;
   wuffs_base__status status = wuffs_base__make_status(NULL);
 
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
   uint32_t coro_susp_point = self->private_impl.p_decode_frame[0];
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
     if (self->private_impl.f_call_sequence < 4) {
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+      }
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
       status = wuffs_png__decoder__decode_frame_config(self, NULL, a_src);
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
+      }
       if (status.repr) {
         goto suspend;
       }
@@ -30008,6 +30043,16 @@
       status = wuffs_base__make_status(wuffs_base__note__end_of_data);
       goto ok;
     }
+    while (true) {
+      {
+        const uint8_t *o_0_io2_a_src = io2_a_src;
+        wuffs_base__io_reader__limit(&io2_a_src, iop_a_src,
+            ((uint64_t)(self->private_impl.f_chunk_length)));
+        io2_a_src = o_0_io2_a_src;
+      }
+      goto label__0__break;
+    }
+    label__0__break:;
     self->private_impl.f_call_sequence = 255;
 
     goto ok;
@@ -30023,6 +30068,10 @@
 
   goto exit;
   exit:
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
   if (wuffs_base__status__is_error(&status)) {
     self->private_impl.magic = WUFFS_BASE__DISABLED;
   }
diff --git a/std/png/decode_png.wuffs b/std/png/decode_png.wuffs
index 208b878..f99be8e 100644
--- a/std/png/decode_png.wuffs
+++ b/std/png/decode_png.wuffs
@@ -231,6 +231,12 @@
 		return base."@end of data"
 	}
 
+	while true {
+		io_limit (io: args.src, limit: this.chunk_length as base.u64) {
+		}
+		break
+	} endwhile
+
 	this.call_sequence = 0xFF
 }