| Commit message (Collapse) | Author | Age | Files | Lines |
... | |
|
|
|
| |
This is just noticeable when debugging locally and doing a quick print to
stdout.
|
|
|
|
| |
VMs will not convert a 0 or undefined from JS into a wasm null reference - it must be null.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This matches #3747 which makes us not log out reference values, instead
we print just their types.
This also prints a type for non-reference things, replacing a previous
exception, which affects things like SIMD and BigInts, but those trap
anyhow at the JS boundary I believe (or did that change for SIMD?).
Anyhow, printing the type won't give a false "ok" when comparing wasm2js
output to the interpreter, assuming the interpreter prints out a value and
not just a type (which is the case). We could try to do better, but this
code is on the JS side, where we don't have the type - just a string
representation of it, which we'd need to parse etc.
|
|
|
|
|
|
|
| |
This avoids an annoying case where in each iteration we try to remove
every function one by one and keep failing. Instead, we'll skip large
numbers of them when the factor is large at least.
Also shorten some unnecessary logging.
|
|
|
|
| |
Also removes experimental SIMD instructions that were not included in the final
spec proposal.
|
|
|
|
| |
This is needed to make sure globals are printed before element segments,
where `global.get` can appear both as offset and an expression.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
When canonical heap types were already present in the global store, for example
during the --roundtrip pass, type canonicalization was not working correctly.
The issue was that the GlobalCanonicalizer was replacing temporary HeapTypes
with their canonical equivalents one type at a time, but the act of replacing a
temporary HeapType use with a canonical HeapType use could change the shape of
later HeapTypes, preventing them from being correctly matched with their
canonical counterparts. This PR fixes that problem by computing all the
temporary-to-canonical heap type replacements before executing them.
To avoid a similar problem when canonicalizing Types, one solution would have
been to pre-calculate the replacements before executing them just like with the
HeapTypes, but that would have required either complex bookkeeping or moving
temporary Types into the global store when they are first canonicalized. That
would have been complicated because unlike for temporary HeapTypeInfos, the
unique_pointer to temporary TypeInfos is not readily available. This PR instead
switches back to using pointer-identity based equality and hashing for
TypeInfos, which works because we only ever canonicalize Types with canonical
children. This change should be a nice performance improvement as well.
Another bug this PR fixes is that shape hashing and equality considered
BasicKind HeapTypes to be different from their corresponding BasicHeapTypes,
which meant that canonicalization could produce different types for the same
type definition depending on whether the definition used a TypeBuilder or not.
The fix is to pre-canonicalize BasicHeapTypes (and Types that have them as
children) during shape hashing and equality. The same mechanism is also used to
simplify Store's canonicalization.
Fixes #3736.
|
|
|
|
| |
Previously an out-of-bounds index would result in an out-of-bounds read during
finalization of the tuple.extract expression.
|
|
|
|
|
|
|
| |
We've been keeping old syntax in the text format parser although they've
been removed from the parser and hardly any test case relies on them.
This PR will remove old syntax support for tables and element segments
and simplify the corresponding parser functions. A few test files were
affected by this that are updated.
|
|
|
|
|
|
|
|
|
| |
The problem is that a tuple with a non-nullable element cannot be stored
to a local. We'd need to split up the tuple, but that raises questions about
what should be allowed in flat IR (we'd need to allow nested tuple ops
in more places). That combination doesn't seem urgent, so add a clear
error for now, and avoid it in the fuzzer.
Avoids #3759 in the fuzzer
|
|
|
|
|
|
|
| |
If we are ignoring implicit traps, and if the cast is from a subtype to a supertype,
then we ignore the possible RTT-related inconsistency and can just drop the
cast.
See #3636
|
|
|
|
|
|
| |
This allows changing a global's value on the commandline in an easy way.
In some toolchains this is useful as the build can contain a global that
indicates something like a logging level, which this can customize.
|
|
|
|
|
| |
The key fix here is to call walkModuleCode so that we pick up on
types that appear only in the table and nowhere else. The rest of
the patch refactors the code so that that is practical.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This code used to remove functions it no longer thinks are needed. That is,
if it adds a legalized version of an import, it would remove the illegal
one which is no longer needed. To avoid removing an illegal import that
is still used it checked for ref.func appearances.
But this was bad in two ways:
We need to legalize the ref.funcs too. We can't call an illegal import
in any way, not using a direct call, indirect call, or call by reference of
a ref.func.
It's silly to remove unneeded functions here. We have a pass for that.
This removes the removal of functions, and adds proper updating of
ref.calls, which means to call the stub function that looks like the
original import, but that calls the legalized one and connects things
up properly, exactly the same way as other calls.
Also remove code that checked if we were in the stub/thunk and to
not replace the call there. That code is not needed: no one will ever
call the illegal import, so we do not need to be careful about
preserving such calls.
|
|
|
|
|
|
|
|
| |
The passive keyword has been removed from spec's text format, and now
any data segment that doesn't have an offset is considered as passive.
This PR remove that from both parser and the Print pass, plus all tests
that used that syntax.
Fixes #2339
|
|
|
|
|
|
|
|
|
|
|
|
| |
Log out an i64 as two i32s. That keeps the output consistent regardless of
whether we legalize.
Do not print a reference result. The printed value cannot be compared, as
funcref(10) (where 10 is the index of the function) is not guaranteed to be
the same after opts.
Trap when trying to call an export with a nondefaultable type (instead of
asserting when trying to create zeros for it).
|
|
|
|
|
|
|
|
|
| |
This is similar to the optimization of BrOn in #3719 and #3724. When the
type tells us the kind of input we have, we can tell at compile time what
result we'll get, like ref.is_func of something with type (ref func) will
always return 1, etc.
There are some code size and perf tradeoffs that should be looked into
more and are marked as TODOs.
|
|
|
|
|
|
| |
We missed that in effects.h, with the result that sets could look like
they had no side effects.
Fixes #3754
|
|
|
|
|
|
|
|
|
|
|
| |
In this case, there is a natural place to fix things up right after
removing a parameter (which is where a local gets added). Doing it
there avoids doing work on all functions unnecessarily.
Note that we could do something even simpler here than calling
the generic code: the parameter was not used, so the new local
is not used, and we could just change the type of the local or not
add it at all. Those would be slightly more code though, and add
complexity to the parameter removal method itself.
|
|
|
|
|
|
|
|
|
| |
This was noticed by samparker on LLVM:
https://reviews.llvm.org/D99171
This is apparently a pattern LLVM emits, and doing it there helps by 1-2%
on the real-world Bullet Physics codebase. Seems worthwhile doing here
as well.
|
|
|
|
|
|
|
| |
This is a partial revert of #3669, which removed the old implementation of
Type::getLeastUpperBound that did not correctly handle recursive types. The new
implementation in this PR uses a TypeBuilder to construct LUBs and for recursive
types, it returns a temporary HeapType that has not yet been fully constructed
to break what would otherwise be infinite recursions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Several old passes like DeadArgumentElimination and DuplicateFunctionElimination
need to look at all ref.funcs, and they scanned functions for that, but that is not
enough as such an instruction might appear in a global initializer. To fix this, add a
walkModuleCode method.
walkModuleCode is useful when doing the pattern of creating a function-parallel
pass to scan functions quickly, but we also want to do the same scanning of code
at the module level. This allows doing so in a single line.
(It is also possible to just do walk() on the entire module, which will find all code,
but that is not function-parallel. Perhaps we should have a walkParallel() option
to simplify this further in a followup, and that would call walkModuleCode afterwards
etc.)
Also add some missing validation and comments in the validator about issues that
I noticed in relation to the new testcases here.
|
|
|
|
| |
If such a parameter is written to then we create a new local for each
such write, and must handle non-nullability of those new locals.
|
|
|
|
| |
There is a makeZeros right below that, which will assert on a nondefaultable
type.
|
|
|
|
| |
Parameters can be non-nullable, and must stay so if they began as such.
By mistake we modified them with the vars.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This implements emscripten-core/emscripten#13744
Inlining functions with a single use allows us to remove the function afterward.
That looks highly beneficial, shrinking every single benchmark in emscripten's
benchmark suite, by an average of 2% on the macrobenchmarks and 3.5% on
all of them. Speed also improves, although mostly on the microbenchmarks so
that might be less realistic.
There may be a slight downside to startup time due to emitting larger functions,
but given the baseline compilers in VMs these days it seems worth it, as the
delay would be just to get to the upper tier. On the benchmark suite the risk
seems low.
See more details in the PR above.
|
|
|
| |
Also handle more cases of non-function data types there.
|
|
|
|
| |
Without this, crashes from things like #3736 simply get reported as
"a parse exception was thrown" with no detail.
|
|
|
|
|
| |
The pass gives a simple short name to each type, $type$N. This can be
useful in debugging, to avoid the autogenerated names which can be very
long.
|
|
|
|
|
|
|
|
| |
We must write them to a tuple with nullable types, then fix that up when
reading. This is similar to what we do in handleNonNullableLocals, except
that it operates on the entire tuple type, so it can't share that code.
This fixes a regression from #3710 that was harder to notice by the fuzzer
until now.
|
|
|
|
|
|
|
| |
This looks like a pre-existing issue to #3731, but
the testcase only fails on that PR for a reason I did not investigate in
depth, so it should land first.
Also fix the check for nondefaultability in the fuzzer.
|
|
|
|
|
|
| |
We can't disallow it, as its result is non-null which we can't spill to a local.
This may cause issues eventually in the combination of GC + flatten, but
I don't expect it to. If it does we may need to revisit.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Makes TypeBuilders growable, adds a `getTempHeapType` method, allows the
`getTemp*Type` methods to take arbitrary temporary or canonical HeapTypes rather
than just an index, and allows BasicHeapTypes to be assigned to TypeBuilder
slots. All of these changes are necessary for the upcoming re-implementation of
equirecursive LUB calculation.
Also adds a new utility to TypeBuilder for using `operator[]` as an intuitive
and readable wrapper around the `getTempHeapType` and `setHeapType` methods.
|
|
|
|
|
| |
This variable is only used when asserts are enabled, so it was causing
compilation errors on optimized builds with -Wunused-variable -Werror. This PR
fixes the build errors by hiding the variable and its uses behind ifdefs.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
used (#3727)
For example, on this invalid wat:
(module
(type $vec (struct (field i64)))
(func $test
(drop
(struct.new_with_rtt $vec (i32.const 1) (rtt.canon $vec))
)
)
)
We used to print:
[wasm-validator error in function test] struct.new operand must have proper type, on
(struct.new_with_rtt ${i64}
(i32.const 1)
(rtt.canon ${i64})
)
We will now print:
[wasm-validator error in function test] struct.new operand must have proper type, on
(struct.new_with_rtt $vec
(i32.const 1)
(rtt.canon $vec)
)
Note that $vec is used. In real-world examples the autogenerated structural name
can be huge, which this avoids.
|
|
|
|
|
|
|
|
|
|
|
|
| |
#3719 optimized the case where the kind is what we want, like br_on_func of
a function. This handles the opposite case, where we know the kind is wrong, and
so the break is not taken.
This seems equally useful for "polymorphic" code that does a bunch of checks
and routes to different code for each case, as after inlining or other optimizations
we may see which paths are taken and which are not.
Also refactor the checking code to a shared location as RefIs/As will also want to
use it.
|
|
|
| |
That pass adds lots of new locals, and we need to handle non-nullable ones.
|
| |
|
|
|
|
|
|
| |
This PR adds support for `ref.null t` as a valid element segment
item. The abbreviated format of `(elem ... func $f $g...)` is kept in
both printing and binary emitting if all items are `ref.func`s. Public
APIs aren't updated in this PR.
|
|
|
|
| |
The type may prove the value is not null, and may also show it to be
of the type we are casting to. In that case, we can simplify things.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* Equirecursive type canonicalization
Use Hopcroft's DFA minimization algorithm to properly canonicalize and
deduplicate recursive types. Type canonicalization has two stages:
1. Shape canonicalization
- The top-level structure of HeapTypes is used to split the declared HeapTypes
into their initial partitions.
- Hopcroft's algorithm refines the partitions such that all pairs of
distinguishable types end up in different partitions.
- A fresh HeapTypeInfo is created for each final partition. Each new
HeapTypeInfo is linked to other new HeapTypeInfos to create a minimal type
definition graph that defines the same types as the original graph.
2. Global canonicalization
- Each new minimal HeapTypeInfo that does not have the same finite
shape as an existing globally canonical HeapTypeInfo is moved to the
global heap type store to become the new canonical HeapTypeInfo.
- Each temporary Type referenced by the newly canonical HeapTypeInfos is
replaced in-place with the equivalent canonical Type to avoid leaking
temporary Types into the global stores.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
After this PR we still do not support non-nullable locals. But we no longer
turn all types into nullable upon load. In particular, we support non-nullable
types on function parameters and struct fields, etc. This should be enough to
experiment with optimizations in both binaryen and in VMs regarding non-
nullability (since we expect that optimizing VMs can do well inside functions
anyhow; it's non-nullability across calls and from data that the VM can't be
expected to think about).
Let is handled as before, by lowering it into gets and sets. In addition, we
turn non-nullable locals into nullable ones, and add a ref.as_non_null on
all their gets (to keep the type identical there). This is used not just for
loading code with a let but also is needed after inlining.
Most of the code changes here are removing FIXMEs for allowing
non-nullable types. But there is also code to handle the issues mentioned
above.
Most of the test updates are removing extra nulls that we added before
when we turned all types nullable. A few tests had actual issues, though,
and also some new tests are added to cover the code changes here.
|
|
|
|
|
| |
We have the if's type, and when replacing it with a select, can use that
type. This could be more efficient. It also avoids a current crash
after the removal of LUBs, but it's worth doing regardless of that.
|
|
|
|
|
|
| |
When we can skip function bodies, we still need to parse the start function
for the pthreads case, see details in the comments. This still gives us 99%
of the speedup as the start function is just 1 function and it's not that big,
so with this we return to full speed after the reversion in #3705
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
I'm not entirely sure how LUB removal made this noticeable, as it seems
to be a pre-existing bug. However, somehow before #3669 it was not
noticable - perhaps the finalize code worked around it.
The bug is that RemoveUnusedBrs was moving code around and
finalizing the parent before the child. The correct pattern is always to
work from the children outwards, as otherwise the parent is trying to
finalize itself based on non-finalized children.
The fix is to just not finalize in the stealSlice method. The caller can
do it after finishing any other work it has. As part of this refactoring,
move stealSlice into the single pass that uses it; aside from that being
more orderly, this method is really not a general-purpose tool, it is
quite specific to what RemoveUnusedBrs does, and it might easily
be used incorrectly elsewhere.
|
| |
|
|
|
|
|
| |
It is a little "fun" to fake their uses because of the overloaded type.
cppreference says that this is the way to do it, and I've found no
other way than a C cast like this (std::function fails).
|