Repository navigation
Run the WebAssembly GC proposal - #497
Draft
kateinoigakukun wants to merge 1 commit into
Draft
kateinoigakukun wants to merge 1 commit into
kateinoigakukun wants to merge 1 commit into
Conversation
Executes GC modules end to end: the text format of the GC instructions, canonical types by recursion group, i31 and the extern/any conversions, structs and arrays in a per-store heap, casts, exceptions kept in the same heap, stack maps for the roots in Wasm frames, a mark-compact collector, and a host API for creating and reading objects. Each engine owns a type registry of canonical recursion groups. A canonical ID carries its type's kind, so subtyping against an abstract type needs no lookup, and concrete-against-concrete checks use a supertype display. The collector runs only when an allocation does not fit, so the only safepoints are calls and allocating instructions. The translator records which frame slots may hold an object at each of them, which keeps modules without GC on their existing code paths. The objects the host holds are reached through root handles released by Store.withRootScope, so the collector can move every object.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runs the GC proposal end to end, on top of the type decoding from #489. It's opt-in through
WasmFeatureSet.gc(--feature gcon the command line). All 23 GC files at the top level ofVendor/testsuitepass, andUnsupportedSpectestshas no GC entries left. The GC files andExtraSuite/gcalso pass in a stress mode where every allocation collects and moves every live object.Note
This is the whole feature as one draft PR, so it can be read in one place. It was developed as nine stages (text format → types → i31 → heap → casts → exnref → stack maps → collector → host API), and I can split it back into a stack of PRs in that order for review.
What's included
Types
Engineowns aTypeRegistryof canonical recursion groups. It replaces the function-type interner, socall_indirect, function entities and casts all use the same canonical IDs.Values
(value << 1) | 1, and objects are 8-byte aligned offsets into the heap.externrefis tagged10in its low two bits. This keeps a table element oneInt, which is 32 bits on embedded targets. Host integers lose two bits (AnyRef.maxHostValue).Referencegains.any(AnyRef?)and.externalized(AnyRef).AnyRefis in WasmTypes, next toReference.Heap
Storehas one growable region, created on the first allocation.funcrefpointer or an internalized host value, so 4 bytes isn't enough.ResourceLimiter.limitGCHeapGrowth(default implementation provided) up toEngineConfiguration.maxGCHeapSize(1 GiB by default). Running out traps with "out of GC heap memory".exnref) are heap objects now. Before, anexnrefthat escaped its handler could dangle.Stack maps and the collector
EngineConfiguration.gcStressMode, package-visible) collects at every allocation. It also copies the live objects into a new region and poisons the old one, so a root the stack maps miss fails a test.SpectestTests.runWithGCStressruns this in the normal test suite.catch_refhandler) and allocating in a constant expression never collect: they grow the heap or trap. No stack map describes the frames at those points.Host API
Store.withRootScopereleases the handles made inside it in LIFO order, as in wasmtime. The handles for a host function's parameters are released when it returns, and using a released handle throws.Engine.register(structType:)/register(arrayType:)returnGCStructType/GCArrayType. These are final types in a group of their own, so they are the same type as a module's matching(type (struct ...)). Their fields can't refer to concrete types yet.StructRefandArrayRefcreate objects and read and write fields.AnyRef.asStruct(in:)/asArray(in:)inspect objects that come from Wasm.Text format
Utilities/Instructions.jsonand the VM instruction list are extended. The new VM instructions go at the end, so existing opcode numbers don't change.Testing
swift test --traits FileSystem,MultiThread,ComponentModel,WasmDebuggingSupport,Disassembler: 635 tests pass. GC files are compared against wasm-tools inEncoderTests, because wast2json can't parse them.swift run --package-path Utilities WasmKitDevUtils vmgen/wasmgenand./Utilities/format.pyleave no diff.gc_disabled.wast: the feature gate.gc/typed_references.wastgc/heap_limit.wast: growth, the limit and the trap.gc/exnref_escape.wast: anexnrefthat outlives its handler.gc/collector.wast: objects that survive collections while held by locals, operands, globals, tables and other objects.GCHostAPITests.swiftcovers the host API, which.wastcan't reach.Performance
Modules without GC don't slow down. The numbers are wasmi-benchmarks and CoreMark, comparing #489's tip with this branch. Each benchmark's time is the best of N interleaved runs; the ratio is after ÷ before (lower is faster) and the overall figure is the geometric mean. The builds are release with
--omit-frame-pointers.No single benchmark moves by more than noise consistently. json-parse came out about 6% slower on both machines, but compression moved by a similar amount the other way, and I couldn't reproduce json-parse as a stable regression.
Code size of
wasmkit-cli(release, stripped):Most of it is in WasmKit (+16%), and the rest in WAT (+4–6%) and WasmParser (+6–7%). GC is always compiled in. A package trait to leave it out would get most of this back.
Benchmarks/gc/adds binary-trees and a deep-stack workload written in WAT.Not included
store.gc(), owned (non-scoped) roots, and typed handles such asEqRefandI31.catch_refhandler or a constant expression.