| Commit message (Collapse) | Author | Age | Files | Lines |
... | |
|
|
|
|
|
|
|
|
|
|
| |
Precompute still tried to precompute a reference because the check was
not in the topmost place.
Also we truncated i8/i16 values, but did not extend them properly. That
was also an issue with structs.
The new test replaces the old one by moving from -O1 to -Oz (which
runs more opts, and would have noticed this earlier), and adds array
operations too, including sign extends.
|
|
|
|
| |
This adds enough to read and write them and test that, but leaves
interpreter support for later.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Introduce TypeBuilder, a utility for constructing heap types in terms of other
heap types that may have not yet been defined. Internally, it works by creating
HeapTypes backed by mutable HeapTypeInfos owned by the TypeBuilder. That lets
the TypeBuilder create temporary Types that can refer to the TypeBuilder-managed
HeapTypes. Those temporary Types can in turn be used to initialize the very
HeapTypes they refer to. Since the TypeBuilder-managed HeapTypes are only valid
for the lifetime of their TypeBuilder, there is a canonicalization step that
converts them into globally interned canonical HeapTypes.
This PR allows HeapTypes to be built in terms of as of yet undefined HeapTypes,
but it currently errors out in the presence of recursive types. Supporting
recursive types will require further work to canonicalize them into finite,
acyclic representations. Currently any attempt to compare, print, or otherwise
manipulate recursive types would infinitely recurse.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
array.new/get/set/len - pretty straightforward after structs and all the
infrastructure for them.
Also fixes validation of the unnecessary heapType param in the
text and binary formats in structs as well as arrays.
Fixes printing of packed types in type names, which emitted i32
for them. That broke when we emitted the same name for an array
of i8 and i32 as in the new testing here.
Also fix a bug in Field::operator< which was wrong for packed
types; again, this was easy to notice with the new testing.
|
| |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
With struct.new read/write support, we can start to do interesting
things! This adds a test of creating a struct and seeing that references
behave like references, that is, if we write to the value X refers to, and
if Y refers to the same thing, when reading from Y's value we see the
change as well.
The test is run through all of -O1, which uncovered a minor issue in
Precompute: We can't try to precompute a reference type, as we can't
replace a reference with a value.
Note btw that the test shows the optimizer properly running
CoalesceLocals on reference types, merging two locals.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This adds rtt.canon and rtt.sub together with RTT type support
that is necessary for them. Together this lets us test roundtripping the
instructions and types.
Also fixes a missing traversal over globals in collectHeapTypes,
which the example from the GC docs requires, as the RTTs are in
globals there.
This does not yet add full interpreter support and other things. It
disables initial contents on GC in the fuzzer, to avoid the fuzzer
breaking.
Renames the binary ID for exnref, which is being removed from
the spec, and which overlaps with the binary ID for rtt.
|
|
|
|
|
|
|
|
|
| |
Interns HeapTypes using the same patterns and utilities already used to intern
Types. This allows HeapTypes to efficiently be compared for equality and hashed,
which may be important for very large struct types in the future. This change
also has the benefit of increasing symmetry between the APIs of Type and
HeapType, which will make the developer experience more consistent. Finally,
this change will make TypeBuilder (#3418) much simpler because it will no longer
have to introduce TypeInfo variants to refer to HeapTypes indirectly.
|
|
|
|
|
|
|
|
|
|
| |
Mostly straightforward after struct.get.
This renames the value field in struct.get to ref. I think this makes
more sense because struct.set has both a reference to a thing, and a
value to set onto that thing. So calling the former ref seems more
consistent, giving us ref, value. This mirrors load/store for example
where we use ptr, value, and ref is playing the role of ptr here
basically.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is the first instruction that uses a GC Struct or Array, so it's where
we start to actually need support in the interpreter for those values, which
is added here.
GC data is modeled as a gcData field on a Literal, which is just a
Literals. That is, both a struct and an array are represented as an
array of values. The type which is alongside would indicate if it's a
struct or an array. Note that the data is referred to using a shared_ptr
so it should "just work", but we'll only be able to really test that once we
add struct.new and so can verify that references are by reference and
not value, etc.
As the first instruction to care about i8/16 types (which are only possible
in a Struct or Array) this adds support for parsing and emitting them.
This PR includes fuzz fixes for some minor things the fuzzer found, including
some bad printing of not having ResultTypeName in necessary places
(found by the text format roundtripping fuzzer).
|
|
|
|
|
|
| |
Compiling scipy requires a `NUM_PARAMS` of at least 61 (!)
https://github.com/iodide-project/pyodide patches emsdk in order
to compile, which this PR can avoid.
|
|
|
|
|
|
| |
This adds support in the text and binary format handling, which allows us
to have a full test of reading and writing the types.
This also adds a "name" field to struct fields, which the text format supports.
|
|
|
|
|
|
|
|
|
|
|
| |
With PIC + threads the offset of passive segments is not constant but
relative to `__memory_base`.
When trying to find passive segment offset based on the target of the
`memory.init` instruction we need to consider this possibility as well as
the regular constant one.
For the llvm side of this that generates the calls to memory.init
see: https://reviews.llvm.org/D92620
|
| |
|
|
|
|
|
|
| |
This is shorter by using ChildIterator, so it's more general. The greater generality
should not be noticeable atm, but it will allow this pass to just work on GC
instructions once we have them (without this, in my testing the optimizer does not
do very well on GC).
|
|
|
|
|
|
| |
In relocable code (MAIN/SIDE modules) we use the start function to run
`__wasm_init_memory` which loads the data segments into place. We
can't call get_sbkr pointer during that function because the sbrk
pointer itself lives in static data segment.
|
|
|
|
|
|
|
|
|
| |
This will allow writing GC types in the future, which are non-signature
heap types.
To allow this PR to work, it adds operator< for HeapType so that it
can be used in the data structures that collect uses.
Drive-by fix of a weird hack with sending a Name* in Print.
|
|
|
|
| |
This name is more descriptive and paves the way toward interning HeapTypes as
well, with an analogous BasicHeapType.
|
|
|
|
|
|
|
|
|
| |
I am starting to write lines like
curr->value->type.getHeapType().getStruct().fields[curr->index].type
and it scares me to think that there may be copies going on there.
The issue is that Types are interned, but HeapTypes and their components
are not.
|
|
|
|
|
| |
See https://reviews.llvm.org/D91803 - there are now -1 or -2 in places that
mark something that the linker removed as not existing/not relevant. We should
ignore those like we ignore 0s there.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The code there looks for a "sign-extend": (x << a) >> b where the
right shift is signed. If a = b = 24 for example then that is a sign
extend of an 8-bit value (it works by shifting the 8-bit value's sign bit
to the position of the 32-bit value's sign bit, then shifting all the way
back, which fills everything above 8 bits with the sign bit). The tricky
thing is that in some cases we can handle a != b - but we forgot a
place to check that. Specifically, a repeated sign-extend is not
necessary, but if the outer one has extra shifts, we can't do it.
This is annoyingly complex code, but for purposes of reviewing this
PR, you can see (unless I messed up) that the only change is to
ensure that when we look for a repeated sign extend, then we
only optimize that case when there are no extra shifts. And a
repeated sign-extend is obviously ok to remove,
(((x << a) >> a) << a) >> a => (x << a) >> a
This is an ancient bug, showing how hard it can be to find certain
patterns either by fuzzing or in the real world...
Fixes #3362
|
|
|
|
| |
unreachable (#3413)
|
|
|
|
|
|
|
|
|
|
| |
Defined types in wasm are really one of the "heap types": a signature type, or
(with GC) a struct or an array type. This refactors the binary and text parsers
to load the defined types into an array of heap types, so that we can start to
parse GC types. This replaces the existing array of signature types (which
could not support a struct or an array).
Locally this PR can parse and print as text simple GC types. For that it was
necessary to also fix Type::getFeatures for GC.
|
|
|
|
|
|
|
|
|
|
|
| |
Calculate a checksum of the original uninstrumented module and emit it as part
of the profile data. When reading the profile, compare the checksum it contains
to the checksum of the module that is being split. Error out if the module being
split is not the same as the module that was originally instrumented.
Also fixes a bug in how the profile data was being read. When `char` is signed,
bytes read from the profile were being incorrectly sign extended. We had not
noticed this before because the profiles we have tested have contained only
small-valued counts.
|
|
|
|
|
|
|
|
|
|
| |
Extend the splitting logic to handle splitting modules with a single table
segment with a non-const offset. In this situation the placeholder function
names are interpreted as offsets from the table base global rather than absolute
indices into the table. Since addition is not allowed in segment offset
expressions, the secondary module's segment must start at the same place as the
first table's segment. That means that some primary functions must be duplicated
in the secondary segment to fill any gaps. They are exported and imported as
necessary.
|
|
|
|
|
|
|
|
|
|
|
|
| |
For a nested type, we used to print e.g.
(param $x (ref (func (param i32))))
Instead of expanding the full type inline, which can get long for
a deeply nested type, print a name when running the Print pass.
In this example that would be something like
(param $x (ref $i32_=>_none))
|
|
|
|
| |
values (#3399)
|
|
|
|
| |
Use matchers and more descriptive variable names to clarify the intent of the
functions for finding and inspecting sign extension patterns.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
bugs (#3401)
* Count signatures in tuple locals.
* Count nested signature types (confirming @aheejin was right, that was missing).
* Inlining was using the wrong type.
* OptimizeInstructions should return -1 for unhandled types, not error.
* The fuzzer should check for ref types as well, not just typed function references,
similar to what GC does.
* The fuzzer now creates a function if it has no other option for creating a constant
expression of a function type, then does a ref.func of that.
* Handle unreachability in call_ref binary reading.
* S-expression parsing fixes in more places, and add a tiny fuzzer for it.
* Switch fuzzer test to just have the metrics, and not print all the fuzz output which
changes a lot. Also fix noprint handling which only worked on binaries before.
* Fix Properties::getLiteral() to use the specific function type properly, and make
Literal's function constructor require that, to prevent future bugs.
* Turn all input types into nullable types, for now.
|
|
|
|
|
|
|
| |
Although there is only one "type store" right now, a subsequent PR will add a
new "TypeBuilder" class that manages its own universe of temporary types. Rather
than duplicate all the logic behind type creation and canonicalization, it makes
more sense to encapsulate that logic in a class that TypeBuilder will be able to
reuse.
|
|
|
|
|
|
| |
Read the profiles produced by wasm-split's instrumentation to guide splitting.
In this initial implementation, all functions that the profile shows to have
been called are kept in the initial module. In the future, users may be able to
tune this so that functions that are run later will still be split out.
|
|
|
|
|
|
|
|
| |
Includes minimal support in various passes. Also includes actual optimization
work in Directize, which was easy to add.
Almost has fuzzer support, but the actual makeCallRef is just a stub so far.
Includes s-parser support for parsing typed function references types.
|
|
|
|
| |
These can be simply raw pointers, given that they are stored in modules
using `unique_ptr`s.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
types (#3388)
This adds the new feature and starts to use the new types where relevant. We
use them even without the feature being enabled, as we don't know the features
during wasm loading - but the hope is that given the type is a subtype, it should
all work out. In practice, if you print out the internal type you may see a typed
function reference-specific type for a ref.func for example, instead of a generic
funcref, but it should not affect anything else.
This PR does not support non-nullable types, that is, everything is nullable
for now. As suggested by @tlively this is simpler for now and leaves nullability
for later work (which will apparently require let or something else, and many
passes may need to be changed).
To allow this PR to work, we need to provide a type on creating a RefFunc. The
wasm-builder.h internal API is updated for this, as are the C and JS APIs,
which are breaking changes. cc @dcodeIO
We must also write and read function types properly. This PR improves
collectSignatures to find all the types, and also to sort them by the
dependencies between them (as we can't emit X in the binary if it depends
on Y, and Y has not been emitted - we need to give Y's index). This sorting
ends up changing a few test outputs.
InstrumentLocals support for printing function types that are not funcref
is disabled for now, until we figure out how to make that work and/or
decide if it's important enough to work on.
The fuzzer has various fixes to emit valid types for things (mostly
whitespace there). Also two drive-by fixes to call makeTrivial where it
should be (when we fail to create a specific node, we can't just try to make
another node, in theory it could infinitely recurse).
Binary writing changes here to replace calls to a standalone function to
write out a type with one that is called on the binary writer object itself,
which maintains a mapping of type indexes (getFunctionSignatureByIndex).
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Implement an instrumentation pass that records the timestamp at which each
defined function is first called. Timestamps are not actual time, but rather
snapshots of a monotonically increasing counter. The instrumentation exports a
function that the embedder can call to dump the profile data into a memory
buffer at a given offset and size. The function returns the total size of the
profile data so the embedder can know how much to read out of the buffer or how
much it needs to grow the buffer.
Parsing and using the profile is left as future work, as is recording a hash of
the input file that will be used to guard against accidentally instrumenting one
module and trying to use the resulting profile to split a different module.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Implement an initial version of the wasm-split tool, which splits modules into a
primary module and a secondary module that can be instantiated after the primary
module. Eventually, this tool will be able to not only split modules, but also
instrument modules to collect profiles that will be able to guide later
splitting. In this initial version, however, wasm-split can neither perform
instrumentation nor consume any kind of profile data.
Despite those shortcomings, this initial version of the tool is already able to
perform module splitting according to function lists manually provided by the
user via the command line. Follow-up PRs will implement the stubbed out
instrumentation and profile consumption functionality.
|
|
|
|
|
|
|
|
|
| |
When Functions, Globals, Events, and Exports are added to a module, if they are
not already in std::unique_ptrs, they are wrapped in a new std::unique_ptr owned
by the Module. This adds an extra layer of indirection when accessing those
elements that can be avoided by allocating those elements as std::unique_ptrs.
This PR updates wasm-builder to allocate module elements via std::make_unique
rather than `new`. In the future, we should remove the raw pointer versions of
Module::add* to encourage using std::unique_ptrs more broadly.
|
|
|
|
|
|
|
|
|
|
|
| |
We did not really model the effects of unreachable properly before. It
always traps, so it's not an implicit trap, but we didn't do anything but mark
it as "branches out", which is not really enough, as while yes it does
branch inside the current function, it also traps which is noticeable outside.
To fix that, add a trap effect to track this. implicitTrap will set trap as well,
automatically, if we do not ignore implicit traps, so it is enough to check just
that (unless one cares about the difference between implicit and explicit
ones).
|
|
|
|
|
|
|
|
|
|
|
| |
If we take a reference of a function, it is dangerous to change the function's
type (which removing dead arguments does), as that would be an observable
different from the outside - the type changes, and some params are now ignored,
and others are reordered.
In theory we could find out if the reference does not escape, but that's not
trivial.
Related to #3378 but not quite the same.
|
|
|
|
|
|
|
|
| |
Call isFunction to check for a general function type instead of just
a funcref, in places where we care about both, and some other minor
miscellaneous typing fixes in preparation for typed function references
(this will be tested fully at that time).
Change is mostly whitespace.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
(#3385)
A user of EffectAnalyzer could call walk or visit, to walk the entire
input or just visit the node without chlidren. But this was unsafe - we just
exposed the Walker/Visitor API here, and did not ensure that users did
the stuff in analyze which does a little stuff before and after. In fact
Vacuum got this wrong.
To avoid that, move all the internals to an internal class. The external
API now only allows the caller to call walk or visit, and both are safe.
The change here is mostly whitespace + adding parent. prefixes.
This is NFC except for fixing possible Vacuum issues (which I am not sure
could happen in practice or not).
|
|
|
|
| |
(#3383)
|
|
|
|
|
|
|
|
| |
The new writesGlobalState has a name that more clearly indicates what it
actually does: affect global state (that is, memory, globals, the table, etc.).
This removes throw from there, and handles it directly in the single caller
of the method, the licm pass. For simplicity, disallow exceptions in that
pass, leaving it for future work.
|
|
|
|
|
|
|
| |
There are several issues here that we can't fully handle, see #3378, but basically
we are comparing results between two separate wasm modules (and
a separate instance of each) - we can't really identify an identical
reference between such things. We can only compare things structurally,
for which we compare the types.
|
|
|
|
|
|
| |
I happened to notice that this was the one place that calls ReFinalize
without the module. That could error if the module is actually needed,
which the pass might use, based on the code (but rare enough that it's
never been an issue I guess).
|
|
|
|
|
|
|
|
| |
cost.h now requires costs be defined, or it halts at runtime. But the fuzzer
already emits those two, so define them to unbreak the fuzzer.
Both access a reference, so they should be at least as costly as a load.
I31New also allocates, which is very cheap on a generational GC, so do not
add much for that.
|
|
|
|
|
|
| |
Almost NFC, but adds a cost to DataDrop, 5, which tries to reflect
that it is costlier than a call (it calls into VM internals) but also not
much more, as it actually helps perf in the long term, so it should be
preferred where possible.
|
|
|
| |
The vacuum code can be deleted as it is handled by the default anyhow.
|