| Commit message (Collapse) | Author | Age | Files | Lines |
... | |
|
|
|
|
| |
It had hardcoded handling of the table, but was missing RefFunc (and maybe more?)
Also some cleanups around that code.
|
|
|
| |
Similar to #4969 but for element segments.
|
|
|
|
| |
These new GC instructions infallibly convert between `extern` and `any`
references now that those types are not in the same hierarchy.
|
|
|
| |
Similar to #4969 but for memories.
|
| |
|
|
|
|
| |
A resize from a large amount to a small amount would sometimes not clear
the flexible storage, if we used it before but not after.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
To unblock some optimizations. For example this:
```wat
(select
(i32.eqz (local.get $x))
(i32.const 0)
(i32.eqz (local.get $y))
)
```
Was previously optimized as:
```wat
(i32.eqz
(select
(i32.const 1)
(local.get $x)
(local.get $y)
)
)
```
Because `optimizeSelect` applied `!x ? !y : 0 -> x ? 0 : !y` then `!(x ? 1 : y)`, blocking the next rules which could have folded this to `or` or `and`.
After this PR the same example optimizes better:
```wat
(i32.eqz
(i32.or
(local.get $x)
(local.get $y)
)
)
```
|
|
|
| |
Similar to #4969 but for globals.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A continuation of #4272.
```
(signed)x < s_min + 1 ==> x == s_min
(signed)x >= s_min + 1 ==> x != s_min
(signed)x > s_max - 1 ==> x == s_max
(signed)x <= s_max - 1 ==> x != s_max
(unsigned)x <= u_max - 1 ==> x != u_max
(unsigned)x > u_max - 1 ==> x == u_max
```
|
|
|
| |
Similar to #4969 but for tables.
|
|
|
| |
These entries were accidentally removed in 9d20a4e1.
|
| |
|
|
|
|
|
|
|
| |
Match the latest version of the GC spec. This change does not depend on V8
changing its interpretation of the shorthands because we are still temporarily
not emitting the binary shorthands, but all Binaryen users will have to update
their interpretations along with this change if they use the text or binary
shorthands.
|
|
|
|
|
|
|
|
|
| |
Implement function parsing, including parsing of locals and type uses. Also add
a new phase of parsing that iterates through type uses that do not have explicit
types to create any implicitly defined types and append them to the type index
space. This is important because the implicitly defined types may be referred to
by index elsewhere and because the legacy parser does not handle these
implicitly defined types correctly. Finally, maintain a map of implicit type use
locations to corresponding types for use in later phases of parsing.
|
| |
|
|
|
|
|
|
|
| |
We do a call to updateMaps() at the end of processNames anyhow, and so we
may as well call addFunction immediately (and the names will get fixed up in that
updateMaps later). The old code for some reason did that for function imports, but
not normal functions. It also stored them separately in temporary storage for some
unclear reason...
|
|
|
| |
Adding multi-memories to the the list of wasm-features.
|
|
|
|
|
|
| |
The wasm spec requires the order of local names in that section to be in
increasing order:
https://webassembly.github.io/spec/core/appendix/custom.html#binary-namemap
|
|
|
|
|
| |
I don't see an existing test for this, and it's useful behavior since such inlining will
propagate the trap to the caller, possibly helping DCE and other things there, so it's
good to have a test to guarantee we never break it.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Fuzzing with TrapsNeverHappen found a bug, and then reading similar code
I found another, where we check structural equality but ignored effects. Some
things look equal but may have different values at runtime:
(foo
(call $x)
(call $y)
)
The arms are identical structurally but due to side effects may not be identical
in value.
|
|
|
|
| |
Also fix a small logic error - call lines can be prefixes of each other, so use
the full line (including newline) to differentiate.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The "ignore trap" logic there is not close to enough for what we'd need to
actually fuzz in a way that ignores traps, so this removes it. Atm that logic
just allows a trap to happen without causing an error (that is, when comparing
two results, one might trap and the other not, but they'd still be considered
"equal"). But due to how we optimize traps in TrapsNeverHappens mode, the
optimizer is free to assume the trap never occurs, which might remove side
effects that are noticeable later. To actually handle that, we'd need to refactor
the code to retain results per function (including the Loggings) and then to
ignore everything from the very first trapping function. That is somewhat
complicated to do, and a simpler thing is done in #4936, so we won't need
it here.
|
|
|
| |
Followup to #4910.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Those instructions need to know if the memory is 64-bit or not. We looked that
up on the module globally, which is convenient, but in the C API this was actually
a breaking change, it turns out. To keep things working, provide that information
when creating a MemoryGrow or MemorySize, as another parameter in the C
API. In the C++ API (wasm-builder), support both modes, and default to the
automatic lookup.
We already require a bunch of other explicit info when creating expressions, like
making a Call requires the return type (we don't look it up globally), and even a
LocalGet requires the local type (we don't look it up on the function), so this is
consistent with those.
Fixes #4946
|
|
|
| |
This change loops through memories to validate each meets wasm spec. Also factors data segment validation out from memory validation, as it makes more sense for data segments to stand alone like the other module-level elements.
|
|
|
| |
After landing #4944, also need to delete these unneeded test files.
|
|
|
|
|
| |
Just like `extern` is no longer a subtype of `any` in the new GC type system,
`func` is no longer a subtype of `any`, either. Make that change in our type
system implementation and update tests and fuzzers accordingly.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This mode is tricky to fuzz because the mode is basically "assume traps never
happen; if a trap does happen, that is undefined behavior". So if any trap occurs
in the random fuzz testcase, we can't optimize with -tnh and assume the results
stay to same. To avoid that, we ignore all functions from the first one that traps,
that is, we only compare the code that ran without trapping. That often is a small
subset of the total functions, sadly, but I do see that this ends up with some useful
coverage despite the drawback.
This also requires some fixes to comparing of references, specifically, funcrefs
are printed with the function name/index, but that can change during opts, so
ignore that. This wasn't noticed before because this new fuzzer mode does
comparisons of --fuzz-exec-before output, instead of doing --fuzz-exec which
runs it before and after and compares it internally in wasm-opt. Here we are
comparing the output externally, which we didn't do before.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This fixes what looks like it might be a regression in #4943. It's not actually
an issue since it just affects wat files, but it did uncover an existing
inefficiency. The situation is this:
(block
..
(br $somewhere)
(nop)
)
Removing such a nop is always helpful, as the pass might see that that
br goes to where control flow is going anyhow, and the nop would
confuse it. We used to remove such nops only when the block had a name,
which is why wat testcases looks optimal, but we were actually doing the
less-efficient thing on real-world code. It was a minor inefficiency, though, as
the nop is quickly removed by later passes anyhow. Still, the fix is trivial (to
always remove such nops, regardless of a name on the block or not).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Previously the wat parser would turn this input:
(block
(nop)
)
into something like this:
(block $block17
(nop)
)
It just added a name all the time, in case the block is referred to by an index
later even though it doesn't have a name.
This PR makes us rountrip more precisely by not adding such names: if there
was no name before, and there is no break by index, then do not add a name.
In addition, this will be useful for non-nullable locals since whether a block has
a name or not matters there. Like #4912, this makes us more regular in our
usage of block names.
|
|
|
|
|
|
|
| |
Some fuzzer initial contents contain non-nullable externrefs that cause the
fuzzer to try to materialize non-nullable externref values. Perviously the
fuzzer did not support this and crashed with an assertion failure. Fix the
assertion failure by instead returning a null cast to non-null, which will trap
at runtime but at least produce a valid module.
|
|
|
| |
Removing the .wasm multi-memories tests as they can be easily represented in .wast format, which is easier to read/handle.
|
|
|
|
|
|
|
| |
Such globals can be written to from the outside, so we cannot infer anything about
their contents.
Fixes #4932
|
|
|
|
|
|
| |
Resolving a couple of issues from the multi-memories PR landing:
Use memName as parameter label instead of name #4916
Add helper func for case of a single memory to binaryen-c #4917
|
|
|
|
|
|
|
|
| |
In LLVM output and probably others, the initial table contents are never
changed. We may append later, but we don't trample the initial table
entries. As a result, with this new flag we can turn indirect calls on those
offsets into direct ones:
--directize-initial-tables-immutable
|
|
|
|
|
| |
A memory must be explicitly defined before being exported.
Fix CI for 6b3f3af.
|
|
|
| |
Follow up to #4771 to test new --print-profile options for wasm-split.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
casts (#4720)
i32 -> f64 -> i32 rountripping optimizations:
```rust
i32(f64(i32(x))) -> x // i32.trunc(_sat)_f64_s(f64.convert_i32_s(x)) -> x
u32(f64(u32(x))) -> x // i32.trunc(_sat)_f64_u(f64.convert_i32_u(x)) -> x
// note assymetric signed / unsigned or unsigned / signed cases can't be simplified in similar way
```
and rounding after integer to float casts:
```rust
ceil(float(int(x))) -> float(int(x))
floor(float(int(x))) -> float(int(x))
trunc(float(int(x))) -> float(int(x))
nearest(float(int(x))) -> float(int(x))
```
where `float = f32 | f64`, `int = i32 | i64 | u32 | u64`
|
|
|
|
| |
Due to missing test coverage, we missed in #4811 that some memory operations
needed to get make64() called on them.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A rather tricky corner case: we normally look at fallthrough values for copies of
fields, so when we try to refine a field, we ignore stuff like this:
a.x = b.x;
That copies the same field on the same type to itself, so refining is not limited by
it. But if we have something else in the middle, and that thing cannot change
type, then it is a problem, like this:
(struct.set
(..ref..)
(local.tee $temp
(struct.get)))
tee has the type of the local, which does not change in this pass. So we can't
look at just the fallthrough here and skip the tee: after refining the field, the
tee's old type might not fit in the field's new type.
We could perhaps add casts to fix things up, but those may have too big a
cost. For now, just ignore the fallthrough.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
DAE will normally not remove an unreachable parameter, because it checks for
effects there. But in TrapsNeverHappen mode, we assume that an unreachable
is an effect we can remove, so we are willing to remove it:
(func $foo (param $unused i32)
;; never use $unused
)
(func $bar
(call $foo
(unreachable)))
;;=> dae+tnh
(func $foo
)
(func $bar
(call $foo))
But that transformation is invalid: the call's type was unreachable before but
no longer is. What went wrong here is that, yes, it is valid to remove an
unreachable, but we may need to update types while doing so, which we
were not doing.
This wasn't noticed before due to a combination of unfortunate factors:
The main reason is that this only happens in TrapsNeverHappens mode. We
don't fuzz that, because it's difficult: that mode can assume a trap never happens,
so a trap is undefined behavior really. On real-world code this is great, but in the
fuzzer it means that the output can seem to change after optimizations.
The validator happened to be missing an error for a call that has type unreachable
but shouldn't: Validator: Validate unreachable calls more carefully #4909 . Without
that, we'd only get an error if the bad type influenced a subsequent pass in a confusing
way - which is possible, but difficult to achieve (what ended up happening in practice is
that SignatureRefining on J2Wasm relied on the unreachable and refined a type too much).
Even with that fix, for the problem to be detected we'd need for the validation error to
happen in the final output, after running all the passes. In practice, though, that's not
likely, since other passes tend to remove unreachables etc. Pass-debug mode is very
useful for finding stuff like this, as it validates after every individual pass. Sadly it turns
out that global validation was off there: Validator: Validate globally by default #4906
(so it was catching the 99% of validation errors that are local, but this particular error
was in the remaining 1%...).
As a fix, simply ignore this case. It's not really worth the effort to optimize it, since DCE
will just remove unreachables like that anyhow. So if we run again after a DCE we'd get
a chance to optimize.
This updates some existing tests to avoid (unreachable). That was used as an example
of something with effects, but after this change it is treated more carefully. Replace those
things with something else that has effects (a call).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Normally the validator will find stale types properly, by just running refinalize and seeing
if the type has changed (if so, then some code forgot to refinalize). However, refinalize
is a local operation, so it does not apply to calls: a call's proper type is determined by
the global information of the function we are calling. As a result, we would not notice
errors like this:
(call $foo) ;; type: unreachable
Refinalizing that would not change the type from unreachable to the proper type, since
that is global information.
To validate this properly, validate that a call whose type is unreachable actually has
an unreachable child. That rules out an invalid unreachable type here, which leaves
concrete types, that we already have proper global validation for. The code here is
generalized to handle non-call things as well, but it only helps expressions requiring
global validation, so it likely only helps global.get and a few others.
|
|
|
|
|
|
|
|
|
|
| |
We already did this if the block was a child of a control flow structure, which is
the common case (see the new added comment around that code, which clarifies
why). This does the same for all other blocks. This is simple to do and a minor
optimization, but the main benefit from this is just to make our handling of blocks
uniform: after this, we never emit a block with no name. This will make 1a non-
nullable locals easier to handle (since they will be able to assume that property;
and not emitting such blocks avoids some work to handle non-nullable locals
in them).
|
|
|
|
|
|
|
| |
The GC proposal has split `any` and `extern` back into two separate types, so
reintroduce `HeapType::ext` to represent `extern`. Before it was originally
removed in #4633, externref was a subtype of anyref, but now it is not. Now that
we have separate heaptype type hierarchies, make `HeapType::getLeastUpperBound`
fallible as well.
|
|
|
|
|
|
|
| |
This PR removes the single memory restriction in IR, adding support for a single module to reference multiple memories. To support this change, a new memory name field was added to 13 memory instructions in order to identify the memory for the instruction.
It is a goal of this PR to maintain backwards compatibility with existing text and binary wasm modules, so memory indexes remain optional for memory instructions. Similarly, the JS API makes assumptions about which memory is intended when only one memory is present in the module. Another goal of this PR is that existing tests behavior be unaffected. That said, tests must now explicitly define a memory before invoking memory instructions or exporting a memory, and memory names are now printed for each memory instruction in the text format.
There remain quite a few places where a hardcoded reference to the first memory persist (memory flattening, for example, will return early if more than one memory is present in the module). Many of these call-sites, particularly within passes, will require us to rethink how the optimization works in a multi-memories world. Other call-sites may necessitate more invasive code restructuring to fully convert away from relying on a globally available, single memory pointer.
|
|
|
|
|
|
|
| |
Without this fix, newer node errors on:
"node-esm-loader.mjs 'resolve'" did not call the next hook in its chain and did not explicitly signal a short circuit. If this is intentional, include `shortCircuit: true` in the hook's return.
This adds that shortCircuit property.
|
|
|
|
|
|
| |
I'm not sure why this defaulted to non-global. Perhaps because of limitations
in the asm.js days. A better default is to validate globally, and this also applies
in pass-debug mode (since that just uses the default there), so this will catch
more problems there.
|
|
|
|
|
|
|
| |
The validation logic to check for stale types (code where we forgot to run
refinalize) had a workaround for a control flow issue. That workaround meant
we didn't catch errors where a type was concrete but it should be unreachable.
This PR makes that workaround only apply for control flow structures, so we can
catch more errors.
|
| |
|
| |
|