-
Notifications
You must be signed in to change notification settings - Fork 5
Add support for global variables #8
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
Comments
Hey, I'm trying to understand the request here. Can you please provide an example of what usage would look like? |
Sure. In the zig code, writing something like pub export var foo: bool = false; Gets you a Currently, I'm doing something like this, after instantiation the wasm module and passing it to the zjb generated glue. for (const key in zjb.exports) {
const exp = zjb.exports[key];
// check here is unnecessary for now since zjb.exports are always functions
if (exp instanceof Function) Game.prototype[key] = exp;
// but future code might add something like:
else Object.defineProperty(game, key, { get: () => zjb.exports[key], set: v => zjb.exports[key] = v });
} Sticking the functions into the prototype like that gives me easy access to them in my javascript-side code, ie functions like The glue code for variables then would need to do a similar thing as where you create array views, creating a view of the memory and then loading/storing values into it. This could be done either in generated |
Ah, yeah, I see the vision here. Here's what the steps to implement this look like: First verify the export behavior. Specifically, try to write some javascript code (probably just play around with the console in dev tools) which gets the value from the exports, and then uses a DataView of the WASM memory to pull out or set the value. Note that wasm is always little endian, so basically of the methods on dataview require a true as their last argument. What you're saying about how you think it works makes sense, but I haven't worked with those types of exports, so worth checking. From that point, there are broadly two parts which need to be done to work together: The zjb library code, and then code that writes the javascript. Between the two layers, all of the information is encoded in the import/export name. You'll need to come up with a prefix for the exported variables (functions use For the zjb library side: pub fn exportGlobal(parent: type, comptime name: []const u8) void {
const export_name = ...;
@export(@field(parent, name), .{ .name = export_name, });
} then usage would look like: var my_var: u8 = 0;
comptime {
zjb.exportGlobal(@Self(), "my_var");
} If none of that works, just make the api return a name based on the desired name and returns a std.builtin.ExportOptions, so the user can call @export in a comptime block themselves. This isn't preferred if it can be helped, though. On the codegen side:
Add some usage to the example, so we can test it out. Thanks for taking an interest in this, good luck, and let me know if you have any questions or hit anything tricky. |
Okay so I've dug in a bit here and run into a couple of issues.
Edit: Ack, I just re-read your post and saw you said "the length becomes zero." I suppose thats my answer for 2. |
It's pretty silly that The double pointer solution is a bit hacky, and less than ideal. One of the design constraints of this project is that things shouldn't be less efficient that writing the wasm glue code yourself. The only violations of this are However, I've done some digging and found ziglang/zig#14911, which is accepted. So this restriction is temporary, and the API should be what we want it to be after this restriction is lifted. Additionally, you should be able to follow the first pointer in the initialization code, so after a very slightly worse startup, the usage code will be just as efficient. Given that we want to eventually take a pointer due to the changes to @export, and that's the type we currently need to export anyways, require the value passed into and yeah, either look at the DataView's ArrayBuffer`s length, or keep track of the length of the wasm instance's memory and check if that value has changed. |
I think there's just one last thing to iron out here
This will require having access to the instantiated instance at initialization, because we'll need to access the exported address. A similar situation arises when storing a I think there's a few possible solutions here:
Though I will admit its a significant change to the api, personally I think 2 is best
Maybe there's another option I'm overlooking here, but thats my 2¢. Let me know what you think. |
current solution is less than optimal imo, see scottredig#8 (comment)
So there's already a post initialization step that's required: zjb.instance = results.instance; Change that to: zjb.setInstance(results.instance); The main reason to avoid option 2 is that it moves zjb from being a tool that can be used with wasm, to being more of a framework that dictates how you must work with wasm. That's a downgrade in compatibility with other requirements users may have. The other option which could be considered would be setting all of those values/functions to perform initialization, and overriding them with the real implementation, but there's no reason for that complexity when you can just do Also, a thought I had is that having a publicly usable dataView() function which returns a cached DataView is useful anyways, so don't hide that functionality away. |
As I said, its hard for me to imagine this facility being used any other way. Maybe you have plans I can't see, but from what I can tell the wasm code generated inherently depends on zjb in that the module instantiation requires an import object exactly matching zjb's, and accessing the exports with the names specified in zig requires a wrapper exactly matching zjb's export object. Besides simple things like sticking extra imports into the object before passing it to the module instantiation, which is a case that could be handled by allowing the user to pass extra imports to the zjb call; or customizing the memory used, which is another case that could be handled with configuration passed to the call; it seems to me that any work-around of the dependency would require not using the generated glue code at all even as it stands today. I don't think it'd actually introduce extra coupling, in practice. Anyhow, I'll leave that with you and go with |
This would be really handy to have. A similar interface as
exportFn
could be used, if necessary. I'm fairly new to WASM, but it looks like the exported variables as is are pointers into the memory buffer. So the generated glue code could use type info to build a property accessor or something? If you could provide some guidance on this, I'd be willing to give a go at implementing it.The text was updated successfully, but these errors were encountered: