-
Notifications
You must be signed in to change notification settings - Fork 44
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
access to Allocator inside wrapped Zig functions #40
Comments
I agree. I have a few ideas on how to do this, though I'll prioritize some bigger changes first |
I've been looking at this the last few days, and I'm not sure the best way to implement it. There is a lot of ambiguity. I'll list my thoughts, and I would appreciate any feedback! As @atweiden suggests, we could add a simple pub fn getAllocator(lua: *Lua) *std.mem.Allocator {
var data: *anyopaque = undefined;
_ = lua.getAllocFn(&data);
return ziglua.opaqueCast(std.mem.Allocator, data);
} This would be the simplest solution. But I think it would be nice if In addition to the standard If the Lua state is created with And in all cases, the existence of So here are the ideas I have:
|
I'm not sure how newStateLibc differs from setting up a Lua state with the c_allocator? I guess this can be a useful configuration in the sense that all lua allocs are guaranteed to be normal malloc/free/realloc without any Zig business in between. |
Thinking about this more, I am leaning towards a get function rather than directly accessing
I had not considered that case, and it does appear setAllocF can be dangerous if misused. After thinking for a while, I think one possible use case would be to temporarily swap out the alloc function (but not the backing allocator) to analyze the allocations? I could imagine some C code might want to swap out the allocator occasionally to track allocation information. There may be other uses. So even in C this seems like the kind of code where you need to trust the programmer to do the right thing. Another use case of Ziglua is to create shared compiled modules to be loaded into other Lua runtimes with require. In those cases there isn't a Zig allocator in the Lua state. This leads me to think a function to get the allocator is the best way forward. All of this reminds me of something related that I've been thinking about over the last year. Currently Ziglua accepts a So far this has worked fine, but I have wondered if it would be better to accept an allocator pointer in the init function. This seems to be less conventional in Zig, but the current code to store a pub fn main() anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
var lua = try Lua.init(&allocator);
defer lua.deinit();
// ... So long as the allocator that was used to create the Lua state stays in scope, this code would be safe. In other words, the programmer is now tasked with ensuring the allocator is available throughout the duration of the Lua state lifetime. I like this. It simplifies the Ziglua implementation slightly, and also opens things up for other simplifications in Ziglua (like making the Lua struct an opaque type instead). So my current plan is this:
Then in the future
I'll let these ideas sit for a few more days, then I will implement them unless we find issues in this plan. |
My initial reaction is that storing a pointer to an
Why does the |
I agree. I guess in the case of initializing a Lua state, I would hope a dangling pointer bug surfaces early and loud.
Lua supports custom allocators through the So, unless I'm mistaken, the only way to get a Zig allocator into this alloc function is through a pointer. The only question at this point is if the Lua state should hide the pointer internally (as is done currently) or make it explicit at the call site. |
I think I didn't get the benefit of you wanted to do this.. But maybe I do now. So you'd like to store the ptr in the C Lua state structure and if Zig code needs it, cast it to an I guess I'd be in support of something like this! |
Haha maybe I haven't been clear enough, since it seems like there is a disconnect. Let me be more detailed here. Current behaviorA Lua state is passed an Allocator (not a pointer) pub fn main() anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
var lua = try Lua.init(allocator);
defer lua.deinit();
// ... In the init code, an Allocator pointer is created. The provided allocator is copied to the pointer. The Allocator pointer (allocator_ptr) is passed to lua_newstate. So Lua is now storing a pointer to the allocator. /// Initialize a Lua state with the given allocator
pub fn init(allocator: Allocator) !Lua {
// the userdata passed to alloc needs to be a pointer with a consistent address
// so we allocate an Allocator struct to hold a copy of the allocator's data
const allocator_ptr = allocator.create(Allocator) catch return error.Memory;
allocator_ptr.* = allocator;
const state = c.lua_newstate(alloc, allocator_ptr) orelse return error.Memory;
return Lua{
.allocator = allocator_ptr,
.state = state,
};
} So currently Lua already uses the Zig allocator for all internal allocations. Proposed behaviorA Lua state is passed an Allocator pointer pub fn main() anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
var lua = try Lua.init(&allocator);
defer lua.deinit();
// ... The allocator pointer is passed directly to Lua. constCast is safe here because the pointer is never mutated inside of Lua (the lua_newstate parameter could be const). /// Initialize a Lua state with the given allocator
pub fn init(allocator: *const Allocator) !Lua {
const state = c.lua_newstate(alloc, @constCast(allocator)) orelse return error.Memory;
return Lua{.state = state};
} So in both the current and proposed implementation, Lua already has a pointer to a Zig allocator. This is already used for all internal Lua allocations. The only difference is how the pointer is created. I like the proposed solution because it makes it clear that Ziglua/Lua relies on a pointer to an allocator, rather than hiding that unconventional use. In hindsight, maybe bringing up this in this issue was a bad idea because it isn't 100% related to the main topic... |
OK, makes sense. I'll mention it here for completeness since the below style of initialization is not idiomatic either. To me an alternative would be to consider that it's the
I fully realize this is not the usual way to initialize new structs in Zig. I guess this style of |
Thanks for mentioning this alternative. While it is not the typical "init" pattern in Zig, it is good to consider. The biggest downside I see here (other than the more clunky initialization), is that it keeps the However, if we find in the future that the pointer is confusing or causes issues, I would consider this as the first alternative. So thanks again for sharing this approach! |
The Lua VM uses a void pointer to store some user data that is passed to the custom allocator function. This modifies Ziglua to require a pointer to be passed to the library rather than allocating the pointer internally. This is primarily motivated by clarity. This way makes it obvious that Ziglua diverges form the norm in Zig code and requires a pointer to an allocator. An additional benefit is the lua.allocator field is no longer required. This makes the struct smaller, and removes confusion (why is lua.allocator not always set?). Part of #40
When calling a wrapped Zig function from within Lua,
lua.allocator
seems to be always null:I think it's due to how
wrapZigFn
creates a new Lua context:I suppose it should somehow setup the
allocator
field too.IMO having a working allocator within the wrapped functions would be useful, so that I can go ahead and use whatever allocator is passed through the lua state.
The text was updated successfully, but these errors were encountered: