5 ms·
> In Wasm a null pointer just refers to memory[0] and it is a legal address to read and write from. Uh, can anyone explain the reasoning behind this? Because i
by kllrnohj 4y ago
> In Wasm a null pointer just refers to memory[0] and it is a legal address to read and write from.
Uh, can anyone explain the reasoning behind this? Because it looks like WASM just made an absolutely brain dead decision here that flies against everything computers have done for the last ~20 years.
Yes yes there exist systems without MMUs where 0x0 is a valid address (along with all the low range addresses for that matter). But it definitely isn't a friendly or "modern" design, so why on earth would WASM go with it? Why wouldn't they just have a fixed offset? I mean really the brk based memory scheme was already bad enough, but to start at 0x0?
EDIT: Oh, also worth noting this doesn't just affect compiled languages like C/C++. But also languages like Java. How do you think NullPointerException works? The runtime isn't inserting if checks before every object access, that'd be murderously slow. No, it's just letting the deref trap and converting the segfault into an NPE exception. So WASM is also a PITA for high level languages that don't expose pointers at all. Well, that's once WASM code can catch traps in the first place, which currently it cannot.
- shadowofneptune 4y agoIt seems reasonable to me based on how memory is used in WebAssembly, and the constraints it runs under. The address space of a WASM module is limited to just that module. A null pointer in one module will never be the same as one in another module, so it's already unlike native code. Mandating that address 0 cannot be used places more constraints on what the browser engine can do when it compiles the module, and for some languages compiled to WASM this feature may not even be necessary. EDIT: As for your own edit, since you cannot do arithmetic on a pointer in Java, this does make it easier. NULL could be set to -1, which unless that module is using 4 gigabytes of memory will cause an out-of-bounds exception.
- kllrnohj 4y ago> Mandating that address 0 cannot be used places more constraints on what the browser engine can do when it compiles the module, and for some languages compiled to WASM this feature may not even be necessary. How do you figure? Just say the WASM heap starts at 0x1000 instead of 0x0 or whatever. Bam, done & done. The browser engine is already incorporating offsets to all the memory accesses anyway, the "real" addresses do not start at 0x0 of course. Edit: > EDIT: As for your own edit, since you cannot do arithmetic on a pointer in Java, this does make it easier. NULL could be set to -1, Well, you'd use something a bit farther away so that the internal engine also doesn't need to worry about small offsets from null (you're not always dereference 0x0 itself, but rather a field "nearby" after all) And yes Java can do that. So can C, for that matter, by defining NULL to be something else. But if everyone has to just reimplement guard pages in the upper 4GB address range, it gets back to why the hell didn't WASM just standardize that like every other MMU-based system? Why did WASM be an unfriendly snowflake? And of course once an mmap-like happens you lose your guard page safety. The runtime could still use the address range you hoped was invalid for something valid. You really need the runtime to guarantee the dead zone. Which a simple heap offset would have trivially achieved.
- shadowofneptune 4y agoThat actually is not a bad solution. Keeps everything in alignment. I assumed your solution would keep addresses 1, 2, 3, etc... as valid.
- flohofwoe 4y ago> by defining NULL to be something else I bet that around 99% of C code in the real world assumes that NULL == 0. I'm sure that if it would be easy to make nullptr accesses trap in WASM browser runtimes without killing performance, the Emscripten team would have come up with a solution.
- josefx 4y ago> I bet that around 99% of C code in the real world assumes that NULL == 0. That is the correct assumption as 0 is interpreted as a null pointer constant when interpreted as a pointer. As far as I understand you have to do something like memset(ptr,0,sizeof(int*)) to bypass this conversion and get a pointer to the memory location 0 instead of a NULL pointer.
- saurik 4y agoYeah, but that memset is super common due to how people clear structs.
- s-macke 4y agoWASM is not designed to work around the shortcomings of C. The fact that nowadays the program crashes with a segmentation fault error after a null pointer dereference is only because modern operating systems are being nice to you. But a proper error handling can still be implemented into WASM. Maybe, the following proposal on memory control will add the option: https://github.com/WebAssembly/memory-control/blob/master/proposals/memory-control/Overview.md https://github.com/WebAssembly/memory-control/blob/master/pr...
- kllrnohj 4y ago> WASM is not designed to work around the shortcomings of C. That's not the question. The question is why was WASM designed to be hostile to C (or C-likes)? There's seemingly no benefit since WASM has to be relocated anyway, the actual memory addresses aren't 0x0 after all. So the WASM runtime is already doing an offset. Therefore there's no cost (and seemingly no implementation burden) to not starting at 0x0, so why would you? Why would you be aggressively hostile to what's going to almost certainly be your 2 largest users?
- tcfhgj 4y agoC# has introduced nullables; safe Rust simply doesn't have nullptrs
- erichdongubler 4y agoNitpick: Safe Rust [_does_][0] let you construct null pointers -- it's _dereferencing_ them that's not allowed in safe Rust. [0]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=800d015d6a7facf22209bf708c1e1d76 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- OskarS 4y agoMy guess is probably that it's performance issue. You would have to do a `if (addr == 0) trap()` branch on EVERY memory access in the WASM runtime, which is not free. I guess it would still has to do some kind of bounds check to see that you're not overflowing your allotted buffer, but I could imagine that adding that kind of null-check to every memory access is not something you just get for free. The other option would maybe be to make the entire first page of memory non-readable, and then have the MMU execute the trap for you (this is how real OSes does this null check, right?), but then you're wasting 4kb in every WASM runtime. Doesn't seem ideal. I agree with you, this is a real shame, and it's going to lead to a LOT of bugs that would have been discovered otherwise. But I can see the reasoning why you wouldn't do this in WASM.
- flohofwoe 4y agoEmscripten allows to compile in a mode where null-pointer accesses trap, but this involves a check before each pointer access and costs a lot of performance. I think null being a valid address is a side effect of the WASM heap being a Javascript ArrayBuffer, where zero is a valid index (and I guess it's not possible to create an ArrayBuffer where the zero index is out-of-bounds). Maybe in non-browser WASM runtimes (where the WASM heap doesn't need to be wrapped in an ArrayBuffer but can be directly mapped to virtual memory) it's possible to let null-pointer accesses segfault without performance cost.
- titzer 4y agoNo having an unmapped zero-page by default is a result of Web Platform integration. There isn't an easy interop story with JS ArrayBuffer where there are inaccessible holes. And it's not just JS (and therefore a JSVM issue); a lot of code in the web platform (e.g. Chromium) uses the memory of ArrayBuffers rawly and is not prepared to encounter holes (OS-level signals). That said, we are working on this: https://github.com/WebAssembly/memory-control https://github.com/WebAssembly/memory-control
- jokoon 4y agoFor for curiosity, how does C handles this? Is that at the OS level? Because if it is, you can't really compare WASM to C, since WASM is not designed to be ran by an OS, so there are other protections. WASM comes at some cost I guess, and I'm not sure if it makes it so much more difficult to debug.
- mananaysiempre 4y agoIn a typical protected memory situation, yes, this is done by the OS setting up a faulting mapping of OS-dependent size at low addresses, though the C runtime startup code could do it if the OS did not. (Not sure how Win9x handled it, by the way—wasn’t it supposed to map the DOS VM in the first 1M?..) In bare metal environments, there is usually something at address zero—if you’re lucky, it’s ROM, and you get a bus fault if you try to write there; if you’re not (as on the 8086), it is part of an architecturally significant part of RAM such as the interrupt vector table, and spectacular amounts of chaos can ensue if you touch it at the wrong time. Technically, while the C standard does require that void *p = 0; makes p be a special pointer value equal to any other one obtained that way and unequal to any normal pointer value, it does not require the memory backing the variable p to actually store all zero bits. Neither does it require that p be equal to q or r in int n = 0; void *q = (void *)n; void *r; memset(r, 0, sizeof r); nor, of course, that actually dereferencing p cause any particular behaviour like a crash. (POSIX may be stricter, I’m not sure.) Therefore the designers of the WASM C ABI could have made (void *)0 an all-ones pointer instead, for example, or better yet that minus some headroom, and guarantee a crash on dereference that way. Of course, this would be a DeathStation-level perversity, and I doubt Clang would be capable of dealing with such a platform out of the box. ... Now that I’m thinking about it, a more realistic solution would be to have (void *)n be stored as n but actually refer to WASM memory address n - 64K or something like that during codegen. Perfectly ordinary platform as far as C is concerned, possibly a bit of binary bloat(?). Don’t know why they didn’t do it that way.