1,683 karma · joined June 6, 2017
What is this argument? Because it's slower it's bad? It could have been done better? Ok. Then learn the language. Or pick a language you do know. Or the performance doesn't matter.
At the end of the day, people were shipping absolute garbage that made them east 50k a year on the side 15 years ago. I know this because ...
Bad arguments all around.
I'm wondering if it's mostly the WASM setup itself (after transfer).
I'm working on a cozy voxel game with RPG and platforming elements. Still early days, so I'm still figuring things out. Players play on their own private instances. The world is very malleable as player instances are based on a "master tape" world that I maintain. After creation that fork is the players own, and they do whatever they want.
You can play a browser build here: https://automatia.libriscv.no:3000/
If playing is not your jam, here's my latest post: https://libriscv.no/blog/next-of-kin (although it will not explain much about the game)
The number one thing to learn from all of this is that governments aren't really hands-on-wheel and maybe never have been. It's interesting to watch an old video of a politician from eg. the 60s, 70s or 80s and compare to today, though. So, I could be wrong.
And "others" will be blamed, if history repeats.
In IPRE, I am pausing the caller while the remote call is on-going, which means "just having a shared block" is inferior to just sharing everything. It's just so much easier and nicer to be able to pass literally anything you want.
The caveat is that the callee has to wait, but I think the fact that the remote is now running in the SAME THREAD without any scheduling involved makes up for it. It's a true synchronous remote function call with some overhead.
There is a blog-style writeup here: https://fwsgonzo.medium.com/an-update-on-tinykvm-7a38518e57e...
Not as rigorous as the paper, but the gist is there.
Here is the abstract: This paper introduces Inter-Process Remote Execution (IPRE), whose primary function is enabling gated persistence for per-request isolation architectures with microsecond-latency access to persistent services. IPRE eliminates scheduler dependency for descheduled processes by allowing a virtual machine to directly and safely call, execute functions in a remote virtual machines address space. Unlike prior approaches requiring hardware modifications (dIPC) or kernel changes (XPC), IPRE works with standard virtualization primitives, making it immediately deployable on commodity systems. We present two implementations: libriscv (12-14ns overhead, emulated execution) and TinyKVM (2-4us overhead, native execution). Both eliminate data serialization through address-space merging. Under realistic scheduler contention from schbench workloads (50-100% CPU utilization), IPRE maintains stable tail latency (p99<5us), while a state-of-the-art lock-free IPC framework shows 1,463× p99 degradation (4.1us to 6ms) when all CPU cores are saturated. IPRE thus enables architectural patterns (per-request isolation, fine-grained microservices) that incur millisecond-scale tail latency in busy multi-tenant systems using traditional IPC.
Bottom line: If you're doing synchronous calls to a remote party, IPRE wouldn't require any scheduler mediation. The same applies to your repo. Passing allocator-less structures to the remote is probably a landmine waiting to happen. If you structure both parties to use custom allocators, at least for the remote calls, you can track and even steal allocations (using a shared memory area). With IPRE there is extra risk of stale pointers because the remote part is removed from the callers memory after it completes. The paper will explain all the details, but for example since we control the VMM we can close the remote session if anything bad happens. (This paper is not out yet, but it should be very soon)
The best part about this kind of architecture, which you immediately mention, is the ability to completely avoid serialization. Passing a complex struct by reference and being able to use the data as-is is a big benefit. It breaks down when you try to do this with something like Deno, unfortunately. But you could do Deno <-> C++, for example.
For libriscv the implementation is simpler: Just loan remote-looking pages temporarily so that read/write/execute works, and then let exception-handling handle abnormal disconnection. With libriscv it's also possible for the host to take over the guests global heap allocator, which makes it possible to free something that was remotely allocated. You can divide the address space into the number of possible callers, and one or more remotes, then if you give the remote a std::string larger than SSO, the address will reveal the source and the source tracks its own allocations, so we know if something didn't go right. Note that this is only an interest for me, as even though (for example) libriscv is used in large codebases, the remote RPC feature is not used at all, and hasn't been attemped. It's a Cool Idea that kinda works out, but not ready for something high stakes.
The iOS version is Swift-based.
Since that storm, we have decided to buy a second fireplace for upstairs with a cooking top.
Although, I also made it create Rust and Go bindings. Two languages I don't really know that well. Or, at least not well enough for that kind of start-to-finish result.
Another commenter wrote a really interesting question: How do you not degrade your abilities? I have to say that I still had to spend days figuring out really hard problems. Who knew that 64-bit MinGW has a different struct layout for gettimeofday than 64-bit Linux? It's not that it's not obvious in hindsight, but it took me a really long time to figure out that was the issue, when all I have to go on is something that looks like incorrect instruction emulation. I must have read the LoongArch manual up and down several times and gone through instructions one by one, disabling everything I could think of, before finally landing on the culprit just being a mis-emulated kind-of legacy system call that tells you the time. ... and if the LLM had found this issue for me, I would have been very happy about it.
There are still unknowns that LLMs cannot help with, like running Golang programs inside the emulator. Golang has a complex run-time that uses signal-based preemption (sysmon) and threads and many other things, which I do emulate, but there is still something missing to pass all the way through to main() even for a simple Hello World. Who knows if it's the ucontext that signals can pass or something with threads or per-state signal state. Progression will require reading the Go system libraries (which are plain source code), the assembly for the given architecture (LA64), and perhaps instrumenting it so that I can see what's going wrong. Another route could be implementing an RSP server for remote GDB via a simple TCP socket.
As a conclusion, I will say that I can only remember twice I ditched everything the LLM did and just did it myself from scratch. It's bound to happen, as programming is an opinionated art. But I've used it a lot just to see what it can dream up, and it has occasionally impressed. Other times I'm in disbelief as it mishandles simple things like preventing an extra masking operation by moving something signed into the top bits so that extracting it is a single shift, while sharing space with something else in the lower bits. Overall, I feel like I've spent more time thinking about more high-level things (and occasionally low-level optimizations).
It is the biggest problem with C++ right now. We can't have nice things (no networking in the stdlib), and we also can't have nice packages (no networking in $pkg), so we end up with whatever appears on search for site:github.com and C++ whatever. It's really not that great. The only tradeoff is we really care about our deps, and won't pull in the world. So we're harder to target for supply chain attacks.
You shouldn't be waiting 20mins for CI builds though, unless you have a massive codebase and 10 platforms to build for. If you're making a Godot addon for every platform, I get it. I have that issue. But the 20mins are down from 50mins without ccache. Ccache is ~5 lines in the GA yaml.
Example: https://github.com/varnish/libvmod-riscv/blob/main/program/c...
chperm can change the permissions of a segment to just execute, instead of read+execute. Of course, you will need a custom linker script because the default linker scripts will merge .text and read-only data to save space.
Currently, I'm battling a Rust program that has a built-in function called __memcmpeq, which isn't a global, so I can't --wrap it.
I know this because I manage several. To this day, building on Windows is a chore and a half, and I consider my build process to be barely working, and it will one day break simply because the packages I depend on will have some new dependency. As a very recent example is protobuf suddenly depending on parts of abseil.
Memory safety is that extra thing that makes the decision easier.
As for Rust, there are problems too, but to a much smaller degree. As an example, compiling for RISC-V requires a cross-compiler (or at least the linker). Cross-compiling to RISC-V with Go does not.
A final anecdote at the end: I recently added support for Common Access Token in a Rust project, and it was exactly as easy as I thought it would be. I'm not much of a Rust programmer, but I cannot express strongly enough just how big of a difference this makes to productivity. In C++ land we sort of ended up agreeing to never agree on how to get proper networking. It's absurd.