-
Notifications
You must be signed in to change notification settings - Fork 298
Adjust allocation strategy for TinyGo-derived modules so they don't immediately crash. #2243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
ae8999b
to
e0b816e
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
0986d4b
to
77891db
Compare
…mmediately crash. You can run the TinyGo empty project in Viceroy now (once Viceroy is updated to depend on the new wit-component herein); it immediately crashed before. It serves a request and returns a 0 exit code, somewhat obscured by a backtrace because an empty project doesn't implement a Reactor. The crux of this update is that the GC phase of adapter application now takes a `ReallocScheme` arg to its `encode()` method. This represents slightly richer advice on how to find or construct a `realloc()` function for the adapter to use to allocate its state. Before, it took only an `Option`: `None` meant "use `memory.grow`", and `Some(such_and_such)` meant "use a realloc function called `such_and_such`, provided in the module being adapted". Now we can also say "construct a realloc routine using the given malloc routine found in the module being adapted". This lets us communicate to TinyGo's GC that we have reserved some RAM, so it doesn't stomp on us later.
…loc if the former is provided. This allows TinyGo programs to take control over reallocation if they desire, elevating an explicit API over the heuristic identification of `malloc()`. It turns out it was already doing this, through the call to `realloc_to_import_into_adapter()`, which returns an (even more specific) `cabi_realloc_adapter()` if there is one and otherwise `cabi_realloc`.
In this case, we have no non-crashing way of allocating memory for the adapter state.
77891db
to
be6af98
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you detail a bit more here why this is necessary? We've so far survived with zero language-specific configurations/hacks (AFAIK) throughout component toolchain tooling and personally I'd like to see it remain that way. More specifically, why can't this be fixed in the Go-specific tooling? Or why can't this be an opt-in option that the Go tooling passes?
Absolutely! Basically, we would like to run existing TinyGo-compiled modules as components. So, while an upstream fix would be ideal for the future, we have a back catalog of compiled artifacts to somehow make work. TinyGo makes an assumption in its memory management that violates no specs but conflicts with assumptions made by the The fix for new code should be something like having TinyGo export a Of course, it would be perfectly possible to pre-process our TinyGo modules outside wit-component: we could inject the same manufactured
Alternative solutions are most welcome! But others we could gin up were ickier, and hopefully WASIp2 will relegate all this to an historical footnote. |
I agree this is probably the best place to solve this, but to put this into context as a reviewer:
It unfortunately also doesn't help that the This can then also all be coupled with the fact that while we all want components to work generally there's still a needle to thread in what's supported where. While I don't believe this PR is doing this, at one extreme it's not reasonable to saddle an open source project with all the legacy burdens and baggage of a specific embedding. The other extreme of blissfully ignoring reality is also not appropriate for an open source project as well, and inevitably the balance is going to be somewhere in the middle. At the end of the day if no one knows how or is willing to update the TinyGo toolchain that's an extremely burdensome requirement to carry. Support for a new platform like components is almost always best done with a give-and-take style approach where embedders, toolchains, languages, etc, are all in a balance of what concerns are handled where. If a toolchain cannot budge from a single position then it, in my opinion, needs to be an extremely high value target to justify saddling everyone else with that burden. Basically this is my rationale for "I think this is best done with an opt-in flag". I don't want the baggage here to proliferate to other toolchains and other languages by default. We already have a means of solving the problems you're encountering for other languages and for a variety of reasons it sounds like TinyGo and/or the surrounding support isn't going to implement those solutions. Given that, I at least personally believe that from an open source project perspective the best way to support this is to land this here but behind an off-by-default flags the explicitly requires users to activate. That's where documentation can be updated etc. On the slightly more technical front, I'm more-or-less taking you at face value that this is the best place to solve this. I do not personally have the energy to boot up on everything TinyGo-related and see if I have a different way forward for this. From my current understanding I don't know, for example, if this is a temporary hack for preexisting modules or a permanent feature intended for all future productions of TinyGo modules. If the former, just for preexisting modules, that feels understandable to me. If the latter, all future modules, I don't really have any intuition for why that is the case. |
Tests are on the way! I had hoped to have them laid in before anyone got around to reviewing, but they proved trickier than expected, and my attention was divided. I apologize for the delay. Please feel free to look away while I finish them. I will turn this back into a Draft until then. For this PR's motivation and problem statement, beyond what's in the initial commit message, I'd reference this comment block in I share your concern that
It's just for preexisting modules. New builds should target WASIp2, e.g.
The TinyGo wasm toolchain is well maintained, having p1 and p2 support as well as a dedicated maintainer on Fastly's staff. I think any ambiguity about the allocation of responsiblity among tools is an accident of timing. As I understand it, providing
Me neither. That's why the baggage is so narrowly scoped. It activates only (1) if TinyGo is in the producers section and (2) no Let me know if I can fill in more details about anything, and I'm eager to hear your feedback either way. (Take your time, as I'll be out Thursday and Friday.) Thanks! |
I'm sorry if I sound like a broken record but I still don't really understand the fundamental reason as to "why" for this. Strategies not working with
Yes to be clear I don't want to saddle you with the burden of improving this function. I mostly wanted to point out that I, probably like everyone else, have no room in my head for permanently understanding how this function works which means I have to reread it every time and it continues to be more of a beast than before.
Part of my point is that TinyGo is not alone here yet this problem has been solved for all other languages. Rust and C, for example, have WASIp1 toolchains that don't support
Personally I'm not swayed by this argument in that the implementation here looks a lot like user agents for browsers. If a toolchain in the future is struggling with this then it'd probably just pretend its TinyGo to get this code path to activate. In that sense to me it's an active downside to sniff the producers section and use that to determine whether this code path is active or not. I basically don't want to be party to a future where everyone's pretending to be TinyGo because that's easier than changing this tool or the language/sdk/etc. |
No problem. Apologies if I repeat things you've already heard as well. First, let me reiterate that our motivation is to support legacy wasm binaries. So fixes to SDKs or other pre-compilation tooling won't help.
Can you explain or point to that fix? Maybe there is indeed some insight I can reuse. I wasn't able to turn up anything definite or applicable after a brief search. I spent the morning digging into TinyGo's memory management code and the adaptation code, looking for an alternative approach. The crux is that TinyGo assumes it owns the entire heap, from Therefore, there's no way for the adapter to store its stuff at the far end of memory. But what about the near end? It might be possible to sock limited-length things (like the stack and Indeed, I believe adapters have every right to call Thus, as far as I can see, calling TinyGo's
I like contracts too! :-D That is indeed the way forward, but it doesn't help legacy binaries.
Yes, it certainly is an heuristic. If runtime crashing of componentized WASIp1 TinyGo programs is less ugly than the heuristic (and potential future misuse), we can fall back to an off-by-default flag, something like |
What I mostly don't understand about this is squaring this comment with:
For this latter point of crashing, I'm not sure what precisely you're trying to convey here. I'm no fan of crashing or subtle corruptions either, and I in no way want to perpetuate things. Questions I am still left with, however, are:
Basically these two motivations seem to be at odds. If fixing future cases is problematic that's where my questions about fixing SDKs and/or the toolchain come from. If fixing future cases is not necessary then that's where my motivation for making this a flag comes in.
The original fix was at WebAssembly/wasi-libc#377
This is not something I've actually considered before. My main hesitation is that the adapter is trying to assume as little as possible about the input module as it can to be as flexible as it can. You're correct though that if we can assume
I will again sort of come back to my original point here, but either way I don't necesarily agree with this. If only preexisting modules matter then there's the option of (a) using malloc or (b) using If future modules also matter then it comes back to the point of asking why can't I'll note I do realize that the
Well, personally, I also hope that everyone's able to solve their problem space in wasm quickly and efficiently and without pain. Apart from an emotional desire though I'd prefer to measure what's happening here based on its technical merits instead. Going back to the start of this comment, I do not know how to square what you're implying here. On one hand you're saying that only preexisting modules matter. On the other hand you're saying that if this is off-by-default then future developers will be plagued with hours of debugging due to forgetting this flag. I don't know how to connect these two situations because it seems like there is a single entity in control of all the modules which can turn on the flag irrespective of whatever the defaults are of
My guess is that this PR is taking more time and effort than you were originally expecting. If that's frustrating you I apologize for that, but at the same time at least in my own experience that's how OSS works out sometimes. What I can do here is reiterate that I'm not trying to be some nefarious entity whose sole purpose is to hobble the TinyGo ecosystem. Rather I see myself as the primary maintainer of this repository and tooling within. To do my job effectively I need to understand code landing in this repository, and I do not understand this code (specifically the requirement this is on-by-default). I'm trying to square this understanding and a textual medium of comments over PRs is only but so good at resolving differences, so I'm doing my best. |
I don't know if my piping up here is helpful, so please feel free to ignore, but it seems like maybe all parties could live with a compromise where And, the affected communities can create a tool (maybe part of wasm-tools or maybe not) that pre-processes the affected modules and adds a That could solve the issue where "the alternative is for everyone to independently discover, diagnose, and search out the solution to this" without special-casing on a producer name to change wit-component's behavior. |
That sounds reasonable to me! |
I'm fine falling back to rewriting the main module. There are 3 projects in my realm of responsibility that need this fix, so I'll need to factor it up someplace. Would you like it in wasm-tools? If yes, it seems like we might as well save people a step and activate it with a Does that sound good? I'll answer a few hanging threads just for completeness:
It's solely about preexisting binaries. Perhaps I just assume more parties have troves of preexisting third-party binaries than actually do; perhaps Fastly is unusual in this regard.
Yep, if we don't care about unlimited-size allocations (i.e. there's really only 1 adapter of consequence in the world), B could totally work. As you say, I'm not psyched to go tearing things apart even moreso at the moment, since this fix is part of a larger effort I need to get back to and since I fear breaking non-TinyGo things with a wider-scoped change. But let's hold it in reserve in case we need it in the future.
Again, no worries. I've been on both sides of this fence many times. Would you have a few minutes today to meet synchronously and make sure we're aligned? (I also want to make sure I'm not coming across like a nefarious interloper out to ruin your codebase.) |
I talked with @erikrose and our conclusions (correct me if I'm wrong) were:
|
Alex and I had a 20-minute talk and decided to…
|
@alexcrichton Early indications are that Big Go has the same problem as TinyGo, but it's less amenable to this solution, since it exposes no So maybe you should go ahead and release wit-component while we experiment. |
Should fix fastly/Viceroy#491. Please see that (plus commit messages) for context.