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

select not optimized when condition is true and ifFalse arm isn't pure #6930

Open
bdwjn opened this issue Sep 11, 2024 · 3 comments
Open

select not optimized when condition is true and ifFalse arm isn't pure #6930

bdwjn opened this issue Sep 11, 2024 · 3 comments

Comments

@bdwjn
Copy link

bdwjn commented Sep 11, 2024

Consider the following expression:

(select
	(i32.div_u (i32.const 3) (i32.const 0))
	(i32.const 2)
	(i32.const 0)
)

Binaryen will try to eliminate the select statement when its condition is constant.

The div_u might cause a trap though, so under safe optimizations (without -tnh or -iit) it needs to be preserved.

Binaryen outputs both arms, and drops the value we don't need:

(drop (i32.div_u (i32.const 3) (i32.const 0)))
(i32.const 2)

Now let's look at the opposite case:

(select
	(i32.const 2)
	(i32.div_u (i32.const 3) (i32.const 0))
	(i32.const 1)
)

Surely it will use the same trick, right? Output both arms, drop the one you don't need:

(i32.const 2)
(drop (i32.div_u (i32.const 3) (i32.const 0)) )

Except it doesn't! For some reason it completely gives up here, and keeps the select and its condition as-is.

The code mentions something about needing to "reverse the order using a temp local" but that makes no sense to me.

@kripken
Copy link
Member

kripken commented Sep 11, 2024

This could be improved, yeah, looks like a current limitation.

I think what that linked code is referring to is the ordering issue when the first arm has effects, e.g.

    (select
	    (call $before)
            (call $later)
	    (i32.const 1)
    )

Here we want to return the call to before, since the condition is true. But later must be called after. So we need to do something like this:

(local.set $temp (call $before))
(drop (call $later))
(local.get $temp)

That may be faster (if VMs do not optimize it, which I am not certain of), but it is around the same size in the binary format.

@bdwjn
Copy link
Author

bdwjn commented Sep 11, 2024

I still don't get why you need that local in your example. When you flatten the select it becomes:

(call $before) ;; stack is [T]
(call $later)  ;; stack is [F] -> T
;; At this point we have 2 values (of the same type) on the stack, validation guarantees this.
;; We also ran both sides in the correct order, so any side effects have happened the way
;; they should. All that's left to do is get the correct value and drop the other one:
(i32.const 1)  ;; stack is [1] -> F -> T
(select)       ;; stack is [T]

So that (i32.const 1) (select) can be replaced with a (drop) to get the True value, and it won't change the side effects on the code above it.

@kripken
Copy link
Member

kripken commented Sep 11, 2024

Yes, in the wasm binary format you can do "stacky" things like that, but not in Binaryen IR, which is more structured. But, good point, since in the time after that comment was written we have added stacky optimizations (StackIR), which can do this. So perhaps it is worth doing now.

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

No branches or pull requests

2 participants