| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
|
|
|
| |
When looking for all values written to a field, we can ignore
values that are loaded from that same field, i.e., are copied from
something already present there. Such operations never introduce
new values.
This helps by a small but non-zero amount on j2cl.
|
|
|
|
|
| |
Use ToolOptions there, which adds --nominal support.
We must also pass --nominal to the sub-commands we run.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
typing (#4069)
(if (result i32)
(local.get $x)
(struct.get $B 1
(ref.null $B)
)
(struct.get $C 1
(ref.null $C)
)
)
With structural typing it is safe to turn this into this:
(struct.get $A 1
(if (result (ref $A))
(local.get $x)
(ref.null $B)
(ref.null $C)
)
)
Here $A is the LUB of the others. This works since $A must have
field 1 in it. But with nominal types it is possible that the LUB in fact
does not have that field, and we would not validate.
This actually seems like a more general issue that might happen with
other things, even though atm perhaps it can't. For simplicity, avoid this
pattern in both nominal and structural typing, to avoid making a
difference between them.
|
| |
|
|
|
|
|
|
| |
This adds and tests the new method. It will be used in a new pass later, where
computing shallow hashes allows it to be done in linear time.
99% of the diff is whitespace.
|
|
|
|
|
|
|
|
|
|
| |
This caused no noticeable bugs, but it could in theory in new passes - in fact
in a pass I will open later this week it did.
Also fix the order in wasm.h. That part has no effect, but it is nice to be
consistent. After this PR, everything should match the single source of
truth which is wasm-delegations-fields.h (as that is used in printing, binary
reading/writing, etc., so it has to be correct). Also Switch now matches
the ordering in Break.
|
|
|
|
|
|
|
|
|
|
|
| |
This was being set in the creation of Loads in the binary reader, but
forgotten in the SIMD logic - which ends up creating a Load with
type v128, and signed_ was uninitialized.
Very hard to test this, but I saw it "break" hash value computation
which is how I noticed this.
Also initialize the I31 sign field. Now all of them in wasm.h are
properly initialized.
|
|
|
|
|
|
|
|
| |
(#4051)
We ignore sets in unreachable code, but their values may not be
compatible with a new type we specialize a local for. That is, the
validator cares about unreachable sets, while logically we don't need
to, and this pass doesn't. Fix up such unreachable sets at the end.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
themselves (#4070)
If the only uses of a global are
if (global == 0) {
global = 1;
}
Then we do not need that global: while it has both reads and
writes, the value in the global does not cause anything observable.
It is read, but only to write to itself, and nothing else.
This happens in real-world code from j2cl quite a lot, as they have
an initialization pattern with globals, and in some cases we can
optimize away the work done in the initialization, leaving only the
globals in this pattern.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
First, move the tiny pattern of call-ref-of-ref-func from Directize
into OptimizeInstructions. This is important because Directize is
a global optimization pass - it looks at the table to see if a
CallIndirect can be turned into a direct call. We only run global
passes at the end of the pipeline, but we don't need any global
data for call-ref of a ref-func, and OptimizeInstructions is the
place for such patterns.
Second, extend that to also handle fallthrough values. This is
less simple, but as call_ref is so inefficient, it's worth doing all
we can.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Previously we would keep doing iterations of inlining until we hit
the number of functions in the module (at which point, we would
definitely know we are recursing). This prevents infinite recursion,
but it can take a very very long time to notice that in a huge
module with one tiny recursive function and 100,000 other ones.
To do better than that, track how many times we've inlined into
a function. After a fixed number of such inlinings, stop.
Aside from avoding very slow compile times, such infinite
recursion likely is not that beneficial to do a great many times,
so anyhow it is best to stop after a few iterations.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
This only moves code around. visitBrOn was in the main part of the pass,
which was incorrect as it could interfere with other work being done there.
Specifically, we have a stack of Ifs there, and if we replace a BrOn with
an If, an assertion was hit. To fix this, run it like sinkBlocks(), in a separate
interleaved phase.
This fixes a bug reported by askeksa-google here:
https://github.com/WebAssembly/gc/issues/226#issuecomment-868739853
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
ConstantFieldPropagation (#4064)
Previously we tracked them in the same way. That means that we did the same
when seeing if either a struct.new or a struct.set can write to the memory that
is read by a struct.get, where the rule is that if either type is a subtype of the
other then they might. But with struct.new we know the precise type, which
means we can do better.
Specifically, if we see a new of type B, then only a get of a supertype of B can
possibly read that data: it is not possible for our struct of type B to appear in
a location that requires a subtype of B. Conceptually:
A = type struct
B = type extends A
C = type extends B
x = struct.new<B>
struct.get<A>(y) // x might appear here, as it can be assigned to a
// variable y of a supertype
struct.get<C>(y) // x cannot appear here
This allows more devirtualization. It is a followup for #4052 that implements
a TODO from there.
The diff without whitespace is simpler.
|
|
|
|
|
| |
This moves code out of the existing methods so that it can be
called directly from more places. A later PR will use the new
methods.
|
| |
|
|
|
|
| |
Localizer may have added a new tee and a local, and we should apply
the tee in the right place.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A field in a struct is constant if we can see that in the entire program we
only ever write the same constant value to it. For example, imagine a
vtable type that we construct with the same funcrefs each time, then (if
we have no struct.sets, or if we did, and they had the same value), we
could replace a get with that constant value, since it cannot be anything
else:
(struct.new $T (i32.const 10) (rtt))
..no other conflicting values..
(struct.get $T 0) => (i32.const 10)
If the value is a function reference, then this may allow other passes
to turn what was a call_ref into a direct call and perhaps also get
inlined, effectively a form of devirtualization.
This only works in nominal typing, as we need to know the supertype
of each type. (It could work in theory in structural, but we'd need to do
hard work to find all the possible supertypes, and it would also
become far less effective.)
This deletes a trivial test for running -O on GC content. We have
many more tests for GC at this point, so that test is not needed, and
this PR also optimizes the code into something trivial and
uninteresting anyhow.
|
|
|
|
|
|
| |
Define a "single constant expression" consistently, as a thing that is a
compile-time constant. Move I31New out of there, and also clean up
the function it is moved into, canInitializeGlobal(), to be consistent
in validating children.
|
|
|
|
|
|
| |
We were missing StructNew and ArrayLen.
Also refactor the helper method to allow for shorter code in each
caller.
|
| |
|
| |
|
|
|
| |
The wrong variable was null checked, leading to segfaults.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Add a new run line to every list test containing a struct type to run the test
again with nominal typing. In cases where tests do not use any struct subtyping,
this does not change the test output. In cases where struct subtyping is used, a
new check prefix is introduced to capture the difference that `(extends ...)`
clauses are emitted in nominal mode but not in equirecursive mode. There are no
other test differences.
Some tests are cleaned up along the way. Notably,
O_all-features{,-ignore-implicit-traps}.wast is consolidated to a single file.
|
|
|
|
|
| |
Add a new OptimizeForJS pass which contains rewriting rules specific to JavaScript.
LLVM usually lowers x != 0 && (x & (x - 1)) == 0 (isPowerOf2) to popcnt(x) == 1 which is ok for wasm and other targets but is quite expensive for JavaScript. In this PR we lower the popcnt pattern back to the isPowerOf2 pattern.
|
|
|
|
| |
The field name lookup was previously failing because the lookup used a fresh
struct type rather than using the intended struct type.
|
|
|
|
|
|
|
|
| |
We just cleared the list of exports, but the exportMap was still populated, so
the data was in an inconsistent state.
This fixes the case of running --extract-function multiple times on a file (which
is usually not useful, unless one of the passes you are debugging adds new
functions to the file - which some do).
|
| |
|
| |
|
| |
|
|
|
|
|
| |
DeadArgumentElimination (#4036)
To do this we need to look at tail calls and not just returns.
|
|
|
|
|
|
| |
It is ok to use the default value of a reference even if we refine the type,
as it would be a more specifically-typed null, and all nulls compare the
same. However, if the default is used then we *cannot* alter the type to
be non-nullable, as then we'd use a null where that is not allowed.
|
|
|
|
| |
Partially reverts #4025, removing the code and updates the test to show
we do the optimization.
|
|
|
|
| |
interpreter (#4023)
|
|
|
|
|
|
|
|
|
| |
The tail call spec does not include subtyping because it is based on the
upstream spec, which does not contain subtyping. However, there is no reason
that subtyping shouldn't apply to tail calls like it does for any other call or
return. Update the validator to allow subtyping and avoid a related null pointer
dereference while we're at it.
Do not run the test in with --nominal because it is buggy in that mode.
|
|
|
|
| |
(#4031)
|
|
|
| |
This is necessary when using GC and EH together, for instance.
|
|
|
|
| |
(#4027)
|
|
|
|
|
|
| |
callees (#4025)
If there is a tail call, we can't change the return type of the function, as it
must match in the functions doing a tail call of it.
|
|
|
|
|
|
| |
Corresponds to #4014 which did the same for parameter types. This sees
whether the return types actually returned from a function allow us to use
a more specific type for the function's return. If so, we update that type, as
well as calls to the function.
|
| |
|
| |
|
| |
|
|
|
|
|
| |
The pass handled non-nullability, but another case is a tuple with nullable
values in it that is assigned non-nullable values, and in general, other stuff
that is nondefaultable (but not non-nullable). Ignore those.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Given
(local $x (ref $foo))
(local.set $x ..)
(.. (local.get $x))
If we remove the local.set but not the get, then we end up with
(local $x (ref $foo))
(.. (local.get $x))
It looks like the local.get reads the initial value of a non-nullable local,
which is not allowed.
In practice, this would crash in precompute-propagate which would
try to propagate the initial value to the get. Add an assertion there with
a clear message, as until we have full validation of non-nullable locals
(and the spec for that is in flux), that pass is where bugs will end up
being noticed.
To fix this, replace the get as well. We can replace it with a null
for simplicity; it will never be used anyhow.
This also uncovered a small bug with reached not containing all
the things we reached - it was missing local.gets.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
If a local is say anyref, but all values assigned to it are something
more specific like funcref, then we can make the type of the local
more specific. In principle that might allow further optimizations, as
the local.gets of that local will have a more specific type that their
users can see, like this:
(import .. (func $get-funcref (result funcref)))
(func $foo (result i32)
(local $x anyref)
(local.set $x (call $get-funcref))
(ref.is_func (local.get $x))
)
=>
(func $foo (result i32)
(local $x funcref) ;; updated to a subtype of the original
(local.set $x (call $get-funcref))
(ref.is_func (local.get $x)) ;; this can now be optimized to "1"
)
A possible downside is that using more specific types may not end
up allowing optimizations but may end up increasing the size of
the binary (say, replacing lots of anyref with various specific
types that compress more poorly; also, for recursive types the LUB
may be a unique type appearing nowhere else in the wasm). We
should investigate the code size factors more later.
|
|
|
|
|
|
|
|
|
| |
Practically NFC, but it does reorder some code a little. Previously we would
find a "zero", then shrink segments, then use that zero - which might no
longer be in the table. That seems weird, so this reorders that, but there
should be no significant difference in the output.
Also reduce the factor of 100 to 1, which in practice is important on one of
the Dart GC benchmarks that has a huge number of table segments.
|
|
|
|
|
|
|
|
| |
If a function is always called with a more specific type than it is declared, we can
make the type more specific.
DeadArgumentElimination's name is becoming increasingly misleading, and should
maybe be renamed. But it is the right place for this as it already does an LTO
scan of the call graph and builds up parameter data structures etc.
|