Skip to content

Simplify signed reminders which compared with zero #3153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Sep 30, 2020

Conversation

MaxGraey
Copy link
Contributor

@MaxGraey MaxGraey commented Sep 21, 2020

eqz((signed)x % C_pot) -> eqz(x & (C_pot - 1))
(signed)x % C_pot == 0 -> x & (C_pot - 1) == 0
(signed)x % C_pot != 0 -> x & (C_pot - 1) != 0

@MaxGraey
Copy link
Contributor Author

Fuzzed:

Invocations so far:
   FuzzExec: 19686
   CompareVMs: 5406
   CheckDeterminism: 1776
   Wasm2JS: 4604
   Asyncify: 4940

ITERATION: 23155

Copy link
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

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

Why do you need the eqz version of this pattern? It looks like it doesn't do anything that the binary version doesn't already do.

@@ -570,6 +570,22 @@ struct OptimizeInstructions
return ret;
}
} else if (auto* unary = curr->dynCast<Unary>()) {
if (unary->op == EqZInt32 || unary->op == EqZInt64) {
Copy link
Member

Choose a reason for hiding this comment

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

Could you move this up to the top of this function with the other matcher-based transformations? The more we can avoid putting new logic in this highly-nested part of the function, the better. It would also be good to start the match with the top-level expression, even though we already know it is a unary expression. Moving this will also let you use the binary matcher without the Match:: prefix.

Copy link
Contributor Author

@MaxGraey MaxGraey Sep 25, 2020

Choose a reason for hiding this comment

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

I decide use slightly different approach due to input wasm could contain mixed eqz and x == 0 operations and due to x == 0 --> eqz x transform apply in early stage exactly after canonicalization I guess better just remove x == 0 matching and always check only x != 0. So x == 0 --> eqz x rule may be make sense even move to canonicalization routine.

Copy link
Member

Choose a reason for hiding this comment

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

I still think it would be good to move these up to where the other matcher-based transformations are. It's ok if you have to move other code as well to make it work out. I would also be willing to do a non-functional change to port most of this code to use matchers first if that would make it easier for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

using namespace Match;
Const* c;
Binary* inner;
Expression* left;
Copy link
Member

Choose a reason for hiding this comment

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

It looks like left is only used to get the child type of the binary expression. You should be able to save a variable by using c->type for that instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

// eqz((signed)x % C_pot) ==> eqz(x & (C_pot - 1))
if (matches(unary->value,
Match::binary(
&inner, Abstract::RemS, any(&left), constant(&c)))) {
Copy link
Member

Choose a reason for hiding this comment

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

You could use ival instead of constant to make this more compact.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It will be impossible after this I guess

@MaxGraey
Copy link
Contributor Author

MaxGraey commented Sep 25, 2020

Why do you need the eqz version of this pattern? It looks like it doesn't do anything that the binary version doesn't already do

Initially it was without eqz version but it seems it necessary. In practice I got mostly of it instead expr == 0

@MaxGraey MaxGraey requested a review from tlively September 26, 2020 12:45
@MaxGraey
Copy link
Contributor Author

refuzzed

@MaxGraey
Copy link
Contributor Author

MaxGraey commented Sep 29, 2020

same issue with CI as yesterday

Copy link
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

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

Looks good with these final changes applied!

if (IsPowerOf2((uint64_t)c->value.getInteger())) {
inner->op = Abstract::getBinary(c->type, Abstract::And);
c->value = c->value.sub(Literal::makeFromInt32(1, c->type));
return eqz;
Copy link
Member

Choose a reason for hiding this comment

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

This can just be return curr; and the eqz variable can be removed. Also you can use ival instead of constant and rename inner to bin to make the pattern more compact.

// (signed)x % C_pot != 0 ==> x & (C_pot - 1) != 0
if (matches(curr,
binary(Abstract::Ne,
binary(&inner, Abstract::RemS, any(&left), constant(&c)),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
binary(&inner, Abstract::RemS, any(&left), constant(&c)),
binary(&inner, Abstract::RemS, any(&left), ival()),

You actually don't need c here because it is already captured in right.

This comment was marked as outdated.

Copy link
Contributor Author

@MaxGraey MaxGraey Sep 29, 2020

Choose a reason for hiding this comment

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

Copy link
Contributor Author

@MaxGraey MaxGraey Sep 29, 2020

Choose a reason for hiding this comment

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

Yes, right and c are different constants. right is outer constant and c is inner for this expression:

(signed)x % c != 0   ==>  x & (c - 1) != 0

where 0 is right.

So I reverted changes)

Copy link
Member

Choose a reason for hiding this comment

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

Oh right, my bad!

if (matches(
curr,
binary(Abstract::Ne,
binary(&inner, Abstract::RemS, any(&left), constant(&c)),
Copy link
Member

Choose a reason for hiding this comment

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

constant can still be renamed ival here for compactness. In general, other than removing c, I think all the comments still apply.

Copy link
Contributor Author

@MaxGraey MaxGraey Sep 29, 2020

Choose a reason for hiding this comment

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

But I should modify this constant's value as:

c->value = c->value.sub(Literal::makeFromInt32(1, left->type));

How I can do this without access to Const* expression?

Copy link
Member

Choose a reason for hiding this comment

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

ival(&c) should work fine. constant is for when the constant can be either an integer or a floating point constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just wondering how about this approach:

      int64_t c;
      Binary* inner;
      if (matches(
            curr,
            binary(Abstract::Ne,
                   binary(&inner, Abstract::RemS, any(&left), ival(&c)),
                   ival(0)))) {
        if (IsPowerOf2((uint64_t)c)) {
          if (left->type == Type::i64) {
            optimizePowerOf2URem(inner, (uint64_t)c);
          } else {
            optimizePowerOf2URem(inner, (uint32_t)c);
          }
          return curr;
        }
      }

Is it make sense?

Copy link
Contributor Author

@MaxGraey MaxGraey Sep 29, 2020

Choose a reason for hiding this comment

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

constant is for when the constant can be either an integer or a floating point constant.

I see. Thanks!
I refactored

Copy link
Contributor Author

@MaxGraey MaxGraey Sep 30, 2020

Choose a reason for hiding this comment

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

What about rename constant to val or xval / cval?

i32()
i64()
f32()
f64()
ival()  ;; i32() | i64()
fval()  ;; f32() | f64()
val()   ;; ival() | fval()

@MaxGraey
Copy link
Contributor Author

Fast refuzzing stats:

Invocations so far:
   FuzzExec: 5334
   CompareVMs: 1427
   CheckDeterminism: 447
   Wasm2JS: 1224
   Asyncify: 1323

ITERATION: 6259

@MaxGraey MaxGraey requested a review from tlively September 30, 2020 01:10
Copy link
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

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

Thanks, this looks great!

@tlively tlively merged commit 7e99538 into WebAssembly:master Sep 30, 2020
@MaxGraey MaxGraey deleted the opt-rem_s-w-cmp-zero branch September 30, 2020 08:20
if (matches(curr,
unary(Abstract::EqZ,
binary(&inner, Abstract::RemS, any(), ival(&c)))) &&
IsPowerOf2((uint64_t)c->value.getInteger())) {
Copy link
Member

@kripken kripken Oct 2, 2020

Choose a reason for hiding this comment

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

why is the cast to uint64_t needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IsPowerOf2 allow only unsigned types but getInteger() return int64_t

Copy link
Contributor Author

@MaxGraey MaxGraey Oct 2, 2020

Choose a reason for hiding this comment

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

Btw negative dividers like x % -4 == 0 currently rejected and not optimized which is ok but it seems it could be handle as (x & 3) == 0 as well.

Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it work without the cast, then? The function is

template<typename T> bool isPowerOf2(T v) {
  return v != 0 && (v & (v - 1)) == 0;
}

which just cares about bits anyhow. There should be no compiler warning or error here? Maybe I'm not sure what you mean by "allow only".

Copy link
Contributor Author

@MaxGraey MaxGraey Oct 2, 2020

Choose a reason for hiding this comment

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

I'm not sure signed type does make sense for isPowerOf2 due to negative values always return false. But you're right casting to uint64_t here unnecessary.

Copy link
Member

@kripken kripken Oct 2, 2020

Choose a reason for hiding this comment

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

Logically, I agree, it's odd to ask about a negative number. But we just have bits here, really. A Literal just stores the bits, without a sign (just like wasm and LLVM IR, etc.).

Copy link
Contributor Author

@MaxGraey MaxGraey Oct 2, 2020

Choose a reason for hiding this comment

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

I'll create PR which handle also negative dividers: x % -C_pot == 0 -> (x & (abs(C_pot) - 1)) == 0 and remove this cast as well

tlively pushed a commit that referenced this pull request Oct 5, 2020
Implement a more general (additional) version of #3153 which also handles negative constant divisors:
`(int32)x % -4 == 0` --> `(x & 3) == 0`
`x % -C_pot == 0` --> `(x & (abs(C_pot) - 1)) == 0`

and special two-complement values as well:
`(int32)x % 0x80000000 == 0` --> `(x & 0x7fffffff) == 0`
`(int64)x % 0x8000000000000000 == 0` --> `(x & 0x7fffffffffffffff) == 0`
as separete rules:
`(int32)x % 0x80000000` --> `x & 0x7fffffff`
`(int64)x % 0x8000000000000000` --> `x & 0x7fffffffffffffff`

The [previous pr](#3153) didn't use these possibilities.
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