[HN Gopher] WebAssembly and C++
___________________________________________________________________
WebAssembly and C++
Author : mariuz
Score : 88 points
Date : 2022-06-27 05:30 UTC (17 hours ago)
(HTM) web link (neugierig.org)
(TXT) w3m dump (neugierig.org)
| flohofwoe wrote:
| There's now an interesting alternative to Emscripten called
| 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 wrote:
| There is also that alternative called clang, which has builtin
| WASM support since version __.0
|
| (but you need to write js glue code yourself)
| flohofwoe wrote:
| Both 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.
| AshamedCaptain wrote:
| > 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 wrote:
| [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.
| wahern wrote:
| You can distinguish where a NULL pointer exception occurred
| by the program counter/instruction pointer. JIT'd code lives
| in particular memory regions (e.g. those explicitly mmap'd
| with PROT_EXEC) dynamically managed by the application. From
| there you may or may not care to examine the stack(s) for
| more specific details.
|
| Alternatively, you could simply only enable NULL pointer
| handling around specific blocks of code where you're prepared
| to handle it, and disable it everywhere else (causing the
| kernel to simply kill the program). I've used this technique
| to elide explicit array overflow checks in some very
| performance critical code, dramatically increasing
| performance (there would have been at least as much overflow
| checks as semantic operations). None of the critical code
| used malloc/free or called into any libraries, so on SIGSEGV
| I would simply longjmp back to a safe place and either grow
| the data structures and reset the state, or return failed,
| depending on how large things had grown.[1]
|
| The hard part of all of this isn't detecting where or even
| why a fault occurred--at least presuming you've architected
| things deliberately--but rather handling asynchronicity. If
| you have access to mechanisms like userfaultfd it's much
| easier, but it's doable on most systems. On most Unix systems
| threading primitives are NOT async-signal safe, but there are
| plenty of syscalls that are, and at least historically the
| semantics of Unix signal handlers were carefully defined to
| permit these sorts of tricks. It's a fine needle that needs
| to be threaded, but one that can done correctly nonetheless.
|
| [1] Note that this wasn't accidental. I wrote the code very
| carefully this way. I had also worked on at least two
| projects before where someone tried to get clever and add
| SIGSEGV handlers to recover from code that was never designed
| to be recoverable, with predictably horrendous results (e.g.
| endless, time-wasting bug reports and "fixes").
| evmar wrote:
| Thank you for explaining! I think the piece I was missing
| is that the sigint handler is passed the instruction
| pointer of the faulting instruction.
| gpderetta wrote:
| AFAIK, GNAT and GCJ (GCC front ends for Ada And Java
| respectively, the latter now dead) implement null pointer
| exceptions by simply raising a language level exception
| from the SIGSEGV handler (after I assume checking it
| happened in user code instead of the runtime).
|
| Completely non portable of course, but with the help of
| glibc, the Itanium ABI exception support and non-call-
| exceptions it can evidently be made to work reliably, at
| least on Linux.
| layer8 wrote:
| See the links to the OpenJDK implementation in this blog
| post: https://shipilev.net/jvm/anatomy-quarks/25-implicit-
| null-che...
| ArchOversight wrote:
| v8 is JavaScript, which is not a JVM. They are two very
| different beasts.
| evmar wrote:
| The 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 wrote:
| Ah, apologies, it seemed like you were accidentally
| conflating the two. Thanks for clarifying!
| titzer wrote:
| FWIW 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
| smartmic wrote:
| > 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.
| kllrnohj wrote:
| C++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 wrote:
| > 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 wrote:
| You're kinda ignoring the middle ground there?
| Intentionally maybe? Libraries written in 2008 _and still
| maintained_ have long since moved to C++11 or C++14 or
| newer. The baseline is easily a post-C++11, which was a
| _huge_ turning point.
|
| > C++ will always pick the wrong defaults
|
| Oh definitely. Bad defaults are probably my only remaining
| complaint with modern C++.
| qsort wrote:
| Absolutely 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.
| snovv_crash wrote:
| Agreed. 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.
| codeflo wrote:
| I 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.
| adrian_b wrote:
| While 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.
| kllrnohj wrote:
| Null 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)
| AshamedCaptain wrote:
| > 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 wrote:
| 0 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.
| 10000truths wrote:
| Compilers 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.
| Kranar wrote:
| >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=63.
| ..
| 10000truths wrote:
| In your example, even though the behavior is undefined
| according to the spec, the program will still reliably
| segfault as long as the target it runs on has the
| appropriate memory protections for the zero page. The crash
| will occur immediately after the elided null check, so the
| failure is localized to the problematic area and a debugger
| will show the problem.
| IX-103 wrote:
| WASM 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 wrote:
| Yes - 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.
| gpderetta wrote:
| Note 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.
| hoseja wrote:
| The problem with NULL not being 0 is you can't do things like
| `if(ptr)`
| janekm wrote:
| Indeed, 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 wrote:
| Easily 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.
| teddyh wrote:
| In 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.
| qsort wrote:
| ???
|
| 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.
| HexDecOctBin wrote:
| Has there been any meaningful progress in doing DOM manipulation
| (and accessing other web APIs) from WASM?
| flohofwoe wrote:
| Depends 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 wrote:
| > (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].
| xwolfi wrote:
| I think trivial. Manipulating the DOM without Javascript is
| as useful to understand it as manipulating a car without
| hands. It can work other ways, you can prefer it but it
| wasn't made for that amount of flexibility. Better use wasm
| for what it's good at: obfuscation of proprietary code,
| high intensity mathematics, port of existing code. Not
| adding a span in a div please.
| gspr wrote:
| > I think trivial.
|
| Neither one of us can back up our claims, but: Surely
| there's a non-trivial amount of people out there who
| abhor JS and who would still want to be able to target
| the web as a platform?
|
| > Manipulating the DOM without Javascript is as useful to
| understand it as manipulating a car without hands.
|
| Why? If you mean that because "things are the way they
| are", then sure, but this isn't a fundamental limitation.
|
| > Better use wasm for what it's good at: obfuscation of
| proprietary code,
|
| That's not what WASM is for.
|
| > high intensity mathematics, port of existing code. Not
| adding a span in a div please.
|
| But _why_? Why should that last task be the domain of
| only JS?
|
| I think this is exactly the attitude that has kept me
| shying away from the web as a platform. On classical OS
| platforms, they may well be better suited and less suited
| languages for a given task - but declaring that you
| _mustn 't do_ something in a given language is just
| strange to me. Yet it seems like the norm on the web.
| esperent wrote:
| > Surely there's a non-trivial amount of people out there
| who abhor JS
|
| The amount of people for whom the hate is deserved and
| not just a knee jerk reaction is probably trivial though
| (at least once you include Typescript and ES6). Modern JS
| is not a bad language and it's time for hating on it to
| go out of fashion.
| gspr wrote:
| > The amount of people for whom the hate is deserved and
| not just a knee jerk reaction is probably trivial though
| (at least once you include Typescript and ES6). Modern JS
| is not a bad language and it's time for hating on it to
| go out of fashion.
|
| I'm not saying it's a bad language any more than I'm
| saying that strawberry ice cream is bad. I'm merely
| stating the fact that _there 's a non-trivial amount of
| people who abhor strawberry ice cream_ and JS. I find
| your reply, that strawberry ice cream is perfectly fine,
| a strange one.
| flohofwoe wrote:
| FWIW 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/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)
| tcfhgj wrote:
| Those libs/frame works would benefit massively from WASM
| DOM access.
| irrational wrote:
| But, why? You would have to recompile the WASM code every
| time a change was made to the DOM. Add a new DIV that you
| now need to address? Add a reference to it in the WASM
| code and recompile. What a pain that would be.
| gspr wrote:
| I don't understand. Can you elaborate?
| HexDecOctBin wrote:
| A div's name is equivalent to a pointer to a widget in a
| GUI library. HTML isn't some special sauce, we've known
| how to do GUI in C/C++ for a long time.
| flohofwoe wrote:
| I doubt it. What would "DOM access in WASM" even look
| like?
|
| The DOM is exposed as a tree of high level Javascript
| objects with properties, but WASM has no notion of
| objects, properties and their relationship, it only has a
| few integer and floating point primitive types and heap
| addresses. All that "DOM access from WASM" would mean is
| that there's a new primitive "external reference" type
| which allows to tunnel an opaque Javascript object
| reference through WASM and back to JS while pinning the
| JS object for the JS garbage collector. But one couldn't
| really do anything useful with that reference on the WASM
| side without calling through JS (AFAIK there are some
| very high level proposals for this too, which could
| basically create the required JS shim code automatically,
| but that would just add more runtime complexity for
| little actual gain - the same shim-generation work could
| be done offline in the compiler toolchain).
|
| All that the "external reference" type would enable is a
| slightly simplified Javascript shim, because it would no
| longer need to map index handles to Javascript objects.
| But that's just a small part of what the JS shim does.
|
| It's a bit like asking if x86 machine code has support
| for the Win32 window system API, the answer is both 'yes'
| and 'no' ;)
| tcfhgj wrote:
| > The DOM is exposed as a tree of high level Javascript
| objects with properties,
|
| I don't understand the reasoning. Of course it should be
| exposed in a way that best fits into WASM tech
| flohofwoe wrote:
| The entire concept of the DOM (-API) is built around the
| Javascript object model, which is on a totally different
| abstraction level than WASM. It's like asking to create a
| CPU which can directly run Javascript, and on top has
| special instructions to "manipulate the DOM". It just
| doesn't make much sense on a practical level.
|
| Creating a library in a high level language which wraps
| DOM manipulation makes a lot more sense, and nothing in
| WASM prevents this (and the rest are just implementation
| details of the library).
| gspr wrote:
| Does the question make sense on the level of sandboxing,
| though? I thought part of the problem was that the WASM
| VM doesn't even have access to the DOM itself?
| flohofwoe wrote:
| The WASM VM can only communicate with the outside world
| through what is essentially a jump table of "Javascript
| FFI functions" (or native functions for WASM VMs that run
| outside the browser), and those functions can only have
| simple arguments and return values like integers and
| floats (no pointers, no strings, no structs, no object
| references, etc...).
|
| Creating a Javascript object on the "JS side" requires a
| mapping of the Javascript object to a simple WASM integer
| type (usually this is performed by the JS shim by storing
| the object in a dictionary with the integer handle as
| key). The integer handle is then passed back to the WASM
| side. Whenever WASM wants to call a Javascript API
| function involving a JS object, it passes the integer
| handle back to the Javascript shim, which looks up the
| associated JS object in the dictionary, and so on and so
| forth...
|
| One proposed improvement is to be able to directly tunnel
| Javascript object references as "opaque handles" through
| WASM, so that the object dictionary housekeeping code
| wouldn't be necessary.
|
| The whole thing seems terribly convoluted, but its not
| much different than FFI mechanisms where a very high
| level language needs to call into C APIs (just reversed).
| NohatCoder wrote:
| So you want to avoid the horrors of the web by inserting
| some language-warping piece of glue code that is guaranteed
| to only ever get in your way. The irony.
|
| In any case, the DOM and CSS is like 90% of the reason to
| hate the basic web stack. The other 90% of the reason that
| people hate JavaScript for is the myriad of frameworks that
| some people opt to use. You don't have to, and if you come
| from C you will probably feel right at home not doing so.
| gspr wrote:
| > So you want to avoid the horrors of the web by
| inserting some language-warping piece of glue code that
| is guaranteed to only ever get in your way. The irony.
|
| No, I in particular want to avoid glue code!
|
| > In any case, the DOM and CSS is like 90% of the reason
| to hate the basic web stack. The other 90% of the reason
| that people hate JavaScript for is the myriad of
| frameworks that some people opt to use. You don't have
| to, and if you come from C you will probably feel right
| at home not doing so.
|
| Perhaps. In my case, it's simply that I abhor the idea
| that this particular platform requires one specific
| langauge. I think that's rather dumb. I'd prefer to treat
| web browsers as just another platform. I do understand
| that this platform is a bit different from the classical
| ones (Linux, Windows, Mac, the other unices, take the
| cartesian product with hardware architectures if you
| want) in that it doesn't have a notion of file access
| (and a bunch of other things), but still - just another
| (a bit esoteric) platform nonetheless. The platform
| shouldn't care what language you write in, as long as you
| have a compiler targeting it.
|
| I pick my language--any with a compiler that targets the
| platforms I require--and then I perhaps write different
| frontends depending on the platform. Maybe on the unices
| the frontend reads and writes files. Maybe on Windows
| it's GUI based. And maybe on the browser one interacts
| with it through DOM manipulation. That last detail
| shouldn't, IMHO, _dictate that the language used_. We don
| 't accept that in other situations, so why do we when it
| comes to the web?
| NohatCoder wrote:
| > We don't accept that in other situations, so why do we
| when it comes to the web?
|
| Because it is the only thing we have sufficiently
| sandboxed so that we can actually run it untrusted.
|
| I totally get what you want, but we already have a bunch
| of X-to-JavaScript compilers that pretty much all deliver
| the experience of: "It works, but it is a bit gnarly, so
| why would you use this when you can just write
| JavaScript". And I don't think WebAssembly is going to
| change that experience much, mainly because it is a
| pretty poor VM, seemingly designed around the
| misconception that a lack of features makes it a good
| compilation target.
|
| I honestly think we could give compiled languages a much
| better time on the web with a new intermediate language,
| but someone has to design it for that purpose rather than
| cargo cult Assembly similarity.
| HexDecOctBin wrote:
| > the calling overhead from WASM to JS (and back)
|
| This is what I meant: is it still necessary to go through JS,
| or do we now get low level access to DOM directly through
| WASM? I guess we don't.
| kllrnohj wrote:
| > 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.
| flohofwoe wrote:
| Emscripten 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.
| shadowofneptune wrote:
| It 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 wrote:
| > 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 wrote:
| That actually is not a bad solution. Keeps everything in
| alignment. I assumed your solution would keep addresses 1,
| 2, 3, etc... as valid.
| flohofwoe wrote:
| > 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 wrote:
| > 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 wrote:
| Yeah, but that memset is super common due to how people
| clear structs.
| titzer wrote:
| No 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
| jokoon wrote:
| For 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 wrote:
| In 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.
| s-macke wrote:
| WASM 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/pr...
| josefx wrote:
| > WASM is not designed to work around the shortcomings of C.
|
| Memory access protection on modern systems is not about C
| segfaults. But if WASM embraces MS DOS era memory access
| management then then that is certainly a choice, might even
| still be an improvement over plain JavaScript. Does it at
| least have near and far pointers?
| s-macke wrote:
| Well, the WASM binary format uses variable-length integer
| encoding. You can see this as an enhanced version of near
| and far pointers.
| kllrnohj wrote:
| > 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?
| s-macke wrote:
| Yes, even in the proof of concept, you could have simply
| added an offset parameter when defining the memory area.
| They did not.
| tcfhgj wrote:
| C# has introduced nullables; safe Rust simply doesn't have
| nullptrs
| erichdongubler wrote:
| Nitpick: 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&editio...
| OskarS wrote:
| My 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.
| kllrnohj wrote:
| > 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.
|
| You're only wasting virtual address space, not actual
| RAM. So the only cost is your max memory possible
| decreases by 4kb. Hardly a noteworthy expense.
|
| And this is an expense that WASM runtimes already have to
| spend anyway, since none of them are running on bare
| metal with address 0x0 being available to them. So
| there's already address translations in play. Multiple of
| them, in fact, as you have to first translate from the
| WASM heap to the host's address space, and then from that
| through the MMU to physical pages.
|
| And of course the WASM runtime already needs to trap for
| invalid addresses (eg, -1 right out the gate). How would
| trapping the first few pages be any more expensive than
| any other unmapped range? You're still working with a
| single contiguous region (at least until mmap finally
| lands). There doesn't seem to be any benefit to starting
| from 0x0 vs. starting from any other constant.
| OskarS wrote:
| > You're only wasting virtual address space, not actual
| RAM.
|
| Fair enough, that's true.
|
| > And of course the WASM runtime already needs to trap
| for invalid addresses (eg, -1 right out the gate). How
| would trapping the first few pages be any more expensive
| than any other unmapped range?
|
| Memory locations are presumably all unsigned, so -1 is
| not an issue. The thing I'm saying is that you're turning
| a test that was `if (addr < LIMIT)` into `if (addr != 0
| && addr < LIMIT)`, which is not nothing. And it's on
| EVERY memory access.
|
| Thinking about it, there's no way you could rely on the
| MMU to be the ONLY memory check obviously. Like, the JIT
| can't translate a reference to `x = memory[ptr]` into
| just a `mov x, [BASE + ptr]`, because it could reference
| non-WASM memory, which is obviously a security no-no
| (unless it could statically guarantee `ptr < LIMIT`). You
| need some kind of runtime check in addition for security
| and stability, you can't get around it. Given that, I
| think I agree with you, might as well just do it. It's,
| like, two or three extra instructions or something? But I
| would not say that it's free.
| cesarb wrote:
| > Thinking about it, there's no way you could rely on the
| MMU to be the ONLY memory check obviously. Like, the JIT
| can't translate a reference to `x = memory[ptr]` into
| just a `mov x, [BASE + ptr]`, because it could reference
| non-WASM memory, which is obviously a security no-no
| (unless it could statically guarantee `ptr < LIMIT`). You
| need some kind of runtime check in addition for security
| and stability, you can't get around it.
|
| On 64-bit systems, you actually can, since WASM is for
| now 32-bit only; just reserve a 4GB (plus 1 page) block
| of virtual memory starting at BASE, and there's no way
| for BASE+ptr (assuming ptr is 32-bit unsigned) to reach
| outside it (the extra 1 page after the end is to catch
| unaligned accesses at the very end of that 4GB).
|
| That is, you can statically guarantee "ptr < LIMIT" if
| "ptr" is 32 bits and LIMIT is 2^32 or more.
| kllrnohj wrote:
| > The thing I'm saying is that you're turning a test that
| was `if (addr < LIMIT)` into `if (addr != 0 && addr <
| LIMIT)`, which is not nothing. And it's on EVERY memory
| access.
|
| But that's not what I'm saying. I'm saying every memory
| access would become:
|
| addr = addr - START_OFFSET (eg, 0x1000) if (addr < LIMIT)
| { do stuff }
|
| There's just an extra sub in there. And you can
| definitely optimize that, same as any other bounds check
| optimization. Which the runtime is already tasked with
| doing since all your loads are already (wasm_address +
| host_array_buffer_ptr). If that add is free, the early
| sub likely is, too. They fall into the same category
| here.
|
| But you can also make it _completely free_ by just
| ensuring there 's a dead zone before the host array
| buffer location, such that anything in the range
| 0x0-0x1000 (or whatever) just lands before the array
| buffer's start location which has been mmap'd to trap.
| Then you don't need any changes at all.
| sudosysgen wrote:
| You can use the MMU of the CPU to trap a null pointer
| dereference for free.
| kevingadd wrote:
| Not all execution targets for wasm can do this.
| [deleted]
___________________________________________________________________
(page generated 2022-06-27 23:01 UTC)