Let skip_u32_fast take non-constant arguments
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index e70125c..50434f6 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -396,9 +396,8 @@
 	"io_reader.skip_u32?(n: u32)",
 
 	// TODO: this should have explicit pre-conditions "actual <= worst_case"
-	// and "worst_case <= available()". As an implementation restriction, we
-	// also require that worst_case has a constant value. For now, that's all
-	// implicitly checked (i.e. hard coded).
+	// and "worst_case <= available()". For now, that's all implicitly checked
+	// (i.e. hard coded).
 	"io_reader.skip_u32_fast!(actual: u32, worst_case: u32)",
 
 	// ---- io_writer
diff --git a/lang/check/bounds.go b/lang/check/bounds.go
index 776b89f..4a1e8a4 100644
--- a/lang/check/bounds.go
+++ b/lang/check/bounds.go
@@ -1023,7 +1023,7 @@
 		}
 
 	} else if recvTyp.IsIOTokenType() {
-		advance, update := (*big.Int)(nil), false
+		advance, advanceExpr, update := (*big.Int)(nil), (*a.Expr)(nil), false
 
 		if method == t.IDUndoByte {
 			if err := q.canUndoByte(recv); err != nil {
@@ -1050,10 +1050,11 @@
 			} else if err != nil {
 				return bounds{}, err
 			}
-			if worstCase.ConstValue() == nil {
-				return bounds{}, fmt.Errorf("check: skip_fast worst_case is not a constant value")
+			if cv := worstCase.ConstValue(); cv != nil {
+				advance, update = cv, true
+			} else {
+				advanceExpr, update = actual, true
 			}
-			advance, update = worstCase.ConstValue(), true
 
 		} else if method == t.IDPeekU64LEAt {
 			args := n.Args()
@@ -1074,12 +1075,18 @@
 			}
 		}
 
-		if advance != nil {
-			if ok, err := q.optimizeIOMethodAdvance(recv, advance, update); err != nil {
+		if (advance != nil) || (advanceExpr != nil) {
+			if ok, err := q.optimizeIOMethodAdvance(recv, advance, advanceExpr, update); err != nil {
 				return bounds{}, err
 			} else if !ok {
-				return bounds{}, fmt.Errorf("check: could not prove %s pre-condition: %s.available() >= %v",
-					method.Str(q.tm), recv.Str(q.tm), advance)
+				adv := ""
+				if advance != nil {
+					adv = advance.String()
+				} else {
+					adv = advanceExpr.Str(q.tm)
+				}
+				return bounds{}, fmt.Errorf("check: could not prove %s pre-condition: %s.available() >= %s",
+					method.Str(q.tm), recv.Str(q.tm), adv)
 			}
 			// TODO: drop other recv-related facts?
 		}
diff --git a/lang/check/optimize.go b/lang/check/optimize.go
index 7cd56df..4cdb968 100644
--- a/lang/check/optimize.go
+++ b/lang/check/optimize.go
@@ -44,11 +44,15 @@
 	return n.LHS().AsExpr(), n.Ident(), args
 }
 
-func (q *checker) optimizeIOMethodAdvance(receiver *a.Expr, advance *big.Int, update bool) (retOK bool, retErr error) {
+func (q *checker) optimizeIOMethodAdvance(receiver *a.Expr, advance *big.Int, advanceExpr *a.Expr, update bool) (retOK bool, retErr error) {
 	// TODO: do two passes? The first one to non-destructively check retOK. The
 	// second one to drop facts if indeed retOK? Otherwise, an advance
 	// precondition failure will lose some of the facts in its error message.
 
+	if advanceExpr != nil {
+		return q.optimizeIOMethodAdvanceExpr(receiver, advanceExpr, update)
+	}
+
 	retErr = q.facts.update(func(x *a.Expr) (*a.Expr, error) {
 		// TODO: update (discard?) any facts that merely mention
 		// receiver.available(), even if they aren't an exact match.
@@ -98,7 +102,7 @@
 
 		if rcv.Cmp(advance) == 0 {
 			// TODO: delete the (adjusted) fact, as newRCV will be zero, and
-			// "foo.advance() >= 0" is redundant.
+			// "foo.available() >= 0" is redundant.
 		}
 
 		// Create a new a.Expr to hold the adjusted RHS constant value, newRCV.
@@ -113,3 +117,45 @@
 	})
 	return retOK, retErr
 }
+
+func (q *checker) optimizeIOMethodAdvanceExpr(receiver *a.Expr, advanceExpr *a.Expr, update bool) (retOK bool, retErr error) {
+	retErr = q.facts.update(func(x *a.Expr) (*a.Expr, error) {
+		// TODO: update (discard?) any facts that merely mention
+		// receiver.available(), even if they aren't an exact match.
+
+		op := x.Operator()
+		if op != t.IDXBinaryGreaterEq && op != t.IDXBinaryGreaterThan {
+			return x, nil
+		}
+
+		// Check that lhs is "receiver.available()".
+		lhs := x.LHS().AsExpr()
+		if lhs.Operator() != t.IDOpenParen || len(lhs.Args()) != 0 {
+			return x, nil
+		}
+		lhs = lhs.LHS().AsExpr()
+		if lhs.Operator() != t.IDDot || lhs.Ident() != t.IDAvailable {
+			return x, nil
+		}
+		lhs = lhs.LHS().AsExpr()
+		if !lhs.Eq(receiver) {
+			return x, nil
+		}
+
+		// Check that rhs is "advanceExpr as base.u64".
+		rhs := x.RHS().AsExpr()
+		if rhs.Operator() != t.IDXBinaryAs ||
+			!rhs.LHS().AsExpr().Eq(advanceExpr) ||
+			!rhs.RHS().AsTypeExpr().Eq(typeExprU64) {
+			return x, nil
+		}
+
+		retOK = true
+
+		if !update {
+			return x, nil
+		}
+		return nil, nil
+	})
+	return retOK, retErr
+}
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 26e6e0b..41a0196 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -5618,7 +5618,6 @@
       uint32_t v_depth;
       uint32_t v_token_length;
       uint8_t v_c;
-      uint64_t scratch;
     } s_decode_tokens[1];
   } private_data;
 
@@ -16436,15 +16435,7 @@
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
             goto label__outer__continue;
           }
-          self->private_data.s_decode_tokens[0].scratch = v_token_length;
-          WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
-          if (self->private_data.s_decode_tokens[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
-            self->private_data.s_decode_tokens[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
-            iop_a_src = io2_a_src;
-            status = wuffs_base__make_status(wuffs_base__suspension__short_read);
-            goto suspend;
-          }
-          iop_a_src += self->private_data.s_decode_tokens[0].scratch;
+          (iop_a_src += v_token_length, wuffs_base__make_empty_struct());
         } else {
           (iop_a_src += 1, wuffs_base__make_empty_struct());
         }
diff --git a/std/cbor/decode_cbor.wuffs b/std/cbor/decode_cbor.wuffs
index 7ec6084..90d89b8 100644
--- a/std/cbor/decode_cbor.wuffs
+++ b/std/cbor/decode_cbor.wuffs
@@ -62,7 +62,7 @@
 				yield? base."$short read"
 				continue.outer
 			}
-			args.src.skip_u32?(n: token_length)
+			args.src.skip_u32_fast!(actual: token_length, worst_case: token_length)
 		} else {
 			args.src.skip_u32_fast!(actual: 1, worst_case: 1)
 		}