Skip to content
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

Add lua_setuserdatatag to update userdata tags #588

Merged
merged 1 commit into from
Jul 14, 2022
Merged

Conversation

khvzak
Copy link
Contributor

@khvzak khvzak commented Jul 14, 2022

It could be useful to update userdata tags, for example if change underlying type.

I plan to use this in Rust in mlua in order to mark userdata as destructed (and avoid running destructors).

@zeux
Copy link
Collaborator

zeux commented Jul 14, 2022

Are you using this in combination with lua_newuserdatadtor?

VM/src/lapi.cpp Outdated Show resolved Hide resolved
@khvzak
Copy link
Contributor Author

khvzak commented Jul 14, 2022

Are you using this in combination with lua_newuserdatadtor?

At the moment I use only lua_newuserdatadtor but planning to switch to lua_newuserdatatagged and then attach destructors by tags using lua_setuserdatadtor (with ability to change tags).

PS Also would be useful to remove/update inline destructors for userdata but seems the name lua_setuserdatadtor is already taken.

@khvzak khvzak requested a review from zeux July 14, 2022 17:19
@zeux
Copy link
Collaborator

zeux commented Jul 14, 2022

The reason I mention this is that the newly-added function has odd interaction with userdata with custom dtors (created via lua_newuserdatadtor).

It's impossible to change the status of "does the userdata have a dtor?" after its created because the dynamically specified dtor takes extra space. It would be possible to disable destructor for a userdata that was previously created with one, I suppose (but that would probably best be done with a different API).

This can be prevented by asserting that the tag of the userdata isn't equal to UTAG_IDTOR (or, more generally, that the userdata tag is less then UTAG_LIMIT).

Could you explain the use of this function in more detail? In general tags were added to make it easier for the host to identify the type of different userdata objects (which usually also means the size of the userdata is different), so it's a little odd to support tag switching - but maybe if I understand the full use case it would make more sense, or there would be some alternative solution.

@khvzak
Copy link
Contributor Author

khvzak commented Jul 14, 2022

@zeux

Could you explain the use of this function in more detail?

In mlua I support exposing Rust userdata types T to Lua, where T has a type identifier TypeId.
Each object of type T in Lua has attached a shared metatable with destructor (via __gc for normal Lua or using dtor for Luau).
Then mlua maintains a table of known userdata metatables:

HashMap<*const c_void, TypeId>

where *const c_void is an address of metatable for type T.

When user try to borrows an instance of T, mlua runs the following checks (simplified):

  1. Get a metatable of userdata object (lua_getmetatable).
  2. Get the table pointer (lua_topointer)
  3. Find TypeId by the pointer in the hashmap HashMap<*const c_void, TypeId>
  4. Check that TypeIf::of::<T>() == type_id_from_hashmap.

It works just fine, but also, in Rust, mlua supports transfering ownership of objects from Lua to Rust, using AnyUserData::take method (there are other ways too!).
Under the hood mlua calls ptr::read and then attaches a special destructed metatable without __gc method to skip destructors. In Luau I need to do the same but there is no way to bypass destructor, so each userdata object has an additional flag, used by dtor function, to skip destruction.

What I try to do, is to optimize this a bit and use advantage of tags provided by Luau:

  1. Maintain a vector Vec<TypeId> where each index is a tag (Tag => TypeId)
  2. When checking that userdata object is of type T, compare TypeId::of::<T>() == vec[tag_id]

For destructed userdata objects I planned to attach a tag 0 (for example), without dtor. There would be no benefits if I still need to call lua_getmetatable and then lua_topointer to find out that a pointer belongs to destructed metatable.
It's important to recognize destructed objects as mlua triggers a special UserDataDestructed error when trying to access them.

So I need a way to change tags assigned to userdata objects in order to mark them as destructed.

@zeux
Copy link
Collaborator

zeux commented Jul 14, 2022

Got it, thanks - this makes sense now, and seems like a good approach for this use-case.

Copy link
Collaborator

@zeux zeux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@zeux zeux merged commit e87009f into luau-lang:master Jul 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants