Web Assembly (Wasm)

Read & Write to Shared Memory

Jem Young

Jem Young

Web Assembly (Wasm)

Check out a free preview of the full Web Assembly (Wasm) course

The "Read & Write to Shared Memory" Lesson is part of the full, Web Assembly (Wasm) course featured in this preview video. Here's what you'd learn in this lesson:

Jem demonstrates how to use AssemblyScript to write directly to memory and access/update the memory from JavaScript. When the memory buffer is accessed from JavaScript, it is converted to a TypedArray and accessed through the location's index. JavaScript can use this same TypedArray to write values back into memory.


Transcript from the "Read & Write to Shared Memory" Lesson

>> There's different ways of creating memory in Web Assembly, but if we wanted to, actually yeah, I think this the last exercise we're gonna do. Cuz [LAUGH] I can tell it's now we're starting to get really deep into and we're talking about unsigned 8 bit memory arrays and things like that.

And this an intro course, I don't wanna go too far in that direction, I just wanna give you a taste just. So when you come across it again, you'll be like, yeah, I remember Jim said that array buffer is actually just ones and zeros and we have to give it a view, that's what a typed array is called a view into it.

So let's go and do that, let's create some memory, let's create a memory object in Web Assembly, and then let's pass memory back and forth. So this is a demo of passing memory to WebAssembly, and so whether someone's gonna read from JavaScript memory, and then JavaScript gonna read from the WebAssembly memory.

Okay, I am going to start over, I don't need this, And gonna check my notes so I get this right. So, we're gonna jump back to our index.ts, Close that, that is in the assembly directory index.ts and you can keep these if you want but I'm gonna clear them out, just to keep things simple.

So the first thing we wanna do is we wanna set the memory, memory because it's fixed and it's linear it doesn't grow automatically. You have to tell the computer, hey, I want from this address to this address and I want that reserved for my space. And I'm gonna do whatever I want within that space, but I don't want anybody else to overwrite that memory, and I want you to know it's allocated to me.

But we have to tell the computer exactly how much memory we want, so we use memory.grow. And we're gonna give it 2 pages, and a page is 64 kilobytes of information, it's just one of the many ways of referring to memory. And we're gonna give it 2 pages worth of information, so we're allocating 128 kilobytes of memory to our Web Assembly process.

So now, and this is pretty cool, we're gonna write directly to memory, there's no fancy utility or anything like that, we're writing directly to memory now. So we're gonna say store, And we're gonna say store at index 0, that is the beginning of our memory after the headers and the offset, things like that.

We're gonna store the number 21, preferred save or if we try to compile it now it's gonna throw an error, because we said we wanna store the number 21 but we didn't say what we want it to look like. Remember, we're dealing with ones and zeros, so we have to be very specific.

So I wanna store this number as an unsigned 8 bit integer, so I'm gonna say store u8. So now we're storing 21 index 0 into our memory. And let's store another number, let's store another unsigned 8 bit integer. And we'll store that index 1, I'm gonna store 99, there.

Okay, I [LAUGH] think this is so cool, I think we are now writing ones and zeros directly into the RAM of our computer or the registry depends on where it's going. I think that's powerful, there's no abstraction on top of this, this is straight, low level programming, which is hopefully what you're here for, if not, I'm excited for you that we get to do this.

All right, next up, we want to export a function that reads memory, so I'm gonna say, export readMemory. And that's gonna to take a number and that's gonna be an unsigned or it's gonna be a 32 bit integer, call it a function. And it's gonna return a 32 bit integer that is read from memory, so i32.

Okay, so here we're gonna use the load function, so store writes directly into memory and verses load is gonna read out from memory. And again, we have to say exactly how we're reading it and what we're reading. So in this case, we're reading an unsigned 8 bit integer, And I wanna load the index that is passed in.

And I'm just gonna say return because out of habit, I know it's implied but it's gonna return anyways. Okay, everybody with me so far, [LAUGH] this is pretty tactical, I know it can be a little confusing for people, especially for I'll say for me, who doesn't work directly in languages like C we have to allocate the allocate memory.

So, storing things in memory, reading them from memory and being specific about the type, this is all brand new stuff to me as well or it was at a time. So, that's why we're spending a little bit of time on it but we're not going too deep into it, cuz, as we'll see in the later more advanced examples, things will get complicated very quickly.

All right, so now let's access the function we just wrote, well, first, let's build it, let's make sure I do everything right. No whammies, no whammies, no whammies, no whammies okay, built so, the code I wrote is valid, that's always helpful. I do like that about typed languages is it catches a lot of mistakes ahead of time.

And now, let's pull out the memory, so one of the things export it is we exported the function called readMemory, remember the one we just wrote. And then we also export it, what exports by default is the memory itself, and this is in the form of a shared array buffer.

And I will actually pause or not pause there, but I'll take a break for a second and that's instance to come back to array buffers. We talked about them briefly but there are two types of array buffers that we care about, and remember, array buffer is just a long string of raw binary data.

There are array buffers, which are isolated to the process itself and then there's the shared array buffer. And shared array buffer is a buffer that is accessible by two different processes. So that is in this case, a shared array buffer is a memory space that both the WebAssembly and JavaScript know the addresses, and they can reach in and they can both read and write to each other.

Funny enough, at underneath all of the craft all the helpers and everything we're doing, that is what's actually happening. That's how we're passing data back and forth is via writing of memory and reading the memory. Fortunately, things like assembly script do all that work for us, so we don't have to worry about it.

But if we were writing pure on adulterated vanilla web assembly, we'd have to handle all this ourselves, as in how to pass data back and forth. But fortunately, the JavaScript API and assembly script loader, do all this work for us. Okay, so we learned what shared array buffers are, you've probably seen them before, anybody remember, this is a bit of trivia.

But anybody remember the, was it spectre and meltdown over those big things from before? All right, so part of it, and I will explain this naively cuz I am not a low level systems engineer, but part of what was happening was a share rate was a timing attack.

So things are memory safe right now, that's in a process can read from another process memory. That's part of what operating systems do is they guarantee this memory security. We want this because, let's say, I don't know, you're streaming are your saving confidential medical information to the hard disk, there's a process that's doing that if we're just taking this as an example.

You wouldn't want another process to be able to read into that memory and extract that information to the side, that is not what we want computers to do. And if we didn't have memory security, we would have to run everything linearly. We'd have to say, you execute and then you execute and then you execute just to make sure that there's no memory crossovers.

So what happened was the shared array buffer is a way of sharing memory. And the bug was other processes could reach in and read that shared array buffer because it wasn't being used properly, it wasn't implemented properly. And there's such thing known as a timing attack, and a timing attack is when a process is writing to memory.

And because the operating systems are generally pretty good, other processes can't tell what's doing. But what it can do, what the other hostile process can do, is it by the amount of time it takes to write into memory. We know that a bigger number is gonna take a certain amount of time and a smaller number is gonna take less time.

And through a lot of complex engineering, you can actually figure out what's being written by the sheer amount of time it takes for the process to start and finish, and that's what is one of the things that was happening. So they actually disabled shared array buffer for a while in browsers until they fix that, and they also reduce the specificity on performance that now.

Cuz performance that now was, I think it got to microsecond precision for it in terms of timing things. And that was one of the things that allowed the timing attacks was when you can get to that level of precision, as in, you know exactly how long it takes the CPU to write to memory.

We're talking about microseconds or pico seconds, you can figure out what's going on, so they disabled a lot of that for a while. Totally an aside, not including the course that's just the, so you can nerd out with me next time we all hang out in person [LAUGH] will nerd out about shared array buffers.

Okay, [LAUGH] back to the cours, itself, so now what we need to do is we want to read from the memory. And then we wanna write to memory and read from memory as an example of JavaScript writing to WebAssembly memory, and WebAssembly writing to JavaScript memory. So in our index.html file, we're back, no more rants from me, I promise, maybe one more, but for now, no more rants.

One of the things that the loader and WebAssembly self exports is the memory, so this is a shared array buffer, the one we just talked about. So, now let's read from the memory array, but the first thing we need to do is we need to cast the memory into a view that makes sense to us now we can decode it.

So I'm gonna say memoryArray, Equals new unsigned integer array. Because remember we are previously in the index ts we are storing everything as unsigned ends. We don't necessarily have to do that, but it just makes things easier when we come up on the other side, we're talking about different types of memory, it helps to keep the same view of that memory.

I'm gonna say the memory buffer, which is the active memory being used right now. And I'm gonna use document.write, again. And I'm gonna pull from the memoryArray at index 1. All right, one of the benefits of an unsigned 8array it's an array like structure. So things like indexing and certain operations like you're gonna move some things like that.

It's an array like it's not an array itself, but it's close enough for our use case, so we can just access it by the index. So index 1, this should print 99 if I've done things correctly, hey, did, all right, pretty cool, that's pretty cool. So now we wrote 99 into the memory at index 1, and then we're reading it from JavaScript via the unsigned 8 array and the memory buffer.

So let's go the other way, let's write to memory and see if WebAssembly can use that as well. So I'm gonna say, just for cleanliness, I'm gonna say document.writes, I'm gonna throw in a br tag, just to make it easier to read, not critical. I'm gonna say, Actually do that next, I'm gonna write to the memory, right?

So, writing to the memory array in JavaScript is a little bit simpler, we don't have to give it types or anything like that. I'm gonna write at three location 3, and I'm gonna say, actually get to all follow my own example, I'm gonna write 42 int array. So now we can read from the memory, so documents.writes, and we're gonna call that an exported wizened function read memory, which reads from whatever the pointer we pass in.

So, I'm gonna read from index 2, from the shared array buffer, And if I've done things correctly, this should print outs 42, yeah. So, this seems trivial and it's just an example, but what we did was we wrote directly to memory and WebAssembly. And then we call that in JavaScript, that shared memory, and we read from it and then we wrote back to the memory and we made sure that WebAssembly can call it itself.

So in this way, we pass things back and forth, we pass data back and forth and this is the primary way of communicating between WebAssembly and JavaScript. Is just through this shared array buffer and writing to the memory locations at once. Now, you see the complexity in doing something like this get to keep track of indexes and the pointer says index, and what type of memory is being saved.

Is it an unsigned integer? Is it a 32 bit integer? Things like that. Which is why, as I said earlier, one of the struggles with creating this course was creating simple enough content or simple enough examples that it didn't get too complicated. Cuz the really build up really performance and applications that really showcased WebAssembly would get incredibly complex, because of all this reading and writing to memory, and I didn't really wanna spend too much time on that.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now