| // Copyright 2017 The Wuffs Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package check |
| |
| // TODO: should bounds checking even be responsible for selecting between |
| // optimized implementations of e.g. read_u8? Instead, the Wuffs code could |
| // explicitly call either read_u8 or read_u8_fast, with the latter having |
| // stronger preconditions. |
| // |
| // Doing so might need some syntactical distinction (not just a question mark) |
| // between "foo?" and "bar?" if one of those methods can still return an error |
| // code but never actually suspend. |
| |
| import ( |
| "math/big" |
| |
| a "github.com/google/wuffs/lang/ast" |
| t "github.com/google/wuffs/lang/token" |
| ) |
| |
| // splitReceiverMethodArgs returns the "receiver", "method" and "args" in the |
| // expression "receiver.method(args)". |
| func splitReceiverMethodArgs(n *a.Expr) (receiver *a.Expr, method t.ID, args []*a.Node) { |
| if n.Operator() != a.ExprOperatorCall { |
| return nil, 0, nil |
| } |
| args = n.Args() |
| n = n.LHS().AsExpr() |
| if n.Operator() != a.ExprOperatorSelector { |
| return nil, 0, nil |
| } |
| return n.LHS().AsExpr(), n.Ident(), args |
| } |
| |
| 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) |
| } |
| |
| // Check if receiver looks like "a[i .. j]" where i and j are constants and |
| // ((j - i) >= advance). |
| if _, i, j, ok := receiver.IsSlice(); ok { |
| icv := (*big.Int)(nil) |
| if i == nil { |
| icv = zero |
| } else if i.ConstValue() != nil { |
| icv = i.ConstValue() |
| } |
| |
| jcv := (*big.Int)(nil) |
| if (j != nil) && (j.ConstValue() != nil) { |
| jcv = j.ConstValue() |
| } |
| |
| if (icv != nil) && (jcv != nil) { |
| n := big.NewInt(0).Sub(jcv, icv) |
| if n.Cmp(advance) >= 0 { |
| retOK = true |
| } |
| } |
| } |
| |
| retErr = q.facts.update(func(x *a.Expr) (*a.Expr, error) { |
| // TODO: update (discard?) any facts that merely mention |
| // receiver.length(), even if they aren't an exact match. |
| |
| op := x.Operator() |
| if (op != t.IDXBinaryGreaterEq) && |
| (op != t.IDXBinaryGreaterThan) && |
| (op != t.IDXBinaryEqEq) { |
| return x, nil |
| } |
| |
| rcv := x.RHS().AsExpr().ConstValue() |
| if rcv == nil { |
| return x, nil |
| } |
| |
| // Check that lhs is "receiver.length()". |
| lhs := x.LHS().AsExpr() |
| if (lhs.Operator() != a.ExprOperatorCall) || (len(lhs.Args()) != 0) { |
| return x, nil |
| } |
| lhs = lhs.LHS().AsExpr() |
| if (lhs.Operator() != a.ExprOperatorSelector) || (lhs.Ident() != t.IDLength) { |
| return x, nil |
| } |
| lhs = lhs.LHS().AsExpr() |
| if !lhs.Eq(receiver) { |
| return x, nil |
| } |
| |
| // Check if the bytes available is >= the bytes needed. If so, update |
| // rcv to be the bytes remaining. If not, discard the fact x. |
| if op == t.IDXBinaryGreaterThan { |
| op = t.IDXBinaryGreaterEq |
| rcv = big.NewInt(0).Add(rcv, one) |
| } |
| if rcv.Cmp(advance) < 0 { |
| if !update { |
| return x, nil |
| } |
| return nil, nil |
| } |
| |
| retOK = true |
| |
| if !update { |
| return x, nil |
| } |
| |
| if rcv.Cmp(advance) == 0 { |
| // TODO: delete the (adjusted) fact, as newRCV will be zero, and |
| // "foo.length() >= 0" is redundant. |
| } |
| |
| // Create a new a.Expr to hold the adjusted RHS constant value, newRCV. |
| newRCV := big.NewInt(0).Sub(rcv, advance) |
| o, err := makeConstValueExpr(q.tm, newRCV) |
| if err != nil { |
| return nil, err |
| } |
| |
| return a.NewExpr(x.AsNode().AsRaw().Flags(), |
| t.IDXBinaryGreaterEq, 0, x.LHS(), nil, o.AsNode(), nil), nil |
| }) |
| 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.length(), 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.length()". |
| lhs := x.LHS().AsExpr() |
| if (lhs.Operator() != a.ExprOperatorCall) || (len(lhs.Args()) != 0) { |
| return x, nil |
| } |
| lhs = lhs.LHS().AsExpr() |
| if (lhs.Operator() != a.ExprOperatorSelector) || (lhs.Ident() != t.IDLength) { |
| 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 |
| } |