All right, hello everyone, my name is Jonas, and I work on the bugging at Apple, and today
I want to talk about the Webbot assembly the bugging in all of the B.
So I'll do my best, the speaker also kind of echoes.
So this work was primarily motivated by a Swift, and targeting Webbot assembly from Swift has
come a long way.
It started originally as a community project, Akio created WAKIP as an open source project
in 2018, and it was the first Webbot assembly run time written in Swift targeting Swift.
And then a few years later, Max, who may or may not be in the room, there he is, he took
over, I mean, the ownership of the project, and he renamed it to WasmKIP.
After that, Yuda, he interned that Apple, and together with Max, they got 100% specta scofferage.
For Webbot assembly with WasmKIP.
And then with the release of Swift 6.0 in 2024, and that at Swift targeting Webb assembly became an experimental
feature, and also in Swift CI, we adopted WasmKIP to target targeting, so the compiler support.
And then last year in 2025, all their hard work culminated in Webb assembly being officially
supported as of Swift 6.2, and WasmKIP now ships as part of the tool chain.
And of course, all of this is open source, I know the kit at the end might be kind of confusing.
The run time is developed under the Swift Wasm organization on GitHub, and of course, the compiler
support is just in upstream Swift.
And if you want to learn more, check out SwiftWasm.org.
So our goal for Swift Compile to Webb assembly is to provide a first class debugging experience,
and that means full source level debugging, where you can set breakpoints, step in, and
over Swift code, and of course, show your variables.
And so in order to achieve that, the debugger needs to know about the Swift programming language,
which means that there's two approaches.
One, we could teach existing tools to debug Webb assembly and teach it about Swift, or we could
teach LDB, which already knows about Swift, about Webb assembly.
So maybe the first approach sounds simpler on the surface, but it really isn't.
The debugging Swift is far from trivial, and LDB already has years and years of investments
in that space.
On top of that, if we add Webb assembly support to LDB, that doesn't just benefit Swift,
but pretty much every language that is supported by all the game.
And that's why we thought that was the better approach.
So before we commit to any approach, I do want to compare a few options that exist for
the debugging, or existed before I started all this.
And we identified three main approaches, each with their own pros and cons, and an interesting
observation here is that they all use LDB one way or another.
If you've played around with Webb assembly, you're probably already familiar with WasmTime.
It is the reference implementation of the Webb assembly runtime, and it's developed by the
bytecode alliance.
It uses just in time compilation to generate native machine code with the debugging
info.
And so the debugging in WasmTime means debugging the runtime, and the jitter code just runs
as part of that same process.
And so you can debug WasmTime this way with either GDB or LDB.
And to the debugger, it just looks like you're debugging native code.
And so what's neat about this approach is that like we're like no changes necessary,
LDB doesn't need to know about Webb assembly at all.
The trade-off on the other hand is that you are debugging the runtime, and so it can make
it kind of hard to distinguish between the runtime code and like decoded you're trying
to debug.
For example, if you're looking at a backtrace, you're going to get all these frames coming
from the runtime that you have to mentally filter out.
Chrome takes a different approach.
They have a delft tools extension, which adds support for debugging C and C++ code,
right from within the browser's built-in developer tools.
And this extension also uses LDB under the hood, and it uses that to parse dwarf and create
types, but pretty much everything else is done by the browser itself.
And so as a developer, that's nice because you can use the same tools for debugging JavaScript
as you do for a web assembly.
The downside is for Swift supporter and the other language, you need to extend the browser
and teach it about it, which is one of the things we wanted to avoid.
The third approach is the web assembly micro runtime, sometimes abbreviated as whammer, and
it's also developed by the bytecode alliance, and it's a lightweight standalone runtime
targeting embedded and IoT applications.
And this has a small debug server that talks the GDB remote protocol, which is an industry
standard that's supported by both GDB and LDB, the spitany.
It's also the approach we started pursuing, and the debug stop in a micro runtime meant
that we had something that already existed and we could work with.
So at this point, you're probably curious what this looks like.
So I'm going to jump straight in with a demo and show you what it looks like before
I talk about boring implementation details.
So hopefully, that's not too small, but I realize that I might have underestimated the
size of the screen.
So there's a short recording of Swift, which is compiled to web assembly, and we're using
LDB individual studio code to the bug is.
The code is fairly trivial, especially for the people in the back.
We got a dictionary mapping some fruits to prices, and then we have a kind of contrived function
that just adds entries to the maps.
So I'm going to start playing the demo.
So I'm going to start a break point here, and then I'm going to run already compiled this,
and you can see we got a back trace, exactly as you would expect.
Then we have local variables.
Show you, this is the input to the function, function arguments.
Then I continue, so you're going to see the values changing, because we're in the next
iteration of this function getting called.
And I'm going to continue stepping out of it.
Here I'm just proving that those values look the way you expect, so I continue, or
step out of it, and then here's the fruit prices variable that we can also inspect.
So far, everything just looks like native debugging, so to prove to you that this is
actually debugging web assembly, I'm doing a business assembly here to show you all the instructions.
But in order to get here, we had to build several key pieces.
So let me talk about that next.
One of the key design decisions was using the GDPRot protocol, so I want to start out with
an overview of that architecture.
So in a native debug session, LDB uses a client server architecture, where the client
is LDB itself, and it runs on the host, and then it communicates with a debug stub, which
runs next to the program you're debugging, which we call the inferior.
The stub is a lightweight and tidal binary that directly controls the inferior, and usually
does so with help from the operating system, for example, a mechOS, and Linux that's
going to be P3s.
And also I want to call out that the same client server architecture is used regardless
of whether you're debugging remotely or locally, in the local case, like you know the host
and the target are just the same machine.
The GDPRot protocol is simple, it's well documented, it's widely adopted, and it's
supported by a bunch of tools, including LDB and GDB, and I realized that this year, it
has existed for 40 years, so it has really proven itself.
Which is the reason we want to build on top of that.
So here's what that architecture looks like for WebAssembly.
Here the runtime would implement the GDPRot stub, and how it does that is entirely implementation
defined.
It can different run thanks can choose to optimize for their own constraints.
And then just the normal way LDB will talk using the GBRot mode protocol to the debug stub,
and that way control the WebAssembly inferior.
And the key takeaway here is that this approach provides a standardized way for any debugger
to talk to any WebAssembly runtime that exposes the GDPRot protocol, and the nice thing here
is like LDB doesn't need to know about the implementation of the runtime and the runtime
doesn't need to be aware of that debugger debugging it.
And also WebAssembly is modeled around a stack-based virtual machine, and as we'll see later
that poses some concepts that can directly be translated into concepts in the GDPRot protocol,
and so this requires a handful of extensions that both the runtime and the debugger need to support.
Luckily the protocol was always designed to be extensible, and the majority of the packets are all standard.
So let's take a look at the implementation, and previous to this work LDB already had some
wares in support, in particular for the Chrome Dev tools integration that I mentioned earlier.
It supports reading, wares and binaries, loading them into memory, and then using the dwarf to create types.
And the micro runtime, as a debug stop, was also designed to work with LDB.
The repository contained a patch, which was based on work from follows Severini.
He was the driving force behind some of the earlier existing WebAssembly supporting LDB,
and some of my work is a continuation of some of the PRs he put up there.
And we also made it a goal to remain compatible with the debug stop in the micro runtime.
During this process we encounter some places where we might have taken some small different design decisions.
But nothing that warrants and breaking fatability.
All right, objects files.
So the first challenge here was that LDB's existing objects file wasn't plugin, was rather rudimentary.
To keep things simple, we were looking for a handful of known sections and ignoring everything else.
And we needed to expand this to read arbitrary sections, in particular Swift Castle metadata,
or language-specific metadata sections that we needed to read.
And most of the work was reading the spec, knowing how to parse those data sections.
WebAssembly generally follows the elf model where sections contain segments.
The one interesting thing is that those segments can be active or passive.
And active segments are automatically loaded into memory during module initialization,
and they use what's called an init expression to specify where.
And it's an init expression consists of a series of wasom operations or subset of them,
which meant that we had to implement a tiny wasom interpreter in her objects file plugin,
which was a fun exercise, as you can imagine.
The next challenge was supporting symbols, and for that we needed a symbol table.
But WebAssembly's concept of a symbol table is exclusively used for linking,
and it isn't preserved in the final binary, but wasom does provide something called a name section,
which is one of those new sections we had to parse.
And the name section contains the function names, and an offset, or actually an index
into the function section, and the function section contains an offset into the code section,
together with a size, and I think that's it.
And so we had to combine all of this together, the name, the size, the offset,
and then populate all the B's concept of a symbol table.
And so with that you can now set breakpoints by symbol name,
even if you do not have to worth debugging information.
Now, now we can set, so we have a symbol table, we can set breakpoints,
we can run to them.
The next thing you want to do is be able to see where you are.
So for that we do the BT command.
And so normally LDB would examine the frame pointer register, and then walk the stack.
But doing unwinding depends on concepts that such as registers, and an API defined stack layout,
and these things just don't exist in WebAssembly.
So for native code, LDB has to do the unwinding ourselves,
but we realize that for WebAssembly, we can rely on the runtime,
which already needs to know this information.
And this brings us to our first extension packet,
to the GDB remote protocol.
It's the Q-Wasm call stack packet, and this queries the runtime for a list of program counters,
representing the call stack for the current thread.
LDB then symbolicates in using the information from the symbol table,
or from the dwarf debug info, and then you get the backtrace, as you would expect.
Next up is variables.
For that, we need a little bit of background about how the bugger is used to work
to parse debug information.
If you were in the talk this morning about dwarf 6, you might have already been prepared.
So the bugger uses dwarf location descriptions to find and recover variable values
at runtime, and these things can take several forms.
So they can be empty, in case the value is unavailable,
because the variable has been optimized out.
They can be implicit when the value is known, but there's no runtime representation,
something like a constant.
If a variable is in memory, you have a memory location, and we get an address,
and if it lives in a register, we have a register location, and we get a registered name.
Empty and implicit locations work exactly the same way in WebAssembly as they do in for native code,
but memory and registers be slightly differently.
WebAssembly doesn't have a concept of registers, and so therefore no register location descriptions.
However, WebAssembly has a few other places where it can store values, namely locals,
globals, and an operand stack.
And so to handle those cases, WebAssembly uses something called virtual registers,
which is entirely at the bugging concept, to describe this in dwarf,
and have the bugger query the runtime.
So when a value is stored in WebAssembly, local global or along the operand stack,
if you use a dwarf vendor extension called DWOPWASM location, it takes two arguments,
the first one's specifying which one of these three it is, and the second one's specifying it
index like the first or the second or the third global.
Each register also has its own corresponding GDP remote packet, these are also extensions,
that the bugger then uses when it encounters it to query the runtime and get the value back out.
So let's look at an example for a function argument,
so in native code, depending on the API, you might expect that to be passed in a register and get a register name.
So in WebAssembly, we will get a dwarf location description that uses the DWOPWASM location.
And so in this particular case, the value is stored as a function local at index two,
so the second function local.
So when all of the being encounters this,
parts as the dwarf expression, encounters this, and then it's going to query the runtime with a queue
with local and index two, gets the value back and it shows it to you.
Variables located in memory, behave pretty much the same and can use dwarf standard memory locations,
but WebAssembly's architecture requires a little bit of care.
Specifically, WebAssembly follows a segmented memory model, where code and data
live at separate address spaces, also different modules each have their own address space.
And hello, the B doesn't currently have address space support,
which is something that's come up a few times today, so we needed a creative solution,
and what we did is we used the top 32 bits of a 64-bit address to encode the address space.
We used the first two bits for the type, so like code or memory, and then the remaining
30 bits we used to encode the module. And so this approach works well for 32-bit
wasom, which is still very much the default, but obviously for WebAssembly 64, we'll need all 64 bits
for the address, and we will need to come up with something different. Luckily, this address space
support node gives something that we've been discussing on the forums. I need to hurry up.
Although Swift uses dwarf as it's a debugging format for anything but trivial types,
we also need the Swift Reflection metadata, for example, to resolve the concrete type of
a generic at runtime. And so Swift uses a common library, a lib Swift Reflection,
and it's used at runtime for performing reflection, and it's used by the debugger to generate types.
And so what that means is it's nice to have one implementation, so we don't have to duplicate the code.
What that means is that this library also needs to be able to parse
those sections I mentioned earlier, and so we had to redo some of that work in lib Swift Reflection.
But the thing is that they was pretty much all it took to support Swift, because all the other things
are built on top of the primitives provided by the GDP remote protocol, and we also didn't
need to modify the runtime or anything to make this all work. Finally, we introduced a
WebAssembly platform to all DB, and so when you create a target in all DB with a WebAssembly
triple, this platform will automatically get selected, and so one of the responsibilities of the
platform is launching binaries. And this platform can be configured with a specific runtime,
so that when you load it in all DB, and you type run, it's automatically going to launch that
under that runtime, connected to GDP, and make it look like you're just debugging something natively.
And once configured, like this thing disappears, and you get the user experience that you're used to.
So with all that, I'm happy to say that I think we delivered on our original goal,
and all the B now have first class debugging support for WebAssembly,
and not just for Swift, but for any language that is supported. We also succeeded in building a
solution that's not tied to a particular runtime. We have already three runtime today that support
debugging with all the B this way, so besides the micro runtime and Wasm kit, there's also support
in WebKit's JavaScript core engine. And so all these runtime support, the protocol extensions
that I've discussed earlier, they are formally documented on the LDB website, and I hope to see
more the Buggers and run times adopt them. So it's next, the immediate priority for us is to
extend our test suite to build all our test binaries for WebAssembly, and then run them under a runtime.
That's going to allow us to reuse our thousands of existing tests to test our WebAssembly support,
and hopefully this will also help uncover bugs in a different run times GDP stops.
And beyond that, we'll want to support more Swift features as they make their way over to WebAssembly,
and then add our spaces is something we'll need to do in order to get Wasm 64 support going.
I want to thank everyone that made it possible to make this happen, Apollo for his work on WebAssembly
and LDB, Adrian Prantel for his work on the Swift parts, David Spickett for viewing my PRs,
Lex and Yuda for their work on Wasm kit, and Yija show and Mark on the WebKit team for
adding this to JavaScript core. Thank you. I think there should be a little bit of time for questions.