10 ms·
WebAssembly and C++
- codeflo 4y agoI have a clarification: Dereferencing a null pointer in C++ doesn’t reliably crash anymore, unfortunately, and if you still believe it does, then I do not want to run your C++ code. I assume that the author understands this and is only trying to simplify. My problem is that this is already such a widespread and dangerous myth that I’m sad to see it perpetuated in an otherwise great article. For anyone who’s wondering, I’m referencing “UB” here (which is short for Undefined Behavior, but don’t be confused by the English language meaning, it’s a precise technical term in the spec). Skipping the details, there’s a surprising (and growing) amount of situations where a null pointer access leads to silent incorrect code execution instead of a crash already, with standard compilers and CPUs. C++ programmers need to deal with that on any platform. As I’m sure the author is aware, what the WASM compilers do here is well within the spec.
- 10000truths 4y agoCompilers following the spec is necessary, but not sufficient. Compilers are expected to be useful, and in practice, that means being able to fail reliably when spec violations occur. Let’s highlight what I personally expect, from my experience: 1. Pretty much every non-embedded CPU architecture has a page-based MMU. 2. Pretty much every compiler treats NULL as a zero value. 3. Every operating system using an MMU will leave the zero page unmapped, and load/stores at those addresses will cause a segfault. 4. If the pointer address cannot be inferred at compile time (e.g. via address of stack variables, or constant propagation), the compiler cannot optimize away the dereference. Now WASM is a little bit different because it’s a virtual bytecode, but 99% of WASM builds use emscripten, and emscripten’s primary use case is porting C programs to the web with minimal changes, which means that in order to be useful, either emscripten (via instrumentation) or WASM (via memory protection feature) needs to be able to accommodate common target specific behaviors like memory protection.
- IX-103 4y agoWASM is a little more different than that, because 0 is a valid memory address -- the beginning of the heap. As you would expect this has all kinds of wonderful ramifications for code that assumes 0 is nullptr.
- 10000truths 4y agoYes - as mentioned in the article, 0 being a valid memory address is an unexpected gotcha. My point is that, even though the C spec allows for such an environment, it still causes lots of problems in practice, so it should be addressed.
- int_19h 4y agoIt should be noted that such an environment has been extremely common back when C was designed, and for a couple decades thereafter. This isn't something new that has just been sprung up unexpected on C coders.
- gpderetta 4y agoNote that the constant 0 being the null pointer is not an assumption but it is guaranteed by the standard. Whether that maps to address 0 it is an implementation detail, but any implementation that doesn't do that is looking for trouble.
- Kranar 4y ago>If the pointer address cannot be inferred at compile time (e.g. via address of stack variables, or constant propagation), the compiler cannot optimize away the dereference. It can't optimize away the dereference, but it can optimize away any NULL checks, so for example the compiler can transform this: if(y != NULL) { printf("Hello"); } int x = *y; Into this: printf("Hello"); int x = *y; It's possible that you never expect "Hello" to be printed if y is NULL, since there is a clear check for NULL before the printf, and yet... because of undefined behavior and compiler optimizations, the compiler is allowed to make assumptions about runtime behavior even if that behavior precedes an undefined operation. https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633 https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63...
- AshamedCaptain 4y ago> Dereferencing a null pointer in C++ doesn’t reliably crash anymore Anymore? I would say the opposite: these days it tends to crash more reliably if anything, as the size of the redzones increases. Even on Windows 9x 0 was a perfectly valid address.
- sumtechguy 4y ago0 is usually a 'valid' address. What is at that addr though can have some interesting bits that may or may not contain valid instructions/data. Now on some processors you can lock it out so it seg faults if you access it though.
- kllrnohj 4y agoNull pointer derefs in C/C++ reliable crash on anything that isn't an embedded device. The times when the compiler "avoids" it are just times when the deref isn't actually used, and thus dead code eliminated. Which doesn't tend to result in incorrect code execution, but rather crashes later on that are harder to understand (like having "this" end up being null inside a member function)
- adrian_b 4y agoWhile you are right that in theory a C or C++ compiler is free to do anything when Undefined Behavior is specified in the standard, any careful programmer should always compile all C/C++ programs with options like "-fsanitize=undefined -fsanitize-undefined-trap-on-error". This guarantees that null pointers or out-of-bounds array accesses will crash the program.
- sanxiyn 4y agoDereferencing a null pointer in C++ doesn't reliably crash, but dereferencing a null pointer in GCC and Clang C++ compiled with -fsanitize=null does reliably crash, with useful error messages, and it is documented to be so. You are not required to write standard C++. Making uses of implementation defined features is a valid engineering choice.
- nix0n 4y ago> standard compilers and CPUs I'm not sure what you mean by that, are you including modern Windows/Linux/Mac PCs? > silent incorrect code execution Do you know of any non-contrived pieces of code that silently do the wrong thing, on platforms that could otherwise run Electron?
- codeflo 4y ago"Non-contrived" is a matter of definition; it's almost necessary to give toy examples, but I hope you can be convinced that the same structural pattern can appear in real code. I hope Clang on x86_64 is common enough for you to make my point. In this example, Clang reasons (backwards in time) that the if couldn't have been taken and dereferences whatever was in p, instead of dereferencing null: https://gcc.godbolt.org/z/zjhY1W67Y https://gcc.godbolt.org/z/zjhY1W67Y
- flohofwoe 4y agoThere's now an interesting alternative to Emscripten called WaJIC: https://github.com/schellingb/wajic https://github.com/schellingb/wajic Enables most of the "Emscripten magic" (like embedding Javascript code into C/C++ files), but in a more bare bones package (apart from clang it essentially just uses the wasm-opt tool from Binaryen for post-processing). (to be clear, wajic has fewer out-of-the-box features than Emscripten, but it might be an alternative for very small projects which don't need all the compatibility shims provided by Emscripten, while still providing features to simplify calling between C/C++ and WASM. Also on an unrelated note: I found working with WASM a lot more enjoyable after I switched from C++ to plain C. The resulting WASM blobs are usually smaller, and interfacing between JS and WASM is a lot easier down on the C API level.
- singularity2001 4y agoThere is also that alternative called clang, which has builtin WASM support since version __.0 (but you need to write js glue code yourself)
- flohofwoe 4y agoBoth WAjic and Emscripten depend on Clang to compile to WASM, but Clang alone isn't very useful except for very simple WASM libraries that don't need to call out into web APIs. WAjic adds exactly the part that simplifies writing the JS glue.
- glandium 4y agoIIRC, it's since version 8.0.
- HexDecOctBin 4y agoHas there been any meaningful progress in doing DOM manipulation (and accessing other web APIs) from WASM?
- flohofwoe 4y agoDepends on what you mean. The DOM still strictly lives on the Javascript side (and IMHO that's a good thing), but the calling overhead from WASM to JS (and back) has been reduced in browsers (ca 2018/19) so that the overhead doesn't matter much anymore, and Emscripten has features to keep the JS shim close to the C/C++ side (by embedding Javascript directly into C/C++ source files). That way it's quite trivial to build a C or C++ library which manipulates the DOM by calling out into small JS source snippets. To the user of such a library it doesn't make a difference whether WASM manipulates the DOM directly or goes through a JS shim. WASM proposals like Interface Types (or whatever it is called currently) will reduce the amount of housekeeping needed by the JS shim, but that would only affect the library maintainer, not the library user. (IMHO: using WASM for a framework which spends most of its time manipulating the DOM is wasted effort, JS is much better suited for this type of "dynamic workload", this type of application might be better done with a hybrid approach where Javascript and WASM work hand in hand)
- gspr 4y ago> (IMHO: using WASM for a framework which spends most of its time manipulating the DOM is wasted effort, JS is much better suited for this type of "dynamic workload", this type of application might be better done with a hybrid approach where Javascript and WASM work hand in hand) Well, I suspect there might be a non-trivial number of us who have absolutely no interest in learning JS, but would like to do DOM manipulation in web apps written 100% in [Personal Favorite Language That Compiles To WASM].
- flohofwoe 4y agoFWIW if you look around, C++ and Rust libraries for DOM manipulation exist (I haven't searched for other languages which compile to WASM): https://github.com/mbasso/asm-dom https://github.com/mbasso/asm-dom https://github.com/sycamore-rs/sycamore https://github.com/sycamore-rs/sycamore I think solving the problem of DOM access on the library level is exactly the right way to tackle this problem. The library user don't need to care about specific WASM features, and the library implementation can be simplified when those WASM features become available (and also implement per-browser fallback paths)
- hoseja 4y agoThe problem with NULL not being 0 is you can't do things like `if(ptr)`
- janekm 4y agoIndeed, I do make an effort to write if(ptr!=NULL) (partially because it more explicitly encapsulates the intent, much like writing ptr=NULL when "clearing" the ptr) but changing the developer expectations around this sounds like a terrible idea.
- ncmncm 4y agoEasily handled, in translation. The compiler understands it is a pointer, and that you are checking if it is equal to nullptr, and generates the right code. Another smart adjustment when making a new ABI is to make true represented as -1. This matches how AVX* and SIMD instructions do things. (The RISC-V people missed this opportunity, among others.) Of course when converting to int, it gets turned into 1, but compilers have absolutely no difficulty with that.
- qsort 4y ago??? Pointers, as per the standard, have an implicit conversion to bool that evaluates to true if and only if the pointer is not exactly nullptr. Code like: if (ptr) { ... } is guaranteed to work the way you expect it to, regardless of how null pointers are implemented.
- teddyh 4y agoIn C, NULL is always 0. Or, more correctly, the constant ‘0’ in a pointer context is always the NULL pointer. It’s then up to the compiler to convert the NULL pointer to the actual memory address constant used for the current architecture. From pure C code, the NULL pointer will always appear to == 0, and you should never be able to tell otherwise.
- smartmic 4y ago> But I think many people with similar C++ experience would agree with me that it's pretty rare for it to actually be the right tool to use for software these days. Is this really the case? Personally, I see C++ (in general, not related to WASM) as a very powerful, modern language, plus it is standardized. Counting on the Lindy effect, I expect it to be around and of value for more than the next 40 years.
- snovv_crash 4y agoAgreed. If you want to do anything with actual deployment to production of image processing for example, and you can't run it in a docker container, then C++ is the only option to get access to the myriad of libraries available.
- qsort 4y agoAbsolutely not, that quote is nuts. It is true that the use cases where you wouldn't pick C++ as your first choice are more popular now than they used to be, and it's probably also true that if you only know C++, learning something like Ruby or Python to complement your skillset is likely to be a smart career move. But come on, almost all the languages that are hip and get a lot of mindshare wish they had a fraction of C++'s usage and influence.
- kllrnohj 4y agoC++20 is honestly a pretty good language. So much of the anti-C++ sentiment seems to be from people who don't realize C++98 isn't what's in common use anymore (and I would absolutely agree C++98 was kinda shit in many ways) But realistically anything where performance is the primary goal, especially graphical or media things, C++ is still going to be on your very very short list of languages to use. Rust is a compelling alternative, but... that's kinda it. It's really a choice between those 2. And you certainly can't be faulted for going with the far more established and far more widely used C++ over the new upstart Rust.
- tialaramex 4y ago> C++20 is honestly a pretty good language. > C++98 was kinda shit in many ways > And you certainly can't be faulted for going with the far more established and far more widely used C++ over the new upstart Rust. This cake-and-eat-it approach doesn't work. Either you're choosing C++ 20, and you get modern luxuries like a starts_with() method on strings but your language is so fresh out of the box that your tools don't work properly yet and your compiler vendor fixes swathes of fundamental bugs in core C++ 20 features every release or you're choosing to say well, C++ is really one language, and then ew... this library I'm using thinks raw pointers signify ownership because it was first written in 2008. Also, although C++ cleaned up lots of things since C++ 98 (for example Bjarne's terrible "look at me, I have operator overloading" I/O Streams is pushed aside by a modern formatting library in C++ 20) the fundamental ethos remains the same - C++ will always pick the wrong defaults. I'm not talking about a couple of unlucky choices, it's impossible to be this unlucky, they must surely be doing it on purpose. Without Vittorio's Epochs, which they very deliberately did not take for C++ 20, they won't be able to fix the defaults.
- 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.
- titzer 4y agoFWIW there is a proposal in the works to add page-based protection, which will allow unmapping the 0 page, restoring the trap-on-null-deref behavior that is important for many languages with safety checks. https://github.com/WebAssembly/memory-control https://github.com/WebAssembly/memory-control
- AshamedCaptain 4y ago> For example, null handling in basically every non-C language doesn't rely on help from the CPU! I am quite sure that Java does rely on page faults to detect null access (i.e. sigsegv causes a NullPointerException) As probably do most Javascript implementations and I would even dare say most languages' runtimes... Cause if they don't, that's an optimization waiting to happen (e.g. for optional types).
- evmar 4y ago[author here] Very interesting, do you have a reference on how Java implements this? I just poked at v8 a bit and they seem to have an explicit 'null' object in the VM, though I don't know a lot about so it's possible it works as you suggest. It seems it might be hard to distinguish language-level null pointers from null pointers within the VM implementation. E.g. v8 runs within a Chrome process that is processing HTML and other things, so if v8 had some special signal handling of null pointers it'd need to be able to distinguish those from HTML processing bugs etc., which I think you might only be able to do by examining the stack? I haven't thought this through but it seems difficult.
- ArchOversight 4y agov8 is JavaScript, which is not a JVM. They are two very different beasts.
- evmar 4y agoThe comment I was replying to made statements about both Java and JavaScript, so my questions were also about both (and whether they were different). I just added a line break to make this clearer.
- ArchOversight 4y agoAh, apologies, it seemed like you were accidentally conflating the two. Thanks for clarifying!
- 4y ago