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

RFC: Built in buffer type and library #739

Merged
merged 18 commits into from
Oct 19, 2023

Conversation

Rerumu
Copy link
Contributor

@Rerumu Rerumu commented Nov 6, 2022

@grilme99
Copy link

grilme99 commented Dec 11, 2022

I want to add that this is a feature that Chickynoid, a server-authoritative character controller, would greatly benefit from. In our test bench for Chickynoid, we found that it could only reliably support up to ~60 players because the bottleneck was, somewhat surprisingly, packet serialization for the custom replication.

Chickynoid created a unique replication packet for each player at 20hz. Beyond 60 players, the time spent in each frame serializing packets exceeded the frame budget and caused massive performance degradation on the server. Unfortunately, there was no way around this because we were limited to slower Lua-side byte buffer implementations.

Our goal was to comfortably support 100 players in each match, but this was impossible without native byte buffers.

EDIT: Just to note, the byte buffer is still serialized to a string so that it can be sent over remotes. I'm not asking for direct binary support over Roblox remotes because that's out of scope here.

@ccuser44
Copy link
Contributor

ccuser44 commented Dec 11, 2022

👍 Outside of integers and floats, bytes should be supported as well for the datatypes. They are very common as well, and this could make working with certain strings more performant as it wouldn't add new strings to the heap when changing them (and making changing them easier)

(A byte is just a number with 8 bits)

@Fireboltofdeath
Copy link

8 bit integers (signed and unsigned) are already included in the RFC.

@Dekkonot
Copy link
Contributor

Existing solutions to working with binary data aren't unworkably slow, but they are incredibly inefficient in terms of memory. Well-made solutions are also prohibitively complex, so the number of people who can write and maintain them isn't very high. This has lead us to the point where as far as I know there's less than a dozen 'good' modules for working with binary data in the wild and they vary wildly in performance.

This is obviously not ideal under any circumstance but it's most impactful when writing performance-sensitive code like you do in game development. Something as simple as saving game data might use a custom binary format, and it pays to be both fast and efficient when serializing to it. Right now, that's not really an option though, so users of Luau are either being thorough with their storage formats or being quick. Not both.

While I'm hesitant to support an RFC that suggests adding a new 'primitive' type to Luau, I think it has to be done for binary manipulation to be done properly. Strings are far too slow to use for this, even though string.pack/string.unpack are as optimized as they reasonably can be. And tables aren't a great solution to this because of their overhead. A single byte in a table is not a single byte in-memory.

@Rerumu At the moment this feels like it's just a proposal to add native (resizable?) arrays and then a library to manipulate binary data on top of them. While obviously the implementation details would have to be bashed out, is there something I'm missing there? Because if not, this may benefit from being split into two RFCs: one for native arrays and one for binary functions.

@vegorov-rbx
Copy link
Collaborator

I would suggest proposing this as a library implemented with a userdata type.

@Rerumu
Copy link
Contributor Author

Rerumu commented Jan 17, 2023

At the moment this feels like it's just a proposal to add native (resizable?) arrays and then a library to manipulate binary data on top of them.

This could be the case, and it might be better to have a general solution, but I have not encountered a problem yet where Lua's built in arrays weren't appropriately fast or memory efficient except for the case of working directly on byte streams. I would be open to a general solution, I just feel as is, one wouldn't find much general use.

@Mooshua
Copy link

Mooshua commented Jan 27, 2023

Don't mean to be a pessimist here, but as Dekkonaut says Luau already has functionality like this: string.pack and string.unpack.

It uses an internal C API type called a "Buffer", which is a super fast append byte array which can be converted to a lua string. The format string's documentation can be found here.

I naively rewrote @grilme99 's motivating example, a server-authoritative character & movement library, to serialize it's CharacterData objects using string.pack and it was over 5x faster (65k w/s -> 300-500k w/s).

@alexmccord alexmccord added the rfc Language change proposal label Feb 10, 2023
@WheretIB
Copy link
Contributor

WheretIB commented Mar 4, 2023

I wonder if mutable strings used internally for string buffer operations (like in string.pack actually) can be exposed with a set of read/write builtin functions.
But that's probably a big enough difference to this RFC to require a separate design submission.

@Rerumu
Copy link
Contributor Author

Rerumu commented Mar 5, 2023

I wasn't aware of the mutable string stuff, so that raises a good question on whether it's worth exposing. Also, if there is a better design proposal that works I'd like to know what changes (or rewrite) of the RFC should be made. As long as the use case is covered I think how the API looks won't matter too much.

@ccuser44
Copy link
Contributor

ccuser44 commented Mar 6, 2023

Don't mean to be a pessimist here, but as Dekkonaut says Luau already has functionality like this: string.pack and string.unpack.

Luau needs to add better documentation for string.pack, string.packsize and string.unpack so people know they exist and how to use them. I think after such is done this RFC can be closed

Many people don't know about thwm and adding documentation would fix a lot of the afformentioned issues as people dont knoe about their existence

@Rerumu
Copy link
Contributor Author

Rerumu commented Mar 8, 2023

I think there has been a misunderstanding. I am aware of string.pack and string.unpack, but not that they already implemented mutable buffers internally that could potentially be exposed. In my RFC I explain why the string.* functions are simply inadequate for this use case.

@ambergamefam
Copy link

ambergamefam commented Mar 14, 2023

This looks exciting to see!

My main reason for not encoding things as strings/chars in Roblox projects is that non-UTF8 strings can't be serialized. APIs such as DataStoreService will throw an error when an invalid UTF8 grapheme is ever encountered in a string, and I believe you can't actually represent certain character sequences in strings in general. This leads to very specific edge cases that you probably won't encounter while developing and playtesting, but exploiters and unlucky users will, and could cause undefined behavior and player data corruption potentially.

Would be awesome if raw bytes could be sent through HTTP APIs on roblox in the future too.

@vegorov-rbx
Copy link
Collaborator

I wasn't aware of the mutable string stuff, so that raises a good question on whether it's worth exposing.

After looking into it, having a mutable string value will bring more issues if the LUA_TSTRING tag is reused for it.
And if we assign it a different tag, then it's not that different from adding a new type without any relation to strings.

To conclude, I think the best way is to implement this as a library that creates a tagged userdata object. Tag can be reserved similar to UTAG_PROXY.
Builtin fastcall functions can be added for functions that benefit the most from those specializations, initial implementation doesn't need to add any, so it might be considered out of scope for the RFC.

@Rerumu Rerumu marked this pull request as draft September 11, 2023 18:36
@vegorov-rbx
Copy link
Collaborator

vegorov-rbx commented Sep 11, 2023

I started an experimental implementation of this RFC as a library and there are some things to consider for this RFC.

To aim for the best performance and especially to be able to map function to fast-calls and later lower them to single instruction in our native code generation, removing the endianness from the API might be a good decision.
Luau is mostly used on little-endian platforms so I think it's ok to focus on methods that read/write LE data. Even on big-endian platforms, floating point data is sometimes stored in LE.
Additional argument increases complexity and makes it harder to match performance that JS engines achieve with ArrayBuffer.
For integers, having LE/BE switch methods in bit32 might be a good alternative.

We also need to explicitly say that unaligned reads are allowed. Once again, JS engines go the other way of requiring a Typed view so that the implementation is reading 32 bit integers at offsets that are multiple of 4, as an example. I believe this was relaxed in WebAssembly, but I don't have a citation on that.

For methods that are reading floating-point numbers I would like for us to have some flexibility around NaN values.
There are different NaNs, like quiet and signaling and other jit engines even use NaN tagging as a compact representation of values. While we are not affected by that right now, we should note that reading a non-canonical NaN number might still return a canonical NaN representation, just to give us a leeway in case we need it.

Finally, something to note in the drawbacks is that resizing will prevent us from embedding data inside the userdata and will require a secondary allocation (small-buffer optimization might help, but that's optional).
But even with SBO, potential for any function to resize the array the code is working on will reduce optimization opportunities we have in native code generation and will require the reload of the data pointer.
And also, since data allocation is separate, accesses are always indirect through that (can be improved with additional optimizations in the codegen).
Having written all this down, is it essential that we support resizing? Can we make it so that the resize returns a new object with data copied over (up to the new size)?
Are there enough use cases for resizable byte array? While I do enjoy my std::vector::push_back, we have a table in Luau for dynamic data like that and we can have a fixed buffer in this proposal with better performance characteristics. If the developer has a use-case where they don't know how much data to write, they can clone-resize and continue working with a new object.

@Rerumu
Copy link
Contributor Author

Rerumu commented Sep 12, 2023

For integers, having LE/BE switch methods in bit32 might be a good alternative.

Good catch, this solution is probably much better and I agree endianness should probably be handled outside the buffer. My only concern might be endianness of 64 bit integers as those would be truncated in bit32.

We also need to explicitly say that unaligned reads are allowed. Once again, JS engines go the other way of requiring a Typed view so that the implementation is reading 32 bit integers at offsets that are multiple of 4, as an example. I believe this was relaxed in WebAssembly, but I don't have a citation on that.

Yeah the WebAssembly memory instructions are explicit that unaligned access must succeed, but it's okay for it to be slower. It makes sense to add something like this to the RFC too.

For methods that are reading floating-point numbers I would like for us to have some flexibility around NaN values.

This I understand though I am a bit wary of; it is an admittedly niche use case but the WASM specification's reinterpret instructions require that bit patterns be preserved to and from float conversions. I have in fact faced issues around that in the past, as LuaJIT does NaN boxing for its value types, and it's resulted in memory corruption due to reading otherwise normal NaN values out of an FFI array. LLVM also somewhat commonly will generate code that relies on this property which eventually makes it down to the WASM output, and anecdotally I've found most of it to be in hashmap implementations.

Having written all this down, is it essential that we support resizing? Can we make it so that the resize returns a new object with data copied over (up to the new size)?

This seems reasonable too if it allows for much better optimization potential. The original use case was that of keeping a single buffer for reuse and expanding similar to vectors in native languages but I see now that it might not be as applicable to this sort of data structure.

@Mooshua
Copy link

Mooshua commented Sep 12, 2023

Is the goal of the implementation to avoid atomization or is it to simply allow strings to be constructed and deconstructed from smaller components?

As in, are we going to be making a brand new type that is completely separate from strings, or are we just making fancier APIs to encode data into a lua string type?

@Fireboltofdeath
Copy link

Fireboltofdeath commented Sep 12, 2023

As in, are we going to be making a brand new type that is completely separate from strings, or are we just making fancier APIs to encode data into a lua string type?

The buffer type would be a separate userdata type. There were some discussions about exposing the mutable strings used internally in the compiler, but it was decided against.

@vegorov-rbx
Copy link
Collaborator

vegorov-rbx commented Sep 26, 2023

I've written an experimental implementation of this here master...vegorov-rbx:luau:bytebuf
Includes all methods mentioned in the current RFC, fast calls and x64 lowering (no arm64 lowering).
Big-endian platform support is also not included, but I'll add it in a follow-up later.

RFC is currently lacking names, so I named the library buffer, and named methods as follows:

  • create
  • len
  • copy
  • readi8, readu8, readi16, readu16, readi32, readu32, readf32, readf64 and readstring
  • writei8, writeu8, writei16, writeu16, writei32, writeu32, writef32, writef64 and writestring

Fastcalls and IR is supported for:

  • readi8, readu8, readi32, readu32, readf32, readf64
  • writei8 (reused for writeu8), writei32, writeu32, writef32, writef64

Including names in the RFC will help bikeshed for better names in the discussion.

Some choices that are omitted from the RFC that were made:

  • create throws an error is size is not a positive integer or 0 (empty buffer is allowed to not trip out generic code)
  • copy uses source_buffer, source_offset, count, target_offset, target_buffer arguments, which is different from table.move that uses end_index instead of count. Something to argue about :)
  • offsets are converted from double to int with truncation, like string library functions. This is different from tables that check if index is a valid integer before going to array part. Since buffers don't need to care about double indices that table can store, it is not applicable here.
  • similarly, integer values are converted from double with truncation (nan can result in different values on different platforms, but that's the users fault)
  • unsigned values are converted from double using bit32 semantics
  • offsets are 0-based. I heard suggestions to make them 1-based to be consistent with string, but to also allocate 1 more element to avoid adding -1 everywhere in lowering like luajit tables do. But this kind of sounds like 0-based indexing with extra steps. Another thing to discuss!

@Rerumu
Copy link
Contributor Author

Rerumu commented Sep 26, 2023

I guess the most difficult problem now is the naming. I don't really know what to name it, buffer is straight to the point and works but I don't know the policies around naming things with what might already be common variable names. I'll take anything, honestly.

As for the choices, copy seems pretty straightforward. I'd still like to suggest instead target_buffer, target_offset, source_buffer, source_offset, count or even source_buffer, source_offset, target_buffer, target_offset, count. The rationale being it's often you see these functions implemented that way (ex. memcpy), often reminiscent of destination[..] = source[..] though again if others make sense it is what it is.

For doubles converted to ints, are they truncated to 32 bits or 64 bits? On Roblox it might be that 4gb+ buffers are never reasonable but in standalone Luau I can see the use case for it, and using the ~52 bit integer limit of doubles might make sense here.

Everything else looks good though. Should I update the RFC to include these specific changes?

@vegorov-rbx
Copy link
Collaborator

For doubles converted to ints, are they truncated to 32 bits or 64 bits?

32 bits.
I set the buffer limit to 1GB, it is consistent with the max string length (MAXSSIZE)
2GB-smallN is the limit for userdata size in this Luau implementation.
1GB is also the limit of table array allocation.

Extending to 2GB-smallN userdata limit is possible, extending further will require a separate feature request (that will require a strong use case of an actual project).

@vegorov-rbx
Copy link
Collaborator

Should I update the RFC to include these specific changes?

Yes. Let's also use 'buffer' as the name.
As for copy, your argument order sounds good, since I already moved away from table.move.

@vegorov-rbx
Copy link
Collaborator

I think we can avoid placing the specific memory limit in the specification and just have this 1GB as the current implementation limit.
Updates to the implementation can increase it in the future without rewriting how API behaves.

@Dekkonot
Copy link
Contributor

Dekkonot commented Sep 26, 2023

My initial concern with this is that constructing a buffer with a specific contents or dumping the contents is going to be boilerplate despite it being a core use of buffers. I'd like to propose something akin to buffer.fromstring(str: string): buffer for creating a buffer and something like buffer.tostring(): string for getting the contents as a string.

It's not terribly cumbersome to do either of these things with the functions as presented above, but I'd rather the boilerplate be removed entirely rather than exist in basically every use case where you need to work with pre-existing data or export the data.

@zeux
Copy link
Collaborator

zeux commented Oct 11, 2023

I don't think there's a definite right answer here, but I weigh much more heavily on the VM type side:

  • Right now userdata has a fairly strict separation between Luau implementation in its entirety (VM, CodeGen, Analysis, etc.) and embedding code. We expect Luau to provide generic mechanisms to work with userdata, and the host to expose new types via these mechanisms. The only current exception to this is newproxy which is a special tagged userdata because a) we can't afford to remove it for compatibility reasons, b) the default implementation has sandboxing risks. It's a deprecated legacy exception to the rule. Because we want buffers to have first class treatment in VM and CodeGen, it is more consistent to use a VM type.
  • Exposing buffers as a VM type eats a little bit into the VM complexity but I would argue that most of the rest of the feature is heavier and this is not a big deal. Which is to say, once we accept the feature itself with the associated complexity in terms of a standard available-everywhere library (similarly to how we pay complexity cost for exposing things like string matching engine or UTF8 support library), a host of fastcalls to accelerate access operation (duplicate implementation that needs to be optimized but also be memory safe), native IR support (ditto - duplicate implementation that needs to be fast & memory safe), then the extra bit of code to handle a very straightforward by nature type feels like a minor thing. Note, I'm not saying the complexity in the other components is unjustified - I think it is - I'm saying the extra integration of a first-class VM type doesn't register in my opinion.
  • The calculation would be a little different if we were able to externalize the type fully. That is, if fastcall and codegen mechanisms were fully pluggable and we could move all new 2K lines of implementation to a separate library that could be optionally included, that would probably push me the other way. But we don't have a realistic path (nor a strong desire) to go down this route, and if we did decide to do this we'd have lots of new problems to solve in terms of interface compatibility and extension points - the sum would probably be more complicated than what we have right now.
  • The integration of the type into VM does require a little bit of code (I mean this literally - there's not that many lines!), but this code is simple. It's fundamentally a byte buffer with no custom/complex memory management or GC semantics. Compare this to something like upvalues and weep.
  • We are not at the risk of running against the VM type limits in my estimation. This is the second new type we've added since we forked Lua 5.1 (after vectors), we have a total of 10 + 3 types right now (10 are user facing, 3 are internal), this will make it 11 + 3. As long as we maintain moderation and only add a new type every few years we'll realistically be just fine. In terms of hard limits, we currently have a hard limit of 16 user facing types due to table key encoding (TKey::tt), however we are actually reserving one bit in this structure "for future use" (TKey::next is 28 bits, but with the current maximum table size 27 bits are sufficient). So even if we somehow add buffers, then add 5 more types, and then decide to add one more, we can just steal a bit and permanently lock the max. table size at its current maximum of 2^26 entries = 2GB allocation... it's fine.
  • I fully agree with what you wrote above re: metatables as well. If this is a first-class VM type, we just don't give it a metatable by default, and the embedders can enhance the type if they really want to. If this is a custom userdata, we kinda want __type to be overridden, so now we need to create & register & store a metatable somewhere, and if embedders want to customize it it's fairly awkward. Plus the RFC text gets much shorter ;-)
  • UPDATE Oct 11: Also, right now we have some flexibility in terms of putting more tags/flags into userdata if we need them, because the userdata length is 32-bit and it feels likely that we can restrict the length here without affecting embedders (which would be sort of a breaking change of the embedding API but we would evaluate it and potentially would have an option to proceed). If we start using userdata for buffers with an inline allocation, we'd need the full 32-bits of the length, and if we need to expand userdata we would need to rework buffers to avoid the extra memory consumption - so decoupling these two types would hopefully make it a little easier to evolve either one.

All of this to say is that I think a new VM type is actually more cohesive with what we have right now, than a custom tag for a userdata that happens to correspond to a type/library that we support out of the box with tight runtime integration for performance. It's easier to explain - we're not just adding a buffer library that happens to be able to create buffer objects and by the way the runtime secretly knows about a userdata tag, we're just adding a buffer type which, just like string or table, has a first-class VM type, library, and runtime integration.

I also don't think we are at the risk of an explosive growth of types - not just because we have limits, and our current pace of adding types is slow, but also because we are fundamentally trying to keep the language and implementation reasonably small and expect most of the "extras" to be userdata types outside of the confines of language specification and implementation - which is to say, I think we should by default resist the addition of new types anyway, and in some sense making it easier to add new types to Luau-the-language (as opposed to, for example, Roblox-the-platform-that-embeds-Luau) is an anti-goal ;-)

(I do like this proposal and think we're getting close to being able to merge it.)

Rewrite design/motivation sections.
@zeux
Copy link
Collaborator

zeux commented Oct 11, 2023

I've also rewritten design/motivation sections (without changing the substance).

@zeux
Copy link
Collaborator

zeux commented Oct 11, 2023

Minor question about copy argument order:

buffer.copy(target_buffer: buffer, target_offset: number, source_buffer: buffer, source_offset: number, count: number) -> ()

I expect that at some point in the future we might want to add buffer.fill, whether or not this happens as part of this RFC. If we look at two signatures together, it makes me want to reorder buffer.copy to be target_buffer, target_offset, count, source_buffer, source_offset for symmetry so that we get this:

buffer.copy(target_buffer: buffer, target_offset: number, count: number, source_buffer: buffer, source_offset: number) -> ()
buffer.fill(target_buffer: buffer, target_offset: number, count: number, value: number) -> ()

Unsure if there are other considerations here (apart from table.move but that function has an ever confusing interface so maybe wise not to emulate it)

@vegorov-rbx
Copy link
Collaborator

Updated the RFC to switch from userdata type to a new tag value type.

Update also includes extra motivation for offsets starting at 0 and describes the C API.

@zeux
Copy link
Collaborator

zeux commented Oct 11, 2023

Re:

There is also a string buffer C API; by having functions talk about 'buffer' (like luaL_extendbuffer) and use luaL_Buffer, it might be a point of confusion for C API users.

Yeah, I agree this is not ideal. However, the luaL_Buffer API has been fairly idiosyncratic for a while. It's mostly an internal API for the implementation of various string libraries. It may be a little useful for embedders, but it's not ABI-stable, it's not API-stable (we've changed details compared to Lua 5.x when reworking these with no notice), and while you can still find a small subset between Lua 5.1, Lua 5.4 and Luau API that compiles everywhere (buffinit/addstring/addvalue/pushresult mainly...), the API is very sensitive to the particular stack arrangement due to boxloc.

aside: boxloc concern isn't purely theoretical as I hit some bugs around this recently when adding addvalueany...

I think we can probably improve this gradually. For example, we can call the type luaL_Strbuf, maintain luaL_Buffer as a compatibility typedef (and in the future mark it with a deprecated attribute), more clearly mark internal functions that we don't want to actually expose (and maybe even remove some of them from the header), improve boxloc by storing an absolute stack index inside the strbuf struct, etc.

We could do an analysis of open source libraries that target Luau API to see if they use luaL_Buffer or not to understand the impact of these changes better. mlua and LuaBridge3 don't use this and neither does Roblox so this gives me hope that this is something we can gradually rework to be nicer without too many issues.

@vegorov-rbx
Copy link
Collaborator

Updated the RFC:

buffer.copy source offset and count are now optional parameters, a whole buffer can be copied into the target using buffer.copy(target, offset, source) instead of buffer.copy(target, offset, source, 0, buffer.len(source)) which seems like good use case to address.
Additionaly, it is more familiar to memcpy.

Added buffer.fill. Placing count before the value didn't feel right (different from memset) and making count optional like in buffer.copy allows to fill the whole buffer using buffer.fill(target, 0, 0) instead of buffer.fill(target, 0, 0, buffer.len(target)) which is again, seems like a common use case.

For a moment, I thought about readbuffer/writebuffer operations mirroring the ones for string:

buffer.readbuffer(b: buffer, offset: number, count: number): buffer
buffer.writebuffer(b: buffer, offset: number, source: buffer, count: number?): ()

buffer.readstring(b: buffer, offset: number, count: number): string
buffer.writestring(b: buffer, offset: number, value: string, count: number?): ()

It could be seen that current copy would match writestring more closely than placing count in the middle.

@zeux
Copy link
Collaborator

zeux commented Oct 13, 2023

Thanks. I agree with buffer.fill addition - before it, a critical use case for buffer reuse was not covered well (if encoding individual structures repeatedly into smaller strings, you might want to avoid the cost of allocating a fresh buffer every time, but this typically requires clearing the buffer before reuse -- buffer.fill solves that).

I propose to lock this RFC for new functionality and just focus on adjusting details or finding inconsistencies in the existing function set from now on.

rfcs/type-byte-array.md Outdated Show resolved Hide resolved
@Dekkonot
Copy link
Contributor

Should the format of signed integers be specified in the RFC? We presumably all assume it will be two's complement, but I would also generally assume that IEEE-754 floats would be used and that was specified. It may be worth including it just for clarity.

rfcs/type-byte-array.md Outdated Show resolved Hide resolved
vegorov-rbx pushed a commit that referenced this pull request Oct 16, 2023
…ers (#1052)

This function might be of particular use if #739 is accepted, but as it
stands, it still brings a measurable reduction in complexity for
swapping byte order and is measurably faster (around a 50% reduction in
time per operation on my machine), albeit on the order of magnitude of
nanoseconds.
Simplified Alternatives section
@zeux
Copy link
Collaborator

zeux commented Oct 16, 2023

I've cleaned up and simplified alternatives section further.

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.

This looks good to me but let's wait for Oct 19th so that there's two full weeks between this RFC going into "in review" status and us merging it.

@vegorov-rbx vegorov-rbx merged commit 380bb70 into luau-lang:master Oct 19, 2023
Vighnesh-V added a commit that referenced this pull request Oct 21, 2023
### What's Changed

- Improve readability of unions and intersections by limiting the number
of elements of those types that can be presented on a single line (gated
under `FFlag::LuauToStringSimpleCompositeTypesSingleLine`)
- Adds a new option to the compiler `--record-stats` to record and
output compilation statistics
- `if...then...else` expressions are now optimized into `AND/OR` form
when possible.

### VM

- Add a new `buffer` type to Luau based on the [buffer
RFC](#739) and additional C API
functions to work with it; this release does not include the library.
- Internal C API to work with string buffers has been updated to align
with Lua version more closely

### Native Codegen

- Added support for new X64 instruction (rev) and new A64 instruction
(bswap) in the assembler
- Simplified the way numerical loop condition is translated to IR

### New Type Solver

- Operator inference now handled by type families
- Created a new system called `Type Paths` to explain why subtyping
tests fail in order to improve the quality of error messages.
- Systematic changes to implement Data Flow analysis in the new solver
(`Breadcrumb` removed and replaced with `RefinementKey`)

---
Co-authored-by: Aaron Weiss <[email protected]>
Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Andy Friesen <[email protected]>
Co-authored-by: Aviral Goel <[email protected]>
Co-authored-by: Lily Brown <[email protected]>
Co-authored-by: Vighnesh Vijay <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>

---------

Co-authored-by: Arseny Kapoulkine <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>
Co-authored-by: Andy Friesen <[email protected]>
Co-authored-by: Lily Brown <[email protected]>
Co-authored-by: Aaron Weiss <[email protected]>
Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Aviral Goel <[email protected]>
RomanKhafizianov added a commit to RomanKhafizianov/luau that referenced this pull request Nov 27, 2023
### What's Changed

- Improve readability of unions and intersections by limiting the number
of elements of those types that can be presented on a single line (gated
under `FFlag::LuauToStringSimpleCompositeTypesSingleLine`)
- Adds a new option to the compiler `--record-stats` to record and
output compilation statistics
- `if...then...else` expressions are now optimized into `AND/OR` form
when possible.

### VM

- Add a new `buffer` type to Luau based on the [buffer
RFC](luau-lang/luau#739) and additional C API
functions to work with it; this release does not include the library.
- Internal C API to work with string buffers has been updated to align
with Lua version more closely

### Native Codegen

- Added support for new X64 instruction (rev) and new A64 instruction
(bswap) in the assembler
- Simplified the way numerical loop condition is translated to IR

### New Type Solver

- Operator inference now handled by type families
- Created a new system called `Type Paths` to explain why subtyping
tests fail in order to improve the quality of error messages.
- Systematic changes to implement Data Flow analysis in the new solver
(`Breadcrumb` removed and replaced with `RefinementKey`)

---
Co-authored-by: Aaron Weiss <[email protected]>
Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Andy Friesen <[email protected]>
Co-authored-by: Aviral Goel <[email protected]>
Co-authored-by: Lily Brown <[email protected]>
Co-authored-by: Vighnesh Vijay <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>

---------

Co-authored-by: Arseny Kapoulkine <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>
Co-authored-by: Andy Friesen <[email protected]>
Co-authored-by: Lily Brown <[email protected]>
Co-authored-by: Aaron Weiss <[email protected]>
Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Aviral Goel <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfc Language change proposal
Development

Successfully merging this pull request may close these issues.