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)
}