std/xz: use io_forget_history
diff --git a/internal/cgen/cgen.go b/internal/cgen/cgen.go
index 7c4a775..cc3ed64 100644
--- a/internal/cgen/cgen.go
+++ b/internal/cgen/cgen.go
@@ -50,7 +50,7 @@
 	aPrefix = "a_" // Function argument.
 	fPrefix = "f_" // Struct field.
 	iPrefix = "i_" // Iterate variable.
-	oPrefix = "o_" // Temporary io_bind variable.
+	oPrefix = "o_" // Temporary IOManip variable.
 	pPrefix = "p_" // Coroutine suspension point (program counter).
 	sPrefix = "s_" // Coroutine stack (saved local variables).
 	tPrefix = "t_" // Temporary local variable.
@@ -67,10 +67,12 @@
 // The other two prefixes, giving names like io1_etc and io2_etc, are auxiliary
 // pointers: lower and upper inclusive bounds. As an iop_etc pointer advances,
 // it cannot advance past io2_etc. In the rarer case that an iop_etc pointer
-// retreats, undoing a read or write, it cannot retreat past io1_etc.
+// retreats, undoing a read or write, it cannot retreat past io1_etc. The
+// io0_etc pointer is used to calculate the history_length and is the base that
+// marks are relative to.
 //
 // The iop_etc pointer can change over the lifetime of a function. The ioN_etc
-// pointers, for numeric N, cannot.
+// pointers, for numeric N, cannot (except by IOManip blocks).
 //
 // At the start of a function, these pointers are initialized from an
 // io_buffer's fields (ptr, ri, wi, len). For an io_reader:
diff --git a/internal/cgen/statement.go b/internal/cgen/statement.go
index 795b884..184189b 100644
--- a/internal/cgen/statement.go
+++ b/internal/cgen/statement.go
@@ -347,6 +347,46 @@
 			}
 			b.writes(");\n")
 
+		} else if keyword == t.IDIOForgetHistory {
+			if !isWriter {
+				return fmt.Errorf("unsupported io_forget_history for an io_reader")
+			}
+			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("%s%s%s = %s%s%s;\n",
+				io0Prefix, prefix, name, iopPrefix, prefix, name)
+			b.printf("%s%s%s = %s%s%s;\n",
+				io1Prefix, prefix, name, iopPrefix, prefix, name)
+			b.printf("wuffs_base__io_buffer %s%d_%s%s;\n",
+				oPrefix, ioBindNum, prefix, name)
+			b.printf("if (%s%s) {\n",
+				prefix, name)
+			if isWriter {
+				b.printf("memcpy(&%s%d_%s%s, %s%s, sizeof(*%s%s));\n",
+					oPrefix, ioBindNum, prefix, name,
+					prefix, name,
+					prefix, name)
+				b.printf("size_t wi%d = %s%s->meta.wi;\n",
+					ioBindNum, prefix, name)
+				b.printf("%s%s->data.ptr += wi%d;\n",
+					prefix, name, ioBindNum)
+				b.printf("%s%s->data.len -= wi%d;\n",
+					prefix, name, ioBindNum)
+				b.printf("%s%s->meta.ri = 0;\n",
+					prefix, name)
+				b.printf("%s%s->meta.wi = 0;\n",
+					prefix, name)
+				b.printf("%s%s->meta.pos = wuffs_base__u64__sat_add(%s%s->meta.pos, wi%d);\n",
+					prefix, name, prefix, name, ioBindNum)
+			} else {
+				// TODO.
+			}
+			b.printf("}\n")
+
 		} else {
 			if !isWriter {
 				b.printf("const bool %s%d_closed_%s%s = %s%s->meta.closed;\n",
@@ -399,13 +439,36 @@
 				io2Prefix, prefix, name,
 				oPrefix, ioBindNum, io2Prefix, prefix, name)
 
+		} else if keyword == t.IDIOForgetHistory {
+			b.printf("if (%s%s) {\n",
+				prefix, name)
+			if isWriter {
+				b.printf("memcpy(%s%s, &%s%d_%s%s, sizeof(*%s%s));\n",
+					prefix, name,
+					oPrefix, ioBindNum, prefix, name,
+					prefix, name)
+				b.printf("%s%s->meta.wi = ((size_t)(%s%s%s - %s%s->data.ptr));\n",
+					prefix, name, iopPrefix, prefix, name, prefix, name)
+				b.printf("%s%s%s = %s%d_%s%s%s;\n",
+					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)
+			} else {
+				// TODO.
+			}
+			b.printf("}\n")
+
 		} else {
 			b.printf("%s%s%s = %s%d_%s%s%s;\n",
 				io2Prefix, prefix, name,
 				oPrefix, ioBindNum, io2Prefix, prefix, name)
 			b.printf("if (%s%s) {\n", prefix, name)
-			b.printf("%s%s->meta.closed = %s%d_closed_%s%s;\n",
-				prefix, name, oPrefix, ioBindNum, prefix, name)
+			if !isWriter {
+				b.printf("%s%s->meta.closed = %s%d_closed_%s%s;\n",
+					prefix, name, oPrefix, ioBindNum, prefix, name)
+			}
 			b.printf("%s%s->%s = ((size_t)(%s%s%s - %s%s->data.ptr));\n",
 				prefix, name, end,
 				io2Prefix, prefix, name,
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 4fd2387..596a5c7 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -67290,19 +67290,41 @@
           }
         } else {
           {
+            uint8_t* o_0_io0_a_dst = io0_a_dst;
+            uint8_t* o_0_io1_a_dst = io1_a_dst;
+            io0_a_dst = iop_a_dst;
+            io1_a_dst = iop_a_dst;
+            wuffs_base__io_buffer o_0_a_dst;
             if (a_dst) {
+              memcpy(&o_0_a_dst, a_dst, sizeof(*a_dst));
+              size_t wi0 = a_dst->meta.wi;
+              a_dst->data.ptr += wi0;
+              a_dst->data.len -= wi0;
+              a_dst->meta.ri = 0;
+              a_dst->meta.wi = 0;
+              a_dst->meta.pos = wuffs_base__u64__sat_add(a_dst->meta.pos, wi0);
+            }
+            {
+              if (a_dst) {
+                a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
+              }
+              if (a_src) {
+                a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+              }
+              wuffs_base__status t_5 = wuffs_lzma__decoder__transform_io(&self->private_data.f_lzma, a_dst, a_src, a_workbuf);
+              v_status = t_5;
+              if (a_dst) {
+                iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
+              }
+              if (a_src) {
+                iop_a_src = a_src->data.ptr + a_src->meta.ri;
+              }
+            }
+            if (a_dst) {
+              memcpy(a_dst, &o_0_a_dst, sizeof(*a_dst));
               a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
-            }
-            if (a_src) {
-              a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
-            }
-            wuffs_base__status t_5 = wuffs_lzma__decoder__transform_io(&self->private_data.f_lzma, a_dst, a_src, a_workbuf);
-            v_status = t_5;
-            if (a_dst) {
-              iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
-            }
-            if (a_src) {
-              iop_a_src = a_src->data.ptr + a_src->meta.ri;
+              io0_a_dst = o_0_io0_a_dst;
+              io1_a_dst = o_0_io1_a_dst;
             }
           }
           wuffs_xz__decoder__apply_non_final_filters(self, wuffs_private_impl__io__since(v_dmark, ((uint64_t)(iop_a_dst - io0_a_dst)), io0_a_dst));
diff --git a/std/xz/decode_xz.wuffs b/std/xz/decode_xz.wuffs
index 50aea4a..f4b1b8e 100644
--- a/std/xz/decode_xz.wuffs
+++ b/std/xz/decode_xz.wuffs
@@ -179,7 +179,12 @@
             if this.num_non_final_filters == 0 {
                 status =? this.lzma.transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
             } else {
-                status =? this.lzma.transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
+                // The non-final filters will modify the args.dst contents so
+                // that its history isn't what this.lzma considers its
+                // (unfiltered) history.
+                io_forget_history (io: args.dst) {
+                    status =? this.lzma.transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
+                }
                 this.apply_non_final_filters!(dst_slice: args.dst.since(mark: dmark))
             }
             compressed_size ~mod+= args.src.count_since(mark: smark)
diff --git a/test/c/std/xz.c b/test/c/std/xz.c
index e5d96a1..bbe67a8 100644
--- a/test/c/std/xz.c
+++ b/test/c/std/xz.c
@@ -131,6 +131,39 @@
 }
 
 const char*  //
+wuffs_xz_decode_with_history(wuffs_base__io_buffer* dst,
+                             wuffs_base__io_buffer* src,
+                             uint32_t wuffs_initialize_flags,
+                             uint64_t wlimit,
+                             uint64_t rlimit) {
+  if (wlimit != UINT64_MAX) {
+    return "wuffs_xz_decode_with_history assumes wlimit == UINT64_MAX";
+  }
+
+  wuffs_xz__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_xz__decoder__initialize(&dec, sizeof dec, WUFFS_VERSION,
+                                             wuffs_initialize_flags));
+
+  while (true) {
+    wuffs_base__io_buffer limited_src = make_limited_reader(*src, rlimit);
+
+    // Compared to wuffs_xz_decode, wuffs_xz_decode_with_history passes dst
+    // directly, not limited_dst (which has no history).
+    wuffs_base__status status = wuffs_xz__decoder__transform_io(
+        &dec, dst, &limited_src, g_work_slice_u8);
+
+    src->meta.ri += limited_src.meta.ri;
+
+    if (((rlimit < UINT64_MAX) &&
+         (status.repr == wuffs_base__suspension__short_read))) {
+      continue;
+    }
+    return status.repr;
+  }
+}
+
+const char*  //
 test_wuffs_xz_decode_enwik5() {
   CHECK_FOCUS(__func__);
   return do_test_io_buffers(wuffs_xz_decode, &g_xz_enwik5_gt, UINT64_MAX,
@@ -138,13 +171,20 @@
 }
 
 const char*  //
-test_wuffs_xz_decode_one_byte_reads() {
+test_wuffs_xz_decode_one_byte_reads_sans_history() {
   CHECK_FOCUS(__func__);
   return do_test_io_buffers(wuffs_xz_decode, &g_xz_romeo_delta1_gt, UINT64_MAX,
                             1);
 }
 
 const char*  //
+test_wuffs_xz_decode_one_byte_reads_with_history() {
+  CHECK_FOCUS(__func__);
+  return do_test_io_buffers(wuffs_xz_decode_with_history, &g_xz_romeo_delta1_gt,
+                            UINT64_MAX, 1);
+}
+
+const char*  //
 test_wuffs_xz_decode_romeo() {
   CHECK_FOCUS(__func__);
   return do_test_io_buffers(wuffs_xz_decode, &g_xz_romeo_gt, UINT64_MAX,
@@ -201,7 +241,8 @@
 
     test_wuffs_xz_decode_enwik5,
     test_wuffs_xz_decode_interface,
-    test_wuffs_xz_decode_one_byte_reads,
+    test_wuffs_xz_decode_one_byte_reads_sans_history,
+    test_wuffs_xz_decode_one_byte_reads_with_history,
     test_wuffs_xz_decode_romeo,
 
 #ifdef WUFFS_MIMIC