Introduction
WebAssembly, also known as Wasm, is an open W3C standard that was first released in 2015. Wasm is a new type of code that allows native applications written in languages like C++ to run efficiently and portably in a web browser. The term ‘portably’, in this case, means the code can run within any browser context using standard browser APIs without requiring any recoding. Wasm allows applications to run in a browser at the speed they would run as a standard O/S executable. In this way, browsers can handle a whole new class of applications that would otherwise run so slowly as to be unuseable. Equally important, developers don’t have to write any Wasm code. They deliver code in whatever language is supported by Wasm, it is converted into Wasm’s highly-efficient low-level assembly language, then wrapped in a Javascript wrapper. While today a Wasm module has to be called by a JavaScript function, in the future developers will be able to call Wasm modules just like any Javascript executable module with the command “<script type='module_name'>.
I don’t think it takes much imagination to see why Wasm might be important to the Google Privacy Sandbox. Given you are trying to run a large number of parallel auctions in the browser, and in some cases on servers (e.g. bid scoring) in a Trusted Execution Environment, performance at scale becomes critical. Wasm is a perfect solution to this problem. We are talking about Wasm in the section on browser elements, even though it isn’t an “element” in the browser per se, because it will enter into storage element discussions in the next post.
Wasm can quickly become an extremely technical discussion. We are not going to go there for now. Perhaps later in the series I may write a drill down for those who wish to learn more, but I doubt it. Frankly, I could just tell you that Wasm is a performance-enhancing wrapper around typical code that can then be called as a script in the browser and we could move on without missing too much. But you are a relatively technical business reader, and I assume that you might enjoy learning just a tad more about this relatively powerful browser technology when we talk about its use in later posts.
I will start with a discussion of Wasm, what it is, and how it works. Then we’ll discuss how it is being used by FLEDGE Origin Trial participants to implement aspects of the Privacy Sandbox
The Goals of WebAssembly (Wasm)
According to the Mozilla web docs, there were four main design goals for Wasm:
- Be fast, efficient, and portable. WebAssembly code can be executed at near-native speed across different platforms by taking advantage of common hardware capabilities.
- Be readable and debuggable. WebAssembly is a low-level assembly language, but it does have a human-readable text format (the specification for which is still being finalized) that allows code to be written, viewed, and debugged by hand.
- Keep secure. WebAssembly is specified to be run in a safe, sandboxed execution environment. Like other web code, it will enforce the browser's same-origin and permissions policies.
- Don't break the web. WebAssembly is designed so that it plays nicely with other web technologies and maintains backwards compatibility.
The Browser as Virtual Machine
If you think about it a certain way, the browser is nothing more than a sandboxed virtual machine that runs Javascript code and can call a series of APIs. The virtual machine runs CSS and HTML and calls Javascript modules to control aspects of the virtual machine (e.g. the Domain Object Model) or to add functionality to run within the browser.
The problem with Javascript, as with any scripting language, is that it runs more slowly than a typical native application. That level of performance is fine for a wide range of browser-based applications. However there are a variety of use cases and applications where Javascript is not fast enough to be practical. These include games, 3D rendering, VR and augmented reality, and browser-based VPNs, amongst others. Moreover, even if it can run fast enough, the cost of downloading, parsing, and compiling JavaScript can make it prohibitive to use in mobile or other resource-constrained platforms - for example automobiles.
WebAssembly is designed to be a complement to JavaScript for those use cases where JavaScript’s lack of efficiency is problematic. It is a low-level, assembly-like language with a compact binary format. It provides near-native performance. It also provides languages that have low-level memory models, such as C++ and Rust, with a compilation target so that they can run on the web.
There are many great design features of Wasm, but the one that we care about most for this discussion is that Wasm does not require the developer to rewrite their code in a new language. Instead, it takes existing code, converts it to a very fast executing, assembly-level code-like format. It then wraps that fast-running code in a Javascript wrapper so that its functions can be accessed just like for any other JavaScript module. Figure 1 shows an example of how this works for C++:
Figure 1 - The Process By Which C++ Is Converted to a Wasm Javascript Module
The developer’s original C++ code is run through a Wasm encoder/converter that compiles the C++ into Wasm binary format. For those who are curious, one of the most common tools for this is Emscripten. Once the Wasm module is created, it is then wrapped in some Javascript “glue code” - basically it is called by a JavaScript function - and then runs inside the browser.
We call this approach to architecture a virtual instruction set architecture (virtual ISA). A virtual ISA emulates the instruction set of one processor type by hardware, firmware and/or software running on a different processor type. It can apply emulation to individual programs or on entire virtual machines. Wasm basically emulates the native compiler for C++ to create an equivalent kind of bytecode that can be run within the browser - so it is emulating, for example, the Windows instruction set on an Intel processor but in a way that it can run on the browser “virtual machines” instruction set. That is not necessarily a perfectly accurate technical description, but it gets at the basic concept.
WebAssembly Text Format
Now, if you have ever looked at assembly language (Figure 2) you will see that it is anything but easily comprehensible to the average human. If you understand what you are seeing here to the left of all the semi-colons, 20 points to Gryffindor! In other words, you are not expected to interpret the code, just get a sense of how inhuman it is.
Figure 2 - An Example of Windows Assembly Code for Adding Two to a Prior Number
; Add 2 to a number starting at 0 and store the result in register EAX
mov eax, 0 ; Move the value 0 into register EAX (initial number)
mov ebx, 2 ; Move the value 2 to register EBX (number to add)
add eax, ebx ; Add the value in EBX (2) to EAX (0), storing the result in EAX
; Optional: Print the result using system call (Interrupt 21h)
; This part requires additional code for setting up parameters and handling return values.
; Exit the program
mov eax, 4 ; System call for exiting (INT 21h with AH=4)
xor ebx, ebx ; Set EBX to 0 (optional, some programs expect it)
int 21h ; Interrupt 21h to exit the program
Sometimes developers want to write Wasm code directly (versus compiling code written in another language) or need to examine Wasm code for debugging purposes. In those cases, even though they could read and write assembler, they probably don’t want to because it is a very “wordy”, time-consuming, and painful way to code - even for experienced developers. (Trust me on this one. I did it long ago on Intel 8086 chips and have been grateful that I haven’t had to do it in the 40 years since.). Thus, the standard provides a text-based format that works with text editors and browser developer tools. Figure 3 shows an example of such code that performs a similar task as shown in Figure 2.
Figure 3: A Wasm Text Function Adding Two to a Prior Number
The text- version of the module called “addTwo” is on the left side, and its Wasm representation on the right-hand side. Don’t worry about how the code works or what the words mean. Just note how much more code is required for the WebAssembly-encoded version.
We then wrap Wasm in a very simple JavaScript function and call it. You can see the addTwo module embedded in a JavaScript function on the left-hand side and the output of the function on the right hand side of Figure 4.
Figure 4 - Output of the Wasm Function in Five Lines of JavaScript Code
Thus Wasm coding can be very efficient, even when it has to be done by hand. It starts to feel a lot like coding in JavaScript, but it results in substantially higher-performing code.
WASM in the Privacy Sandbox
As mentioned earlier in the post, it is easy to imagine why Wasm is an important technology leveraged by the Privacy Sandbox. It allows code to run at native speeds in the browser for things like adding interest groups, running auctions, and bidding.
Let’s take interest groups in the Protected Audience API. These were introduced in the post on navigators, promises, and beacons. Interest groups, as a reminder, are audiences that are stored in the browser’s partitioned storage as a SQLite file, keyed by owner and origin - so they are partitioned.
What is important about interest groups for this discussion is that in the definition of interest groups in the Protected Audience explainer there is a field (actually, to be technically accurate, a key in a key-value pair) in the associated metadata called biddingWasmHelperURL. This metadata field is actually a helper for a key function in the bidding process called, practically enough, generateBid. The biddingWasmHelperURL field allows the owner of the interest group to call computationally-expensive subroutines in WebAssembly rather than embedding that code in its standard bidding logic. That way advertisers or DSPs who want to bid can run high-overhead processes in the browser fast enough to respond before the bid request times out. An example would be using sophisticated logic to evaluate bid requests to determine if they wish to participate in a specific auction.
A second reason for using Wasm is the way bidding is implemented in Chrome on a mobile platform. In mobile all Protected Audience JavaScript operations use a single thread. Once a process is assigned, Chrome creates something called an executor for the script, which starts loading the JavaScript and Wasm URLs. Because bidders share processes based on their origin, all of a single buyer origin’s interest groups are assigned an executor (newly created or reused) at once.
What that means is all the buyer’s bidding logic for every interest group in a specific auction loads at the same time, meaning this code has to potentially run in parallel if each interest group has different bidding logic. Thus this is another instance where computational performance, and thus Wasm, become critical.
Wasm is used throughout the Privacy Sandbox for similar applications. For example, here is an example from the Attribution Reporting API repository that is part of code that is validating event-level reporting:
Figure 5: Sample Code that Calls Wasm User Defined Functions in the Attribution Reporting API
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
"integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
"dev": true,
"dependencies": { "@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/helper-wasm-section": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-opt": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6",
"@webassemblyjs/wast-printer": "1.11.6" }
It’s not important to know what the code is doing - I just want you to see the extent to which Wasm is drawn upon for this one module. You can look throughout the code in the repository and see many more examples of this.
One last example - this time on the service side of Protected Audiences. I talked about the Key Management Services in one of my earliest posts on Sandbox architecture. Each key management service hosts a key-management server in a Trusted Execution Environment (TEE). These servers store and manage encryption keys for each origin (i.e. advertiser, publisher, etc) in a separate, secure partition (there’s that term again, but used in a new context). This is because encryption keys are highly sensitive and if accessed by an evil actor could do untold damage to the origin whose keys have been accessed. With multiple parties running multiple bids and auctions on the server, there could be hundreds, maybe thousands, of concurrent calls from each partition for the code to generate, update, or retrieve a particular origin’s encryption keys. It is also possible that each partition runs slightly different versions of the code based on their business requirements .
As a result, the Protected Auction Services API, which specifies much of the server side of the Protected Audiences API, uses what are called user-defined functions to handle these capabilities. User defined functions can be written in JavaScript or Wasm. Whether Wasm is selected depends on the performance needs of the origin, which in turn depends on their hardware configuration and their scale of business, among other things. Figure 6 shows an example of code from the Protected Auction Services API repository. The code creates a Wasm function that generates a UDF delta file (don’t worry about what that is right now).
Figure 6 - Example of Wasm Text-Based Code Used in the Protected Auction Services API
def cc_inline_wasm_udf_delta(
name,
srcs,
custom_udf_js,
custom_udf_js_handler = "HandleRequest",
output_file_name = "DELTA_0000000000000005",
logical_commit_time = None,
udf_tool = "//tools/udf/udf_generator:udf_delta_file_generator",
deps = [],
tags = ["manual"],
linkopts = []):
"""Generate a JS + inline WASM UDF delta file and put it under dist/ directory
Performs the following steps:
1. Takes a C++ source file and uses emscripten to compile it to inline WASM + JS.
2. The generated JS file is then prepended to the custom udf JS.
3. The final JS file is used to generate a UDF delta file.
We’ll leave it there for now.
Next Up: The Privacy Sandbox Changes to Browser Storage