Skip to content
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

Consider optimizing x < 0 || x > positiveInt to single unsigned comparison #56083

Closed
osa1 opened this issue Jun 26, 2024 · 0 comments
Closed
Assignees
Labels
area-dart2wasm Issues for the dart2wasm compiler.

Comments

@osa1
Copy link
Member

osa1 commented Jun 26, 2024

The expression x < 0 || x > positiveInt can be compiled as a single unsigned comparison x.gtU(positiveInt).

Currently wasm-opt doesn't do this for us and it seems like it won't do it any time soon: WebAssembly/binaryen#6685

In a few places in the standard library we do it manually by using the WasmI64 type:

if (WasmI64.fromInt(length).leU(WasmI64.fromInt(index))) {

However there are many more places where we can do this optimization:

lib/regexp_helper.dart:139:    if (start < 0 || start > string.length) {
lib/regexp_helper.dart:164:    if (start < 0 || start > string.length) {
lib/regexp_helper.dart:189:    if (index < 0 || index >= _match.length.toDartInt) {
lib/convert_patch.dart:1224:            if (digit < 0 || digit > 5) {
lib/convert_patch.dart:1809:        if (byte < 0 || byte > 255) {
lib/simd.dart:512:    if ((mask < 0) || (mask > 255)) {
lib/simd.dart:528:    if ((mask < 0) || (mask > 255)) {
lib/simd.dart:775:    if ((mask < 0) || (mask > 255)) {
lib/simd.dart:790:    if ((mask < 0) || (mask > 255)) {
lib/list.dart:94:    if (index < 0 || index > this.length) {
lib/list.dart:320:    if ((index < 0) || (index > length)) {
lib/list.dart:370:    if (index < 0 || index > length) {
lib/typed_data.dart:39:  if (length < 0 || length > _maxWasmArrayLength) {
lib/boxed_double.dart:351:    if (fractionDigits < 0 || fractionDigits > 20) {
lib/boxed_double.dart:390:      if (fractionDigits < 0 || fractionDigits > 20) {
lib/boxed_int.dart:302:    if (b < 0 || b > m) {
lib/boxed_int.dart:393:    if ((t < 0) || (t >= m)) t %= m;
lib/string.dart:223:    if (bits < 0 || bits > 0xFFFFF) {
lib/string.dart:279:    if (bits < 0 || bits > 0xFFFFF) {
lib/string.dart:381:    if ((start < 0) || (start + len > this.length)) {
lib/string.dart:397:    if ((index < 0) || (index > this.length)) {
lib/string.dart:407:    if ((start < 0) || (start > this.length)) {
lib/string.dart:432:    } else if (start < 0 || start > this.length) {
lib/string.dart:626:      if (startIndex < 0 || startIndex > this.length) {
lib/string.dart:1013:    if (start < 0 || start > string.length) {
lib/string.dart:1020:    if (start < 0 || start > string.length) {

It may worth doing this in the code generator instead of relying on wasm-opt.

Note that some of the cases cannot be optimized by wasm-opt. For example it may not possible for wasm-opt to infer that string.length cannot be negative. In those cases we have no option but to call the intrinsics manually.

@osa1 osa1 added the area-dart2wasm Issues for the dart2wasm compiler. label Jun 26, 2024
@osa1 osa1 self-assigned this Jun 28, 2024
copybara-service bot pushed a commit that referenced this issue Jul 2, 2024
…possible"

This reverts commit 8b1aa18.

Reason for revert:

This caused at least two issues:

(1) wasm-opt doesn't inline the simple helpers introduced in this CL,
for example:

```
  (func $IntToWasmInt|geU (;217;) (param $var0 i64) (param $var1 i64) (result i32)
    local.get $var0
    local.get $var1
    i64.ge_u
  )
```

Even though when inlined this becomes just one instruction.

This causes diffs like:

```
               i64.const 1
               i64.add
               local.set $var1
+              local.get $var2
               local.get $var10
               struct.get $JSArrayBufferImpl_80 $field2
               call $wasm:js-string.length (import)
               i64.extend_i32_u
               local.tee $var3
-              local.get $var2
-              i64.le_u
+              call $IntToWasmInt|geU
               if
```

(2) Changing `length.leU(index)` to `index.geU(length)` causes missing
some optimizations like the following:

```
     local.get $var0
     ref.cast $JSArrayBufferImpl_80
     local.tee $var3
     struct.get $JSArrayBufferImpl_80 $field2
     call $wasm:js-string.length (import)
-    drop
+    i64.extend_i32_u
+    local.tee $var4
+    i64.const 0
+    i64.lt_u
+    if
+      i64.const 0
+      i64.const 0
+      local.get $var4
+      ref.null none
+      ref.null none
+      call $RangeError.range
+      call $Error._throwWithCurrentStackTrace
+      unreachable
+    end
```

Here the `i64.const 0; i64.lt_u` always produces `0`, but wasm-opt
doesn't do this optimization.

The Dart changes that caused this diff:

```
-    if (WasmI64.fromInt(length).leU(WasmI64.fromInt(index))) {
+    if (index.geU(length)) {
```

Original change's description:
> [dart2wasm] Use single unsigned cmp instead of two cmps when possible
>
> wasm-opt doesn't optimize `0 < x || x > y` when y is known to be
> positive (e.g. a positive integer constant), so we do it manually.
>
> We also do it in a few places where `y` is not known to be positive in
> the Wasm code, but we know it's always positive, for example when it's a
> length.
>
> Example improvement in the wasm-opt output:
>
> ```
>    (func $_newArrayLengthCheck (;426;) (param $var0 i64) (result i64)
>      local.get $var0
>      i64.const 2147483647
> -    i64.le_s
> -    local.get $var0
> -    i64.const 0
> -    i64.ge_s
> -    i32.and
> -    i32.eqz
> +    i64.gt_u
>      if
>        i32.const 46
>        i32.const 0
> @@ -19190,13 +19172,8 @@
>                i64.const 97
>                i64.sub
>                local.tee $var3
> -              i64.const 0
> -              i64.ge_s
> -              local.get $var3
>                i64.const 5
> -              i64.le_s
> -              i32.and
> -              i32.eqz
> +              i64.gt_u
>                if
>                  local.get $var0
>                  local.get $var6
> @@ -19810,10 +19787,10 @@
>      global.get $global4
>      array.new_fixed $Array<_Type> 2
>    )
> ```
>
> Closes #56083.
>
> Change-Id: Idb1dd0d0809b26be8aec3d082aa341c59e1a353d
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373663
> Reviewed-by: Martin Kustermann <[email protected]>
> Commit-Queue: Ömer Ağacan <[email protected]>

Change-Id: Iac5428037b0c19d76e4c841a1b6623b7305ff702
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373800
Reviewed-by: Martin Kustermann <[email protected]>
Reviewed-by: Ömer Ağacan <[email protected]>
Bot-Commit: Rubber Stamper <[email protected]>
Commit-Queue: Ömer Ağacan <[email protected]>
osa1 added a commit to osa1/sdk that referenced this issue Jul 2, 2024
…possible"

This is a reland of commit 8b1aa18

Original change's description:
> [dart2wasm] Use single unsigned cmp instead of two cmps when possible
>
> wasm-opt doesn't optimize `0 < x || x > y` when y is known to be
> positive (e.g. a positive integer constant), so we do it manually.
>
> We also do it in a few places where `y` is not known to be positive in
> the Wasm code, but we know it's always positive, for example when it's a
> length.
>
> Example improvement in the wasm-opt output:
>
> ```
>    (func $_newArrayLengthCheck (;426;) (param $var0 i64) (result i64)
>      local.get $var0
>      i64.const 2147483647
> -    i64.le_s
> -    local.get $var0
> -    i64.const 0
> -    i64.ge_s
> -    i32.and
> -    i32.eqz
> +    i64.gt_u
>      if
>        i32.const 46
>        i32.const 0
> @@ -19190,13 +19172,8 @@
>                i64.const 97
>                i64.sub
>                local.tee $var3
> -              i64.const 0
> -              i64.ge_s
> -              local.get $var3
>                i64.const 5
> -              i64.le_s
> -              i32.and
> -              i32.eqz
> +              i64.gt_u
>                if
>                  local.get $var0
>                  local.get $var6
> @@ -19810,10 +19787,10 @@
>      global.get $global4
>      array.new_fixed $Array<_Type> 2
>    )
> ```
>
> Closes dart-lang#56083.
>
> Change-Id: Idb1dd0d0809b26be8aec3d082aa341c59e1a353d
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373663
> Reviewed-by: Martin Kustermann <[email protected]>
> Commit-Queue: Ömer Ağacan <[email protected]>

Change-Id: I822ca612e5c8d5d33ba443107b72e9f1021c5c4a
copybara-service bot pushed a commit that referenced this issue Jul 2, 2024
…possible"

This is a reland of commit 8b1aa18

This relands the commit with the following changes to avoid regressions
and some random changes:

- Add inline annotations to `IntToWasmInt` extensions.

  These methods are 3 instructions long and often become just one
  instruction when inlined, but wasm-opt still doesn't inline them,
  turning a single `i64.lt_u` and similar into a function call.

- Revert changes from `length.leU(index)` to `index.geU(length)`.

  I had done this change because I find `if (index >= length) throw`
  easier to read than `if (length <= index) throw`, but the change
  introduced some larger changes in the wasm-opt output. These changes
  are probably harmless, but to minimize unintentional changes I
  reverted these changes for now.

Original change's description:
> [dart2wasm] Use single unsigned cmp instead of two cmps when possible
>
> wasm-opt doesn't optimize `0 < x || x > y` when y is known to be
> positive (e.g. a positive integer constant), so we do it manually.
>
> We also do it in a few places where `y` is not known to be positive in
> the Wasm code, but we know it's always positive, for example when it's a
> length.
>
> Example improvement in the wasm-opt output:
>
> ```
>    (func $_newArrayLengthCheck (;426;) (param $var0 i64) (result i64)
>      local.get $var0
>      i64.const 2147483647
> -    i64.le_s
> -    local.get $var0
> -    i64.const 0
> -    i64.ge_s
> -    i32.and
> -    i32.eqz
> +    i64.gt_u
>      if
>        i32.const 46
>        i32.const 0
> @@ -19190,13 +19172,8 @@
>                i64.const 97
>                i64.sub
>                local.tee $var3
> -              i64.const 0
> -              i64.ge_s
> -              local.get $var3
>                i64.const 5
> -              i64.le_s
> -              i32.and
> -              i32.eqz
> +              i64.gt_u
>                if
>                  local.get $var0
>                  local.get $var6
> @@ -19810,10 +19787,10 @@
>      global.get $global4
>      array.new_fixed $Array<_Type> 2
>    )
> ```
>
> Closes #56083.
>
> Change-Id: Idb1dd0d0809b26be8aec3d082aa341c59e1a353d
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373663
> Reviewed-by: Martin Kustermann <[email protected]>
> Commit-Queue: Ömer Ağacan <[email protected]>

Change-Id: I822ca612e5c8d5d33ba443107b72e9f1021c5c4a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/374000
Reviewed-by: Martin Kustermann <[email protected]>
Commit-Queue: Ömer Ağacan <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-dart2wasm Issues for the dart2wasm compiler.
Projects
None yet
Development

No branches or pull requests

1 participant