| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
|
|
|
|
| |
The `op` string_view was intentionally created to point into the `buf` buffer so
that reading past its end would still be safe, but some C++ standard library
implementations assert when reading past the end of a string_view. Change the
generated code to read out of `buf` instead to avoid those assertions.
Fixes #5322.
Fixes #5342.
|
|
|
|
|
|
|
| |
We previously supported only the non-standard cast instructions introduced when
we were experimenting with nominal types. Parse the names and opcodes of their
standard counterparts and switch to emitting the standard names and opcodes.
Port all of the tests to use the standard instructions, but add additional tests
showing that the non-standard versions are still parsed correctly.
|
|
|
|
|
|
|
|
| |
This finds types that can be merged into their super: types that add no
fields, and are not used in casts, etc. - so we might as well use the super.
This complements TypeSSA, in that it can merge back the new types that
TypeSSA created, if we never found a use for them. Without this, TypeSSA
can bloat binary size quite a lot (I see 10-20%).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This creates new nominal types for each (interesting) struct.new. That then allows
type-based optimizations to be more precise, as those optimizations will track
separate info for each struct.new, in effect. That is kind of like SSA, however, we
do not handle merges. For example:
x = struct.new $A (5);
print(x.value);
y = struct.new $A (11);
print(y.value);
// => //
x = struct.new $A.x (5);
print(x.value);
y = struct.new $A.y (11);
print(y.value);
After the pass runs each of those struct.new creates a unique type, and type-based
analysis can see that 5 or 11 are the only values written in that type (if nothing else
writes there).
This bloats the type section with the new subtypes, so it is best used with a pass
to merge unneeded duplicate types, which a later PR will add. That later PR will
exactly merge back in the types created here, which are nominally different but
indistinguishable otherwise.
This pass is not enabled by default. It's not clear yet where is the best place to do it,
as it must be balanced by type merging, but it might be better to do multiple
rounds of optimization between the two. Needs more investigation.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
With this change we default to an open world, that is, we do the safe thing
by default: we no longer assume a closed world. Users that want a closed
world must pass --closed-world.
Atm we just do not run passes that assume a closed world. (We might later
refine them to find which types don't escape and only optimize those.) The
RemoveUnusedModuleElements is an exception in that the closed-world
flag influences one part of its operation, but not the rest.
Fixes #5292
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* Do not compare reference values across executions
Since we optimize assuming a closed world, optimizations can change the types
and structure of GC data even in externally-visible ways. Because differences
are expected, the fuzzer already did not compare reference-typed values from
before and after optimizations when running with nominal typing. Update it to
not compare these values under any type system.
* Unpin V8
Our WasmGC output is no longer compatible with the previously pinned version and
the issue that caused us to pin it in the first place has been resolved.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
(some.operation
(ref.cast .. (local.get $ref))
(local.get $ref)
)
=>
(some.operation
(local.tee $temp
(ref.cast .. (local.get $ref))
)
(local.get $temp)
)
This can help cases where we cast for some reason but happen to not use the
cast value in all places. This occurs in j2wasm in itable calls sometimes: The
this pointer is is refined, but the itable may be done with an unrefined pointer,
which is less optimizable.
So far this is just inside basic blocks, but that is enough for the cast of itable
calls and other common patterns I see.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Monomorphization finds cases where we send more refined types to a function
than it declares. In such cases we can copy the function and refine the parameters:
// B is a subtype of A
foo(new B());
function foo(x : A) { ..}
=>
foo_B(new B()); // call redirected to refined copy
function foo(x : A) { ..} // unchanged
function foo_B(x : B) { ..} // refined copy
This increases code size so it may not be worth it in all cases. This initial PR is
hopefully enough to start experimenting with this on performance, and so it does
not enable the pass by default.
This adds two variations of monomorphization, one that always does it, and the
default which is "careful": it sees whether monomorphizing lets the refined function
actually be better than the original (say, by removing a cast). If there is no
improvement then we do not make any changes. This saves a significant amount
of code size - on j2wasm the careful version increases by 13% instead of 20% -
but it does run more slowly obviously.
|
|
|
|
|
|
|
|
|
| |
In order to test them, fix the binary and text parsers to accept passive data
segments even if a module has no memory. In addition to parsing and emitting the
new instructions, also implement their validation and interpretation. Test the
interpretation directly with wasm-shell tests adapted from the upstream spec
tests. Running the upstream spec tests directly would require fixing too many
bugs in the legacy text parser, so it will have to wait for the new text parser
to be ready.
|
|
|
|
|
|
|
| |
That PR renamed test/lit/optimize-instructions.wast to
test/lit/optimize-instructions-mvp.wast. However, the fuzzer was explicitly
adding the testto the list of important initial contents under the old name, so
it was failing an assertion that the initial contents existed. Update the fuzzer
to use the new test name.
|
|
|
|
|
|
|
| |
These operations emit a completely different type than their input, so they must be
marked as roots, and not as things that flow values through them (because then
we filter everything out as the types are not compatible).
Fixes #5219
|
| |
|
|
|
|
|
| |
The fuzzer started to fail on the recent externalize/internalize test
that was added in #5175 as we lack interpreter support. Move that to a separate
file and ignore it in the fuzzer for now.
|
|
|
|
|
|
|
|
|
|
| |
Add parsing functions for `memarg`s, the offset and align fields of load and
store instructions. These fields are interesting because they are lexically
reserved words that need to be further parsed to extract their actual values. On
top of that, add support for parsing all of the load and store instructions.
This required fixing a buffer overflow problem in the generated parser code and
adding more information to the signatures of the SIMD load and store
instructions. `SIMDLoadStoreLane` instructions are particularly interesting
because they may require backtracking to parse correctly.
|
|
|
|
|
|
|
|
|
| |
Since gen-s-parser.py is essentially a giant table mapping instruction names to
the information necessary to construct the corresponding IR nodes, there should
be no need to further parse instruction names after the code generated by
gen-s-parser.py runs. However, memory instruction parsing still parsed
instruction names to get information such as size and default alignment. The new
parser does not have the ability to parse that information out of instruction
names, so put it in the gen-s-parser.py table instead.
|
|
|
|
|
|
|
|
| |
The `makeXXX` functions that are responsible for individual instructions will
generally need the locations of those functions to emit useful errors. However,
since the instruction names are parsed before the `makeXXX` functions are
called, the functions have no good way of getting the location of the beginning
of the instruction. Fix this by explicitly passing them the location of the
beginning of the instruction.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Rather than passing both a `Ctx` and a `ParseInput` to every parsing function,
pass only a `Ctx` with a `ParseInput` inside of it. This significantly reduces
verbosity in the parser. To handle cases where parsing needs to happen at
specific locations, which used to be handled by constructing a new `ParseInput`
independent from the ctx, introduce a new RAII utility for temporarily changing
the location of the `ParseInput` inside a context.
Also add a utility for generating an error at a particular location to avoid
having to construct new `ParseInput` objects just for that purpose. This
resolves a few TODOs about correcting error locations, but since we don't test
those yet, I still consider this NFC.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
With the goal of supporting null characters (i.e. zero bytes) in strings.
Rewrite the underlying interned `IString` to store a `std::string_view` rather
than a `const char*`, reduce the number of map lookups necessary to intern a
string, and present a more immutable interface.
Most importantly, replace the `c_str()` method that returned a `const char*`
with a `toString()` method that returns a `std::string`. This new method can
correctly handle strings containing null characters. A `const char*` can still
be had by calling `data()` on the `std::string_view`, although this usage should
be discouraged.
This change is NFC in spirit, although not in practice. It does not intend to
support any particular new functionality, but it is probably now possible to use
strings containing null characters in at least some cases. At least one parser
bug is also incidentally fixed. Follow-on PRs will explicitly support and test
strings containing nulls for particular use cases.
The C API still uses `const char*` to represent strings. As strings containing
nulls become better supported by the rest of Binaryen, this will no longer be
sufficient. Updating the C and JS APIs to use pointer, length pairs is left as
future work.
|
|
|
| |
Fixes ./scripts/fuzz_opt.py --auto-initial-contents 7044408155933374954
|
|
|
|
|
|
| |
More recent version of V8 include a change from `dataref` to `structref` that
prevent WasmGC modules produced by the fuzzer from validating. Use the last
compatible v8 version if it is in the path until we can update Binaryen to be
compatible with newer v8.
|
|
|
| |
Also fix some formatting issue in the file.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This adds a map of function name => the effects of that function to the
PassOptions structure. That lets us compute those effects once and then
use them in multiple passes afterwards. For example, that lets us optimize
away a call to a function that has no effects:
(drop (call $nothing))
[..]
(func $nothing
;; .. lots of stuff but no effects, only a returned value ..
)
Vacuum will remove that dropped call if we tell it that the called function has
no effects. Note that a nice result of adding this to the PassOptions struct
is that all passes will use the extra info automatically.
This is not enabled by default as the benefits seem rather minor, though it
does help in a small but noticeable way on J2Wasm code, where we use
call.without.effects and have situations like this:
(func $foo
(call $bar)
)
(func $bar
(call.without.effects ..)
)
The call to bar looks like it has effects, normally, but with global effect info
we know it actually doesn't.
To use this, one would do
--generate-global-effects [.. some passes that use the effects ..] --discard-global-effects
Discarding is not necessary, but if there is a pass later that adds effects, then not
discarding could lead to bugs, since we'd think there are fewer effects than there are.
(However, normal optimization passes never add effects, only remove them.)
It's also possible to call this multiple times:
--generate-global-effects -O3 --generate-global-effects -O3
That computes affects after the first -O3, and may find fewer effects than earlier.
This doesn't compute the full transitive closure of the effects across functions. That is,
when computing a function's effects, we don't look into its own calls. The simple case
so far is enough to handle the call.without.effects example from before (though it
may take multiple optimization cycles).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
In practice typed function references will not ship before GC and is not
independently useful, so it's not necessary to have a separate feature for it.
Roll the functionality previously enabled by --enable-typed-function-references
into --enable-gc instead.
This also avoids a problem with the ongoing implementation of the new GC bottom
heap types. That change will make all ref.null instructions in Binaryen IR refer
to one of the bottom heap types. But since those bottom types are introduced in
GC, it's not valid to emit them in binaries unless unless GC is enabled. The fix
if only reference types is enabled is to emit (ref.null func) instead
of (ref.null nofunc), but that doesn't always work if typed function references
are enabled because a function type more specific than func may be required.
Getting rid of typed function references as a separate feature makes this a
nonissue.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
An overview of this is in the README in the diff here (conveniently, it is near the
top of the diff). Basically, we fix up nn locals after each pass, by default. This keeps
things easy to reason about - what validates is what is valid wasm - but there are
some minor nuances as mentioned there, in particular, we ignore nameless blocks
(which are commonly added by various passes; ignoring them means we can keep
more locals non-nullable).
The key addition here is LocalStructuralDominance which checks which local
indexes have the "structural dominance" property of 1a, that is, that each get has
a set in its block or an outer block that precedes it. I optimized that function quite
a lot to reduce the overhead of running that logic after each pass. The overhead
is something like 2% on J2Wasm and 0% on Dart (0%, because in this mode we
shrink code size, so there is less work actually, and it balances out).
Since we run fixups after each pass, this PR removes logic to manually call the
fixup code from various places we used to call it (like eh-utils and various passes).
Various passes are now marked as requiresNonNullableLocalFixups => false.
That lets us skip running the fixups after them, which we normally do automatically.
This helps avoid overhead. Most passes still need the fixups, though - any pass
that adds a local, or a named block, or moves code around, likely does.
This removes a hack in SimplifyLocals that is no longer needed. Before we
worked to avoid moving a set into a try, as it might not validate. Now, we just do it
and let fixups happen automatically if they need to: in the common code they
probably don't, so the extra complexity seems not worth it.
Also removes a hack from StackIR. That hack tried to avoid roundtrip adding a
nondefaultable local. But we have the logic to fix that up now, and opts will
likely keep it non-nullable as well.
Various tests end up updated here because now a local can be non-nullable -
previous fixups are no longer needed.
Note that this doesn't remove the gc-nn-locals feature. That has been useful for
testing, and may still be useful in the future - it basically just allows nn locals in
all positions (that can't read the null default value at the entry). We can consider
removing it separately.
Fixes #4824
|
|
|
|
| |
These new GC instructions infallibly convert between `extern` and `any`
references now that those types are not in the same hierarchy.
|
|
|
|
| |
Also fix a small logic error - call lines can be prefixes of each other, so use
the full line (including newline) to differentiate.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 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 was reading these tests and failing to find the names script.
|
|
|
|
|
| |
Also, add support for the `--binaryen-bin` flag to
`scripts/port_passes_tests_to_lit.py`. This is needed for folks who
don't do in-tree builds.
|
|
|
|
| |
So it lives alongside `update_lit_checks.py` and `update_help_checks.py`
|
|
|
|
|
|
|
| |
* better
* fix
* undo
|
| |
|
|
|
|
| |
This is no longer needed by emscripten as of:
https://github.com/emscripten-core/emscripten/pull/16529
|
|
|
|
|
|
|
| |
RTTs were removed from the GC spec and if they are added back in in the future,
they will be heap types rather than value types as in our implementation.
Updating our implementation to have RTTs be heap types would have been more work
than deleting them for questionable benefit since we don't know how long it will
be before they are specced again.
|
|
|
|
|
|
|
|
|
| |
Sometimes the fuzzer produces valid modules that trap during instantiation. When
that happens, the JS harness used to run the fuzzer output in d8 would
previously throw an error, creating spurious fuzzer failures on valid modules.
Update fuzz_shell.js to catch and supress errors during instantiation (but not
validation) to avoid these spurious failures.
Fixes #4865.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This tracks the possible contents in the entire program all at once using a single IR.
That is in contrast to say DeadArgumentElimination of LocalRefining etc., all of whom
look at one particular aspect of the program (function params and returns in DAE,
locals in LocalRefining). The cost is to build up an entire new IR, which takes a lot
of new code (mostly in the already-landed PossibleContents). Another cost
is this new IR is very big and requires a lot of time and memory to process.
The benefit is that this can find opportunities that are only obvious when looking
at the entire program, and also it can track information that is more specialized
than the normal type system in the IR - in particular, this can track an ExactType,
which is the case where we know the value is of a particular type exactly and not
a subtype.
|
| |
|
| |
|
|
|
|
| |
This measures the length of a view, so it seems simplest to make it a
sub-operation of the existing measure instruction.
|
|
|
|
|
|
|
| |
Unfortunately one slice is the same as python [start:end], using 2 params,
and the other slice is one param, [CURR:CURR+num] (where CURR is implied
by the current state in the iter). So we can't use a single class here. Perhaps
a different name would be good, like slice vs substring (like JS does), but
I picked names to match the current spec.
|
| |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Update gen-s-parser.py to produce a second version of its parsing code that
works with the new wat parser. The new version automatically replaces the `s`
element argument in the existing parser with the `ctx` and `in` arguments used
by the new parser, so adding new instructions will not require any additional
work in gen-s-parser.py after this change.
Also add stub `make***` functions to the new wat parser, with a few filled out,
namely `makeNop`, `makeUnreachable`, `makeConst`, and `makeRefNull`. Update the
`global` parser to parse global initializer instructions and update
wat-kitchen-sink.wast to demonstrate that the instructions are parsed correctly.
Adding new instruction classes will require adding a new `make***` function to
wat-parser.cpp in additional to wasm-s-parser.{h,cpp} after this change, but
adding a trivial failing implementation is good enough for the time being, so I
don't expect this to appreciably increase our maintenance burden in the near
term.
The infrastructure for parsing folded instructions, instructions with operands,
and control flow instructions will be implemented in future PRs.
|
| |
|
|
|
|
|
|
|
| |
This implements it as a StringMeasure opcode. They do have the same number of
operands, same trapping behavior, and same return type. They both get a string and
do some inspection of it to return an i32. Perhaps the name could be StringInspect
or something like that, rather than StringMeasure..? But I think for now this might be
good enough, and the spec may change anyhow later.
|
| |
|