Skip to content

optimise binary operands with evaluate()#1427

Merged
rvanvelzen merged 1 commit intomishoo:masterfrom
alexlamsl:binary
Jan 26, 2017
Merged

optimise binary operands with evaluate()#1427
rvanvelzen merged 1 commit intomishoo:masterfrom
alexlamsl:binary

Conversation

@alexlamsl
Copy link
Copy Markdown
Collaborator

This one evolves out of #1425 (comment) - adding [WIP] in title so @kzc and others can throw corner cases at this and see what sticks 😉

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Jan 17, 2017

Regarding the changes to the functionality of AST_Node.is_constant(). Is this because the optimizations are done elsewhere? Is the compress option evaluate=false no longer respected?

The AST_Node method is_constant was renamed to is_node_constant but were all occurrences of its use replaced? I still see is_constant being called elsewhere. Not sure how the tests passed - were there two methods of the same name?

If you're changing AST_Node.is_constant() could you please add if (this instanceof AST_RegExp) return false; as the first line of the method? It's not really a constant and can be problematic in corner cases. If this change is made then instanceof AST_RegExp can be removed from collapse_single_use_vars().

Not sure that the hoisting of evaluate for ll and rr to the top of OPT(AST_Binary) is correct. It does not appear to take into account the side effects of the reverse() function which conditionally swaps left and right.

Be aware that the tests do not cover all the code paths, so any change to these optimizations could result in potential failures if not careful. You'll be the new authority on compress and will likely have to support it after release.

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

@kzc there should be no logic changes to AST_Node.is_constant(). The change is to introduce a new AST_Node.is_node_constant() which checks if a given node is "constant" without invoking any optimisations, i.e. evaluate(). So all existing usages of is_constant() should be left untouched.

(I'll think about the other points in your comment and get back to you in a moment.)

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

@kzc actually, would you mind sharing the cases where RegExp and is_constant() is in conflict?

The two places is_constant() is being used are collapse_vars and y ? x : x => x

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Jan 17, 2017

It's just code cleanup regarding RegExp. Moving the regexp check into is_constant is the correct thing to do as it is mutable and there's also instance identity (===) implications with its substitution:

        (function(){
            var result, rx = /ab*/g;
            while (result = rx.exec('acdabcdeabbb'))
                console.log(result[0]);
        })();

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Jan 17, 2017

there should be no logic changes to AST_Node.is_constant().

It appears to be calling evaluate() indirectly in this PR which has implications for respecting the compress option evaluate=false.

+        AST_Node.DEFMETHOD("is_constant", function(compressor){
+            return this.is_node_constant() || this.evaluate(compressor).length > 1;
          });

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

It appears to be calling evaluate() indirectly in this PR which has implications for respecting the compress option evaluate=false .

Isn't it already calling evaluate() in master?

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Jan 17, 2017

Isn't it already calling evaluate() in master?

My bad. You are correct.

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

@kzc started afresh with a commit that should address the concerns mentioned in your first comment.

is_constant() now rejects AST_RegExp and no longer calls evaluate() internally, which in the worst case would prohibit some cases from optimisation.

I realise that making extensive changes to compress.js is going to flag me up for the next bunch of bug reports for upcoming releases. Meanwhile, I hope this new version is less intrusive and easier to review.

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Jan 17, 2017

I think your latest changes are safer. Sorry for the misinterpretation of past compress behavior.

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

Apologies in advance for being redundant - calling evaluate() when evaluate=false is essentially a noop

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Jan 17, 2017

calling evaluate() when evaluate=false is essentially a noop

Good to know.

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

@kzc thanks for the review as always 👍

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Jan 17, 2017

No problem. You have as good or better understanding of the code base than I do.

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Jan 17, 2017

I realise that making extensive changes to compress.js is going to flag me up for the next bunch of bug reports for upcoming releases.

@alexlamsl The code base could indeed use a clean up which is what you were doing - and that's a good thing from both a long term maintenance and code efficiency point of view. Because Uglify is used by a lot of projects I personally try to err on the side of caution and make fairly conservative changes. But if you're willing to support the code base after making sweeping improvements, please go ahead. I just don't have as much time to commit to this project going forward.

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

@kzc thanks for the kind words. I also think we should err on the conservative side and make incremental changes. So if/when regressions pop up they would be easier to track down.

Please don't hesitate to point out potential areas of improvement as I'm not familiar with the development history of this code base.

lib/compress.js Outdated
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, ll[0]);
}
if (compressor.option("conditionals")) switch (self.operator) {
Copy link
Copy Markdown
Contributor

@kzc kzc Jan 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally wrote that condition if (compressor.option("conditionals")) and it's incorrect. I later learned that the term "conditional" in a javascript context means "ternary operator" which has nothing to do with short-circuiting conditions. The "booleans" option is not appropriate either because it deals specifically with the true/false -> !0/!1 transform. I suggest to simply remove if (compressor.option("conditionals")) altogether and unconditionally run the body. Might impact some tests though.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kzc will investigate 👌

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If too many test lines are changed, don't worry about it. "conditionals" is enabled by default and nobody noticed this issue.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kzc looks pretty straightforward - I've separated the tests out to a new file (and removed conditionals flag in those tests) for clarity.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works. Thanks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It being governed solely by the compress option "evaluate" makes more sense.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kzc sure, though my "no_evaluate" tests need revision then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexlamsl It's your call - leave as you now have it or put it under "evaluate".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kzc evaluate as requested - I wouldn't let a few tests stop us from doing The Right Thing 😎

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the spirit! Thanks again.

- remove call to evaluate() in is_constant() and let nested optimize() does its job instead
- reject RegExp in is_constant() and remove special case logic under collapse_vars
- operands to conditionals optimisation are now always evaluate()-ed
- throw error in constant_value() instead of returning undefined to catch possible bugs, similar to make_node_from_constant()
- optimise binary boolean operators under `evaluate` instead of `conditionals`
@alexlamsl alexlamsl changed the title [WIP] optimise binary operands for possible constants under evaluate() optimise binary operands with evaluate() Jan 19, 2017
@rvanvelzen
Copy link
Copy Markdown
Collaborator

Looking good once again.

@rvanvelzen rvanvelzen merged commit 0610c02 into mishoo:master Jan 26, 2017
@alexlamsl alexlamsl deleted the binary branch January 26, 2017 11:35
@alexlamsl alexlamsl mentioned this pull request Mar 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants