├── test_source ├── empty.lua ├── custom_signature.lua ├── big_endian.lua ├── little_endian.lua ├── dynamic_table.lua ├── constants.lua ├── for_loop.lua ├── variadic.lua ├── 32bit.lua ├── lua50.lua └── large_table.lua ├── .gitignore ├── .github ├── actions-rs │ └── grcov.yml ├── preview.png └── workflows │ ├── quality.yml │ └── tests.yml ├── test_files ├── 32bit.luab ├── empty.luab ├── lua50.luab ├── constants.luab ├── for_loop.luab ├── variadic.luab ├── big_endian.luab ├── large_table.luab ├── dynamic_table.luab ├── little_endian.luab └── custom_signature.luab ├── .config ├── extra_words.dic └── spellcheck.toml ├── src ├── serialization │ ├── mod.rs │ ├── macros.rs │ ├── writer.rs │ └── stream.rs ├── function │ ├── local.rs │ ├── instruction │ │ ├── mod.rs │ │ ├── interface.rs │ │ ├── settings.rs │ │ ├── macros.rs │ │ ├── lua50.rs │ │ ├── lua51.rs │ │ └── operand │ │ │ ├── mode.rs │ │ │ ├── mod.rs │ │ │ └── layout.rs │ ├── constant.rs │ ├── convert.rs │ ├── mod.rs │ ├── builder.rs │ └── upcast.rs ├── format │ ├── endianness.rs │ ├── width.rs │ ├── version.rs │ └── mod.rs ├── number.rs ├── error.rs └── lib.rs ├── rustfmt.toml ├── Cargo.toml ├── LICENSE.md └── README.md /test_source/empty.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /test_source/custom_signature.lua: -------------------------------------------------------------------------------- 1 | result = 9 2 | -------------------------------------------------------------------------------- /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | output-type: html 2 | -------------------------------------------------------------------------------- /.github/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/.github/preview.png -------------------------------------------------------------------------------- /test_files/32bit.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/32bit.luab -------------------------------------------------------------------------------- /test_files/empty.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/empty.luab -------------------------------------------------------------------------------- /test_files/lua50.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/lua50.luab -------------------------------------------------------------------------------- /test_files/constants.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/constants.luab -------------------------------------------------------------------------------- /test_files/for_loop.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/for_loop.luab -------------------------------------------------------------------------------- /test_files/variadic.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/variadic.luab -------------------------------------------------------------------------------- /test_files/big_endian.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/big_endian.luab -------------------------------------------------------------------------------- /test_files/large_table.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/large_table.luab -------------------------------------------------------------------------------- /test_files/dynamic_table.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/dynamic_table.luab -------------------------------------------------------------------------------- /test_files/little_endian.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/little_endian.luab -------------------------------------------------------------------------------- /test_files/custom_signature.luab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vE5li/lunify/HEAD/test_files/custom_signature.luab -------------------------------------------------------------------------------- /test_source/big_endian.lua: -------------------------------------------------------------------------------- 1 | sequence = { 1, 2, 3, 4, 5 } 2 | result = sequence[4] + sequence[5] + sequence[1] - 1 3 | -------------------------------------------------------------------------------- /test_source/little_endian.lua: -------------------------------------------------------------------------------- 1 | sequence = { 1, 2, 3, 4, 5 } 2 | result = sequence[4] + sequence[5] + sequence[1] - 1 3 | -------------------------------------------------------------------------------- /.config/extra_words.dic: -------------------------------------------------------------------------------- 1 | 9 2 | lua 3 | lunify 4 | endianness 5 | io 6 | variadic 7 | opcode 8 | globals 9 | upvalues 10 | + 11 | -------------------------------------------------------------------------------- /.config/spellcheck.toml: -------------------------------------------------------------------------------- 1 | dev_comments = true 2 | 3 | [Hunspell] 4 | use_builtin = true 5 | skip_os_lookups = false 6 | search_dirs = ["."] 7 | extra_dictionaries = ["extra_words.dic"] 8 | -------------------------------------------------------------------------------- /src/serialization/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod macros; 3 | mod stream; 4 | mod writer; 5 | 6 | pub(crate) use self::stream::ByteStream; 7 | pub(crate) use self::writer::ByteWriter; 8 | -------------------------------------------------------------------------------- /src/function/local.rs: -------------------------------------------------------------------------------- 1 | pub(crate) struct LocalVariable { 2 | pub(crate) name: String, 3 | pub(crate) start_program_counter: i64, 4 | pub(crate) end_program_counter: i64, 5 | } 6 | -------------------------------------------------------------------------------- /test_source/dynamic_table.lua: -------------------------------------------------------------------------------- 1 | table = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, unpack({11, 12}) } 2 | result = 1 3 | 4 | for k, v in ipairs(table) do 5 | result = result + v 6 | end 7 | 8 | result = result - 70 9 | -------------------------------------------------------------------------------- /test_source/constants.lua: -------------------------------------------------------------------------------- 1 | result = 11 2 | is_nil = nil 3 | is_true = true 4 | is_false = false 5 | 6 | if not is_nil then 7 | result = result - 1 8 | end 9 | 10 | if is_true and not is_false then 11 | result = result - 1 12 | end 13 | -------------------------------------------------------------------------------- /test_source/for_loop.lua: -------------------------------------------------------------------------------- 1 | result = 0 2 | table = {{2}, {2}} 3 | 4 | for v = 1, 2 do 5 | result = result + 1 6 | end 7 | 8 | for k, v in ipairs(table) do 9 | for _k, v in ipairs(v) do 10 | result = result + k + v; 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test_source/variadic.lua: -------------------------------------------------------------------------------- 1 | function test(first, second, ...) 2 | if first ~= "one" then 3 | return 0 4 | end 5 | if second ~= "two" then 6 | return 0 7 | end 8 | return arg[2] 9 | end 10 | 11 | result = test("one", "two", 8, 9, 10) 12 | -------------------------------------------------------------------------------- /test_source/32bit.lua: -------------------------------------------------------------------------------- 1 | function increment(value) 2 | value = 1 3 | 4 | for i = 1, 15 do 5 | value = value + 1 6 | end 7 | 8 | return value 9 | end 10 | 11 | result = increment(result) 12 | 13 | function closure() 14 | while result ~= 9 do 15 | result = result - 1 16 | end 17 | end 18 | 19 | closure() 20 | -------------------------------------------------------------------------------- /test_source/lua50.lua: -------------------------------------------------------------------------------- 1 | function increment(value) 2 | value = 1 3 | 4 | for i = 1, 15 do 5 | value = value + 1 6 | end 7 | 8 | return value 9 | end 10 | 11 | result = increment(result) 12 | 13 | function closure() 14 | while result ~= 9 do 15 | result = result - 1 16 | end 17 | end 18 | 19 | closure() 20 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | version = "Two" 3 | max_width = 140 4 | fn_call_width = 80 5 | condense_wildcard_suffixes = true 6 | format_macro_matchers = true 7 | format_strings = true 8 | imports_granularity = "Module" 9 | reorder_impl_items = true 10 | group_imports = "StdExternalCrate" 11 | use_field_init_shorthand = true 12 | overflow_delimited_expr = true 13 | wrap_comments = true 14 | -------------------------------------------------------------------------------- /src/function/instruction/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod macros; 3 | mod interface; 4 | /// Lua 5.0 settings. 5 | pub mod lua50; 6 | /// Lua 5.1 settings. 7 | pub mod lua51; 8 | mod operand; 9 | mod settings; 10 | 11 | pub(crate) use self::interface::LuaInstruction; 12 | pub(crate) use self::operand::{Bx, ConstantRegister, Generic, Register, SignedBx, Unused, BC}; 13 | pub use self::operand::{InstructionLayout, OperandType}; 14 | pub use self::settings::Settings; 15 | -------------------------------------------------------------------------------- /src/function/instruction/interface.rs: -------------------------------------------------------------------------------- 1 | use super::InstructionLayout; 2 | use crate::serialization::ByteStream; 3 | use crate::{LunifyError, Settings}; 4 | 5 | pub(crate) trait LuaInstruction: Sized { 6 | fn from_byte_stream(byte_stream: &mut ByteStream, settings: &Settings, layout: &InstructionLayout) -> Result; 7 | fn move_stack_accesses(&mut self, stack_start: u64, offset: i64); 8 | fn to_u64(&self, settings: &Settings) -> Result; 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lunify" 3 | authors = ["Lucas Pütz"] 4 | description = "A crate for converting Lua byte code to different versions and formats" 5 | categories = ["encoding"] 6 | keywords = ["lua", "bytecode"] 7 | version = "1.1.0" 8 | edition = "2021" 9 | homepage = "https://github.com/vE5li/lunify" 10 | repository = "https://github.com/vE5li/lunify" 11 | readme = "README.md" 12 | license = "MIT" 13 | exclude = ["test_files/", "LICENSE.md", "rustfmt.toml"] 14 | 15 | [dependencies] 16 | serde = { version = "1.0.144", features = ["serde_derive"], optional = true } 17 | mlua = { version = "0.8", features = ["lua51", "vendored"], optional = true } 18 | 19 | [features] 20 | debug = [] 21 | integration = ["mlua"] 22 | -------------------------------------------------------------------------------- /test_source/large_table.lua: -------------------------------------------------------------------------------- 1 | function nine() 2 | return 9 3 | end 4 | 5 | sequence = { 6 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 7 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 8 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 9 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 10 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 11 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 12 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 13 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 14 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 15 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 16 | "0", "1", "2", "3", "4", "5", "6", "7", "8", nine(), 17 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" 18 | } 19 | 20 | result = sequence[110] 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lucas Pütz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/function/instruction/settings.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::{lua50, lua51}; 5 | 6 | /// Lua 5.0 and Lua 5.1 compile constants. The Lua interpreter is compiled with 7 | /// certain predefined constants that affect how the byte code is generated. 8 | /// This structure represents a small subset of the constants that are relevant 9 | /// for Lunify. If the byte code you are trying to modify was complied with 10 | /// non-standard constants, you can use these settings to make it compatible. 11 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 13 | pub struct Settings<'a> { 14 | /// Lua 5.0 input compile constants. 15 | #[cfg_attr(feature = "serde", serde(borrow))] 16 | pub lua50: lua50::Settings<'a>, 17 | /// Lua 5.1 input compile constants. 18 | #[cfg_attr(feature = "serde", serde(borrow))] 19 | pub lua51: lua51::Settings<'a>, 20 | /// Emitted Lua 5.1 compile constants. 21 | #[cfg_attr(feature = "serde", serde(borrow))] 22 | pub output: lua51::Settings<'a>, 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: Code Quality 4 | 5 | jobs: 6 | check: 7 | name: Code Quality 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install stable toolchain 12 | uses: actions-rs/toolchain@v1 13 | with: 14 | profile: minimal 15 | toolchain: nightly 16 | override: true 17 | components: clippy, rustfmt 18 | 19 | - name: Check formatting 20 | uses: actions-rs/cargo@v1 21 | with: 22 | command: fmt 23 | args: --all --check 24 | 25 | - name: Run clippy 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: clippy 29 | args: --all -- -D warnings 30 | 31 | - name: Install cargo-spellcheck 32 | uses: taiki-e/install-action@v2 33 | with: 34 | tool: cargo-spellcheck 35 | 36 | - uses: actions/checkout@v3 37 | - name: Run cargo-spellcheck 38 | run: cargo spellcheck --code 1 39 | 40 | - name: Install cargo-deadlinks 41 | uses: taiki-e/install-action@v2 42 | with: 43 | tool: cargo-deadlinks 44 | 45 | - name: Run cargo-deadlinks 46 | run: cargo deadlinks -- --features integration 47 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: Tests 4 | 5 | jobs: 6 | lint: 7 | name: Tests 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: nightly 14 | override: true 15 | - uses: actions-rs/cargo@v1 16 | with: 17 | command: test 18 | args: --features integration --no-fail-fast 19 | env: 20 | CARGO_INCREMENTAL: '0' 21 | RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' 22 | RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' 23 | - uses: actions-rs/grcov@v0.1 24 | id: coverage 25 | - name: Copy coverage report 26 | run: | 27 | cp -r ${{ steps.coverage.outputs.report }} ${GITHUB_WORKSPACE}/coverage 28 | - uses: actions/upload-artifact@v3 29 | with: 30 | name: Coverage 31 | path: coverage 32 | - uses: popsiclestick/gist-sync-action@v1.2.0 33 | with: 34 | auth: ${{ secrets.GIST_TOKEN }} 35 | gist_url: https://gist.github.com/vE5li/173a7aa9ac7d5e82e238048e06aa99c2 36 | gist_title: lunify-coverage-badge.svg 37 | gist_description: Lunify coverage badge 38 | github_file: coverage/badges/flat.svg 39 | -------------------------------------------------------------------------------- /src/serialization/macros.rs: -------------------------------------------------------------------------------- 1 | /// Workaround until `*_le_bytes` and `*_be_bytes` are part of a trait. 2 | macro_rules! to_slice { 3 | ($writer:expr, $value:expr, $width:ident, $type32:ty) => { 4 | match ($writer.format.$width, $writer.format.endianness) { 5 | (BitWidth::Bit32, Endianness::Little) => $writer.slice(&($value as $type32).to_le_bytes()), 6 | (BitWidth::Bit32, Endianness::Big) => $writer.slice(&($value as $type32).to_be_bytes()), 7 | (BitWidth::Bit64, Endianness::Little) => $writer.slice(&$value.to_le_bytes()), 8 | (BitWidth::Bit64, Endianness::Big) => $writer.slice(&$value.to_be_bytes()), 9 | } 10 | }; 11 | } 12 | 13 | /// Workaround until `*_le_bytes` and `*_be_bytes` are part of a trait. 14 | macro_rules! from_slice { 15 | ($stream:expr, $width:expr, $endianness:expr, $type32:ty, $type64:ty) => {{ 16 | let endianness = $endianness; 17 | let slice = $stream.slice(u8::from($width) as usize)?; 18 | 19 | match (slice.len(), endianness) { 20 | (4, Endianness::Little) => <$type32>::from_le_bytes(slice.try_into().unwrap()) as $type64, 21 | (4, Endianness::Big) => <$type32>::from_be_bytes(slice.try_into().unwrap()) as $type64, 22 | (8, Endianness::Little) => <$type64>::from_le_bytes(slice.try_into().unwrap()), 23 | (8, Endianness::Big) => <$type64>::from_be_bytes(slice.try_into().unwrap()), 24 | _ => unreachable!(), 25 | } 26 | }}; 27 | } 28 | -------------------------------------------------------------------------------- /src/format/endianness.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::LunifyError; 5 | 6 | /// The endianness of Lua-internal types. 7 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 9 | pub enum Endianness { 10 | /// Least significant byte first. 11 | Big, 12 | /// Most significant byte first. 13 | Little, 14 | } 15 | 16 | impl TryFrom for Endianness { 17 | type Error = LunifyError; 18 | 19 | fn try_from(value: u8) -> Result { 20 | match value { 21 | 0 => Ok(Endianness::Big), 22 | 1 => Ok(Endianness::Little), 23 | endianness => Err(LunifyError::InvaildEndianness(endianness)), 24 | } 25 | } 26 | } 27 | 28 | impl From for u8 { 29 | fn from(value: Endianness) -> Self { 30 | match value { 31 | Endianness::Big => 0, 32 | Endianness::Little => 1, 33 | } 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::{Endianness, LunifyError}; 40 | 41 | #[test] 42 | fn big_endian() { 43 | assert_eq!(0.try_into(), Ok(Endianness::Big)); 44 | } 45 | 46 | #[test] 47 | fn small_endian() { 48 | assert_eq!(1.try_into(), Ok(Endianness::Little)); 49 | } 50 | 51 | #[test] 52 | fn unsupported_endianness() { 53 | assert_eq!(Endianness::try_from(2), Err(LunifyError::InvaildEndianness(2))); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lunify 2 | 3 | [![Tests](https://github.com/ve5li/lunify/workflows/Tests/badge.svg)](https://github.com/ve5li/lunify/actions?query=workflow%3ATests) 4 | [![Code Quality](https://github.com/ve5li/lunify/workflows/Code%20Quality/badge.svg)](https://github.com/ve5li/lunify/actions?query=workflow%3ACode+Quality) 5 | [![Test Coverage](https://raw.githubusercontent.com/gist/vE5li/173a7aa9ac7d5e82e238048e06aa99c2/raw/lunify-coverage-badge.svg)](https://github.com/ve5li/lunify/actions?query=workflow%3ATests) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 7 | [![crates.io](https://img.shields.io/crates/v/lunify.svg)](https://crates.io/crates/lunify) 8 | 9 | A crate for converting Lua byte code to different versions and formats. 10 | 11 | Currently Lua 5.0 and Lua 5.1 are supported inputs. 12 | 13 | # Example 14 | 15 | ```rust 16 | use lunify::{Format, LunifyError, Endianness, BitWidth, unify}; 17 | 18 | // Lua byte code in any suppored format 19 | let input_bytes = include_bytes!("../test_files/lua50.luab"); 20 | 21 | // Desired output format. May specify pointer width, endianness, sizes of datatypes, ... 22 | let output_format = Format { 23 | endianness: Endianness::Little, 24 | // Convert from byte code that runs on a 32 bit machine to byte code that runs on a 64 bit machine 25 | size_t_width: BitWidth::Bit64, 26 | ..Format::default() 27 | }; 28 | 29 | // Convert input bytes to the desired format 30 | let output_bytes = unify(input_bytes, &output_format, &Default::default()); 31 | ``` 32 | -------------------------------------------------------------------------------- /src/format/width.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[cfg(feature = "serde")] 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// The width of Lua-internal types in bits. 7 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 9 | pub enum BitWidth { 10 | /// 32 bits 11 | Bit32, 12 | /// 64 bits 13 | Bit64, 14 | } 15 | 16 | impl TryFrom for BitWidth { 17 | type Error = u8; 18 | 19 | fn try_from(value: u8) -> Result { 20 | match value { 21 | 4 => Ok(BitWidth::Bit32), 22 | 8 => Ok(BitWidth::Bit64), 23 | width => Err(width), 24 | } 25 | } 26 | } 27 | 28 | impl From for u8 { 29 | fn from(value: BitWidth) -> Self { 30 | match value { 31 | BitWidth::Bit32 => 4, 32 | BitWidth::Bit64 => 8, 33 | } 34 | } 35 | } 36 | 37 | impl Display for BitWidth { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | match self { 40 | BitWidth::Bit32 => write!(f, "32 bit"), 41 | BitWidth::Bit64 => write!(f, "64 bit"), 42 | } 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use crate::BitWidth; 49 | 50 | #[test] 51 | fn bit32() { 52 | assert_eq!(4.try_into(), Ok(BitWidth::Bit32)); 53 | } 54 | 55 | #[test] 56 | fn bit64() { 57 | assert_eq!(8.try_into(), Ok(BitWidth::Bit64)); 58 | } 59 | 60 | #[test] 61 | fn unsupported_width() { 62 | assert_eq!(BitWidth::try_from(6), Err(6)); 63 | } 64 | 65 | #[test] 66 | fn format_bit32() { 67 | assert_eq!(format!("{}", BitWidth::Bit32).as_str(), "32 bit"); 68 | } 69 | 70 | #[test] 71 | fn format_bit64() { 72 | assert_eq!(format!("{}", BitWidth::Bit64).as_str(), "64 bit"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/number.rs: -------------------------------------------------------------------------------- 1 | use crate::LunifyError; 2 | 3 | #[derive(Clone, Copy, Debug, PartialEq)] 4 | pub(crate) enum Number { 5 | /// Lua non-integral number type. 6 | Float(f64), 7 | /// Lua integral number type. 8 | Integer(i64), 9 | } 10 | 11 | impl Number { 12 | pub(crate) fn as_integer(self) -> Result { 13 | match self { 14 | Number::Integer(value) => Ok(value), 15 | Number::Float(value) => match value == value.round() { 16 | true => Ok(value as i64), 17 | false => Err(LunifyError::FloatPrecisionLoss), 18 | }, 19 | } 20 | } 21 | 22 | pub(crate) fn as_float(self) -> Result { 23 | match self { 24 | Number::Float(value) => Ok(value), 25 | Number::Integer(value) => match value < f64::MAX as i64 { 26 | true => Ok(value as f64), 27 | false => Err(LunifyError::IntegerOverflow), 28 | }, 29 | } 30 | } 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::Number; 36 | use crate::LunifyError; 37 | 38 | #[test] 39 | fn integer_as_integer() { 40 | let number = Number::Integer(10); 41 | assert_eq!(number.as_integer(), Ok(10)) 42 | } 43 | 44 | #[test] 45 | fn integer_as_float() { 46 | let number = Number::Integer(10); 47 | assert_eq!(number.as_float(), Ok(10.0)) 48 | } 49 | 50 | #[test] 51 | fn float_as_float() { 52 | let number = Number::Float(10.0); 53 | assert_eq!(number.as_float(), Ok(10.0)) 54 | } 55 | 56 | #[test] 57 | fn float_as_integer() { 58 | let number = Number::Float(10.0); 59 | assert_eq!(number.as_integer(), Ok(10)) 60 | } 61 | 62 | #[test] 63 | fn fraction_as_integer() { 64 | let number = Number::Float(10.5); 65 | assert_eq!(number.as_integer(), Err(LunifyError::FloatPrecisionLoss)) 66 | } 67 | 68 | #[test] 69 | fn i64_max_as_float() { 70 | let number = Number::Integer(i64::MAX); 71 | assert_eq!(number.as_float(), Err(LunifyError::IntegerOverflow)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/format/version.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::LunifyError; 4 | 5 | /// List of supported Lua versions. 6 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 7 | pub(crate) enum LuaVersion { 8 | /// Lua 5.0.* 9 | Lua50, 10 | /// Lua 5.1.* 11 | Lua51, 12 | } 13 | 14 | impl TryFrom for LuaVersion { 15 | type Error = LunifyError; 16 | 17 | fn try_from(value: u8) -> Result { 18 | match value { 19 | 0x51 => Ok(LuaVersion::Lua51), 20 | 0x50 => Ok(LuaVersion::Lua50), 21 | version => Err(LunifyError::UnsupportedVersion(version)), 22 | } 23 | } 24 | } 25 | 26 | impl From for u8 { 27 | fn from(value: LuaVersion) -> Self { 28 | match value { 29 | LuaVersion::Lua51 => 0x51, 30 | LuaVersion::Lua50 => 0x50, 31 | } 32 | } 33 | } 34 | 35 | impl Display for LuaVersion { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | match self { 38 | LuaVersion::Lua50 => write!(f, "Lua 5.0"), 39 | LuaVersion::Lua51 => write!(f, "Lua 5.1"), 40 | } 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::{LuaVersion, LunifyError}; 47 | 48 | #[test] 49 | fn lua51() { 50 | assert_eq!(0x51.try_into(), Ok(LuaVersion::Lua51)); 51 | } 52 | 53 | #[test] 54 | fn lua50() { 55 | assert_eq!(0x50.try_into(), Ok(LuaVersion::Lua50)); 56 | } 57 | 58 | #[test] 59 | fn unsupported_version() { 60 | assert_eq!(LuaVersion::try_from(0x52), Err(LunifyError::UnsupportedVersion(0x52))); 61 | } 62 | 63 | #[test] 64 | fn lua50_to_u8() { 65 | assert_eq!(u8::from(LuaVersion::Lua50), 0x50); 66 | } 67 | 68 | #[test] 69 | fn lua51_to_u8() { 70 | assert_eq!(u8::from(LuaVersion::Lua51), 0x51); 71 | } 72 | 73 | #[test] 74 | fn format_lua50() { 75 | assert_eq!(format!("{}", LuaVersion::Lua50).as_str(), "Lua 5.0"); 76 | } 77 | 78 | #[test] 79 | fn format_lua51() { 80 | assert_eq!(format!("{}", LuaVersion::Lua51).as_str(), "Lua 5.1"); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Error during [unify](super::unify). 5 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 7 | pub enum LunifyError { 8 | /// The specified instruction layout is not valid. This can happen when size 9 | /// limitations are not respected, when operand types are specified more 10 | /// than once, or when the B and C operands are not adjacent. 11 | InvalidInstructionLayout, 12 | /// The provided byte code does not start with the signature `\[27]Lua`. 13 | IncorrectSignature, 14 | /// The version of Lua that the byte code was compiled for is not supported 15 | /// by Lunify. 16 | UnsupportedVersion(u8), 17 | /// The specified format does not specify a valid endianness. 18 | InvaildEndianness(u8), 19 | /// The instruction memory layout is not supported by Lunify. This error can 20 | /// only occur in Lua 5.0. 21 | UnsupportedInstructionFormat([u8; 4]), 22 | /// The pointer width of the system that the byte code was compiled for is 23 | /// not supported by Lunify. 24 | UnsupportedSizeTWidth(u8), 25 | /// The integer width of the system that the byte code was compiled for is 26 | /// not supported by Lunify. 27 | UnsupportedIntegerWidth(u8), 28 | /// The instruction width of the system that the byte code was compiled for 29 | /// is not supported by Lunify. 30 | UnsupportedInstructionWidth(u8), 31 | /// The number width of the system that the byte code was compiled for 32 | /// is not supported by Lunify. 33 | UnsupportedNumberWidth(u8), 34 | /// The byte code contains an instruction that is not recognized by Lunify. 35 | InvalidOpcode(u64), 36 | /// The byte code contains a constant with a type that is not recognized by 37 | /// Lunify. 38 | InvalidConstantType(u8), 39 | /// The byte code contains a number with a non-integral value that can't be 40 | /// represented when `is_number_integral` is set to true. 41 | FloatPrecisionLoss, 42 | /// The byte code contains an integral value that is too big to be 43 | /// represented when `is_number_integral` is set to false. 44 | IntegerOverflow, 45 | /// The byte code is truncated. 46 | InputTooShort, 47 | /// The byte code has access padding. 48 | InputTooLong, 49 | /// The byte code generated by converting is using stack values that are 50 | /// bigger than Lua 5.1 `MAXSTACK`. 51 | StackTooLarge(u64), 52 | /// The byte code generated by converting to Lua 5.1 needs to store a value 53 | /// in an operand that exceed the maximum possible value. 54 | ValueTooBigForOperand, 55 | /// The Lua 5.0 `FORLOOP` instruction specified a positive jump, even though 56 | /// we expect it to always be negative. 57 | UnexpectedForwardJump, 58 | } 59 | -------------------------------------------------------------------------------- /src/function/instruction/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! lua_instructions { 2 | ($($vname:ident ( $mode:ty, $move_a:literal ),)*) => { 3 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 4 | pub(crate) enum Instruction { 5 | $($vname { a: u64, mode: $mode },)* 6 | } 7 | 8 | impl super::LuaInstruction for Instruction { 9 | // Needed because the compiler sees these functions as never being used and index as never being read. 10 | #[allow(dead_code, unused_assignments)] 11 | fn from_byte_stream(byte_stream: &mut crate::ByteStream, settings: &super::settings::Settings, layout: &InstructionLayout) -> Result { 12 | use super::operand::OperandGet; 13 | 14 | let value = byte_stream.instruction()?; 15 | let opcode: Opcode = OperandGet::::get(value, settings, layout); 16 | let a: A = OperandGet::::get(value, settings, layout); 17 | let mut index = 0; 18 | 19 | $( 20 | if opcode.0 == index { 21 | return Ok(Instruction::$vname { a: a.0, mode: OperandGet::::get(value, settings, layout) }); 22 | } 23 | index += 1; 24 | )* 25 | 26 | Err(crate::LunifyError::InvalidOpcode(opcode.0)) 27 | } 28 | 29 | #[allow(dead_code)] 30 | fn move_stack_accesses(&mut self, stack_start: u64, offset: i64) { 31 | use super::operand::OperandOffset; 32 | 33 | match self { 34 | $(Self::$vname { a, mode } => { 35 | if $move_a { 36 | let value = *a; 37 | if value >= stack_start { 38 | *a = (value as i64 + offset) as u64; 39 | } 40 | } 41 | mode.offset(stack_start, offset); 42 | },)* 43 | } 44 | } 45 | 46 | #[allow(dead_code, unused_assignments)] 47 | fn to_u64(&self, settings: &super::settings::Settings) -> Result { 48 | use super::operand::OperandPut; 49 | 50 | let mut index = 0; 51 | 52 | $( 53 | if let Instruction::$vname { a, mode } = self { 54 | let mut instruction = 0; 55 | instruction |= Opcode(index).put( settings)?; 56 | instruction |= A(*a).put( settings)?; 57 | instruction |= mode.put( settings)?; 58 | return Ok(instruction); 59 | } 60 | index += 1; 61 | )* 62 | 63 | unreachable!() 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/function/instruction/lua50.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::operand::{Bx, Generic, Opcode, SignedBx, A, BC}; 5 | use super::{ConstantRegister, InstructionLayout, OperandType, Register, Unused}; 6 | use crate::LunifyError; 7 | 8 | /// Lua 5.0 compile constants. The Lua interpreter is compiled with certain 9 | /// predefined constants that affect how the byte code is generated. This 10 | /// structure represents a small subset of the constants that are relevant for 11 | /// Lunify. If the byte code you are trying to modify was complied with 12 | /// non-standard constants, you can use these settings to make it compatible. 13 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 15 | pub struct Settings<'a> { 16 | /// Maximum number of elements that can be on the stack at the same time 17 | /// (`MAXSTACK`). 18 | pub stack_limit: u64, 19 | /// Number of elements to put on the stack before inserting a `SETLIST` 20 | /// instruction (`LFIELDS_PER_FLUSH`). 21 | pub fields_per_flush: u64, 22 | /// Lua binary file signature (`LUA_SIGNATURE`). 23 | pub binary_signature: &'a str, 24 | /// Memory layout of instructions inside the Lua byte code (`SIZE_*`, 25 | /// `POS_*`). 26 | pub layout: InstructionLayout, 27 | } 28 | 29 | impl<'a> Default for Settings<'a> { 30 | fn default() -> Self { 31 | Self { 32 | stack_limit: 250, 33 | fields_per_flush: 32, 34 | binary_signature: "\x1bLua", 35 | layout: InstructionLayout::from_specification([ 36 | OperandType::Opcode(6), 37 | OperandType::C(9), 38 | OperandType::B(9), 39 | OperandType::A(8), 40 | ]) 41 | .unwrap(), 42 | } 43 | } 44 | } 45 | 46 | lua_instructions! { 47 | Move(BC, true), 48 | LoadK(Bx, true), 49 | LoadBool(BC, true), 50 | LoadNil(BC, true), 51 | GetUpValue(BC, true), 52 | GetGlobal(Bx, true), 53 | GetTable(BC, true), 54 | SetGlobal(Bx, true), 55 | SetUpValue(BC, true), 56 | SetTable(BC, true), 57 | NewTable(BC, true), 58 | _Self(BC, true), 59 | Add(BC, true), 60 | Subtract(BC, true), 61 | Multiply(BC, true), 62 | Divide(BC, true), 63 | Power(BC, true), 64 | Unary(BC, true), 65 | Not(BC, true), 66 | Concatinate(BC, true), 67 | Jump(SignedBx, false), 68 | Equals(BC, false), 69 | LessThan(BC, false), 70 | LessEquals(BC, false), 71 | Test(BC, true), 72 | Call(BC, true), 73 | TailCall(BC, true), 74 | Return(BC, true), 75 | ForLoop(SignedBx, true), 76 | TForLoop(BC, true), 77 | TForPrep(SignedBx, true), 78 | SetList(Bx, true), 79 | SetListO(Bx, true), 80 | Close(BC, true), 81 | Closure(Bx, true), 82 | } 83 | -------------------------------------------------------------------------------- /src/function/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::number::Number; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub(crate) enum Constant { 5 | Nil, 6 | Boolean(bool), 7 | Number(Number), 8 | String(String), 9 | } 10 | 11 | pub(super) struct ConstantManager<'a> { 12 | pub(super) constants: &'a mut Vec, 13 | } 14 | 15 | impl<'a> ConstantManager<'a> { 16 | pub(super) fn create_unique(&mut self, program_counter: usize) -> u64 { 17 | let constant_index = self.constants.len() as u64; 18 | let mut index = 0; 19 | 20 | let constant = loop { 21 | let constant_name = format!("__%lunify%__temp{program_counter}_{index}\0"); 22 | let constant = Constant::String(constant_name); 23 | 24 | if !self.constants.contains(&constant) { 25 | break constant; 26 | } 27 | 28 | index += 1; 29 | }; 30 | 31 | self.constants.push(constant); 32 | constant_index 33 | } 34 | 35 | pub(super) fn constant_for_str(&mut self, constant_str: &'static str) -> u64 { 36 | let zero_terminated = format!("{constant_str}\0"); 37 | 38 | // If the constant already exists we don't need to add it again. 39 | let matches = |constant: &_| matches!(constant, Constant::String(string) if string == zero_terminated.as_str()); 40 | if let Some(index) = self.constants.iter().position(matches) { 41 | return index as u64; 42 | } 43 | 44 | let constant_index = self.constants.len() as u64; 45 | self.constants.push(Constant::String(zero_terminated)); 46 | constant_index 47 | } 48 | 49 | pub(super) fn constant_nil(&mut self) -> u64 { 50 | // If the constant already exists we don't need to add it again. 51 | let matches = |constant: &_| matches!(constant, Constant::Nil); 52 | if let Some(index) = self.constants.iter().position(matches) { 53 | return index as u64; 54 | } 55 | 56 | let constant_index = self.constants.len() as u64; 57 | self.constants.push(Constant::Nil); 58 | constant_index 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::{Constant, ConstantManager}; 65 | 66 | #[test] 67 | fn create_unique() { 68 | let mut constants = Vec::new(); 69 | let mut constant_manager = ConstantManager { constants: &mut constants }; 70 | 71 | assert_eq!(constant_manager.create_unique(9), 0); 72 | assert_eq!(&constants[0], &Constant::String("__%lunify%__temp9_0\0".to_owned())); 73 | } 74 | 75 | #[test] 76 | fn create_unique_twice() { 77 | let mut constants = vec![Constant::String("__%lunify%__temp9_0\0".to_owned())]; 78 | let mut constant_manager = ConstantManager { constants: &mut constants }; 79 | 80 | assert_eq!(constant_manager.create_unique(9), 1); 81 | assert_eq!(&constants[1], &Constant::String("__%lunify%__temp9_1\0".to_owned())); 82 | } 83 | 84 | #[test] 85 | fn constant_for_str() { 86 | let mut constants = vec![Constant::String("constant".to_owned())]; 87 | let mut constant_manager = ConstantManager { constants: &mut constants }; 88 | 89 | assert_eq!(constant_manager.constant_for_str("test"), 1); 90 | assert_eq!(&constants[1], &Constant::String("test\0".to_owned())); 91 | } 92 | 93 | #[test] 94 | fn constant_for_str_duplicate() { 95 | let mut constants = vec![Constant::String("test\0".to_owned()), Constant::String("constant".to_owned())]; 96 | let mut constant_manager = ConstantManager { constants: &mut constants }; 97 | 98 | assert_eq!(constant_manager.constant_for_str("test"), 0); 99 | } 100 | 101 | #[test] 102 | fn constant_nil() { 103 | let mut constants = vec![Constant::String("constant".to_owned())]; 104 | let mut constant_manager = ConstantManager { constants: &mut constants }; 105 | 106 | assert_eq!(constant_manager.constant_nil(), 1); 107 | assert_eq!(&constants[1], &Constant::Nil); 108 | } 109 | 110 | #[test] 111 | fn constant_nil_duplicate() { 112 | let mut constants = vec![Constant::Nil, Constant::String("constant".to_owned())]; 113 | let mut constant_manager = ConstantManager { constants: &mut constants }; 114 | 115 | assert_eq!(constant_manager.constant_nil(), 0); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/function/instruction/lua51.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | #[cfg(feature = "serde")] 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use super::operand::{Bx, ConstantRegister, Generic, Opcode, Register, SignedBx, Unused, A, BC}; 7 | use super::{InstructionLayout, OperandType}; 8 | use crate::LunifyError; 9 | 10 | /// Lua 5.1 compile constants. The Lua interpreter is compiled with certain 11 | /// predefined constants that affect how the byte code is generated. This 12 | /// structure represents a small subset of the constants that are relevant for 13 | /// Lunify. If the byte code you are trying to modify was complied with 14 | /// non-standard constants, you can use these settings to make it compatible. 15 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 16 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 17 | pub struct Settings<'a> { 18 | /// Maximum number of elements that can be on the stack at the same time 19 | /// (`MAXSTACK`). 20 | pub stack_limit: u64, 21 | /// Number of elements to put on the stack before inserting a `SETLIST` 22 | /// instruction (`LFIELDS_PER_FLUSH`). 23 | pub fields_per_flush: u64, 24 | /// Lua binary file signature (`LUA_SIGNATURE`). 25 | pub binary_signature: &'a str, 26 | /// Memory layout of instructions inside the Lua byte code (`SIZE_*`, 27 | /// `POS_*`). 28 | pub layout: InstructionLayout, 29 | } 30 | 31 | impl<'a> Default for Settings<'a> { 32 | fn default() -> Self { 33 | Self { 34 | fields_per_flush: 50, 35 | stack_limit: 250, 36 | binary_signature: "\x1bLua", 37 | layout: InstructionLayout::from_specification([ 38 | OperandType::Opcode(6), 39 | OperandType::A(8), 40 | OperandType::C(9), 41 | OperandType::B(9), 42 | ]) 43 | .unwrap(), 44 | } 45 | } 46 | } 47 | 48 | impl<'a> Settings<'a> { 49 | pub(crate) fn get_constant_bit(&self) -> u64 { 50 | 1 << (self.layout.b.size - 1) 51 | } 52 | 53 | pub(crate) fn get_maximum_constant_index(&self) -> u64 { 54 | self.get_constant_bit() - 1 55 | } 56 | } 57 | 58 | lua_instructions! { 59 | Move(BC, true), 60 | LoadK(Bx, true), 61 | LoadBool(BC, true), 62 | LoadNil(BC, true), 63 | GetUpValue(BC, true), 64 | GetGlobal(Bx, true), 65 | GetTable(BC, true), 66 | SetGlobal(Bx, true), 67 | SetUpValue(BC, true), 68 | SetTable(BC, true), 69 | NewTable(BC, true), 70 | _Self(BC, true), 71 | Add(BC, true), 72 | Subtract(BC, true), 73 | Multiply(BC, true), 74 | Divide(BC, true), 75 | Modulo(BC, true), 76 | Power(BC, true), 77 | Unary(BC, true), 78 | Not(BC, true), 79 | Length(BC, true), 80 | Concatinate(BC, true), 81 | Jump(SignedBx, false), 82 | Equals(BC, false), 83 | LessThan(BC, false), 84 | LessEquals(BC, false), 85 | Test(BC, true), 86 | TestSet(BC, true), 87 | Call(BC, true), 88 | TailCall(BC, true), 89 | Return(BC, true), 90 | ForLoop(SignedBx, true), 91 | ForPrep(SignedBx, true), 92 | TForLoop(BC, true), 93 | SetList(BC, true), 94 | Close(BC, true), 95 | Closure(Bx, true), 96 | VarArg(BC, true), 97 | } 98 | 99 | impl Instruction { 100 | /// Get the stack index that a given instruction will move data into. 101 | /// `SetTable` and `SetList` are technically not moving any data, but rather 102 | /// modifying it, but we need this behavior for detecting the correct 103 | /// `SetList` instruction inside the `upcast` function. 104 | pub(crate) fn stack_destination(&self) -> Option> { 105 | match *self { 106 | Instruction::Move { a, .. } => Some(a..a), 107 | Instruction::LoadK { a, .. } => Some(a..a), 108 | Instruction::LoadBool { a, .. } => Some(a..a), 109 | Instruction::LoadNil { a, mode: BC(b, _) } => Some(a..b.0), 110 | Instruction::GetUpValue { a, .. } => Some(a..a), 111 | Instruction::GetGlobal { a, .. } => Some(a..a), 112 | Instruction::GetTable { a, .. } => Some(a..a), 113 | Instruction::SetGlobal { .. } => None, 114 | Instruction::SetUpValue { .. } => None, 115 | Instruction::SetTable { a, .. } => Some(a..a), 116 | Instruction::NewTable { a, .. } => Some(a..a), 117 | Instruction::_Self { a, .. } => Some(a..a + 1), 118 | Instruction::Add { a, .. } => Some(a..a), 119 | Instruction::Subtract { a, .. } => Some(a..a), 120 | Instruction::Multiply { a, .. } => Some(a..a), 121 | Instruction::Divide { a, .. } => Some(a..a), 122 | Instruction::Modulo { a, .. } => Some(a..a), 123 | Instruction::Power { a, .. } => Some(a..a), 124 | Instruction::Unary { a, .. } => Some(a..a), 125 | Instruction::Not { a, .. } => Some(a..a), 126 | Instruction::Length { a, .. } => Some(a..a), 127 | Instruction::Concatinate { a, .. } => Some(a..a), 128 | Instruction::Jump { .. } => None, 129 | Instruction::Equals { .. } => None, 130 | Instruction::LessThan { .. } => None, 131 | Instruction::LessEquals { .. } => None, 132 | Instruction::Test { .. } => None, 133 | Instruction::TestSet { a, .. } => Some(a..a), 134 | Instruction::Call { a, mode: BC(_, c) } => Some(a..a + c.0 - 1), 135 | Instruction::TailCall { .. } => None, 136 | Instruction::Return { .. } => None, 137 | Instruction::ForLoop { a, .. } => Some(a..a + 3), 138 | Instruction::ForPrep { a, .. } => Some(a..a + 2), 139 | Instruction::TForLoop { a, mode: BC(_, c) } => Some(a..a + 2 + c.0), 140 | Instruction::SetList { a, .. } => Some(a..a), 141 | Instruction::Close { .. } => None, 142 | Instruction::Closure { a, .. } => Some(a..a), 143 | Instruction::VarArg { a, mode: BC(b, _) } => Some(a..a.max((a + b.0).saturating_sub(1))), 144 | } 145 | } 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use super::Settings; 151 | 152 | #[test] 153 | fn settings_get_constant_bit() { 154 | let settings = Settings::default(); 155 | assert_eq!(settings.get_constant_bit(), 1 << 8); 156 | } 157 | 158 | #[test] 159 | fn settings_get_maximum_constant_index() { 160 | let settings = Settings::default(); 161 | assert_eq!(settings.get_maximum_constant_index(), (1 << 8) - 1); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/function/instruction/operand/mode.rs: -------------------------------------------------------------------------------- 1 | use super::OperandLayout; 2 | use crate::{lua50, lua51, LunifyError, Settings}; 3 | 4 | pub(crate) trait ModeGet { 5 | fn get(value: u64, settings: &Settings, layout: &OperandLayout) -> Self; 6 | } 7 | 8 | pub(crate) trait ModePut { 9 | fn put(&self, settings: &Settings, layout: &OperandLayout) -> Result; 10 | } 11 | 12 | pub(crate) trait ModeOffset { 13 | fn offset(&mut self, _stack_start: u64, _offset: i64) {} 14 | } 15 | 16 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 17 | pub(crate) struct Unused; 18 | 19 | impl ModeGet for Unused { 20 | fn get(_value: u64, _settings: &Settings, _layout: &OperandLayout) -> Self { 21 | Unused 22 | } 23 | } 24 | 25 | impl ModePut for Unused { 26 | fn put(&self, _settings: &Settings, _layout: &OperandLayout) -> Result { 27 | Ok(0) 28 | } 29 | } 30 | 31 | impl ModeOffset for Unused {} 32 | 33 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 34 | pub(crate) struct Generic(pub u64); 35 | 36 | impl ModeGet for Generic { 37 | fn get(value: u64, _settings: &Settings, layout: &OperandLayout) -> Self { 38 | Generic(layout.get(value)) 39 | } 40 | } 41 | 42 | impl ModePut for Generic { 43 | fn put(&self, _settings: &Settings, layout: &OperandLayout) -> Result { 44 | layout.put(self.0) 45 | } 46 | } 47 | 48 | impl ModeOffset for Generic {} 49 | 50 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 51 | pub(crate) struct Register(pub u64); 52 | 53 | impl ModeGet for Register { 54 | fn get(value: u64, _settings: &Settings, layout: &OperandLayout) -> Self { 55 | Register(layout.get(value)) 56 | } 57 | } 58 | 59 | impl ModePut for Register { 60 | fn put(&self, _settings: &Settings, layout: &OperandLayout) -> Result { 61 | layout.put(self.0) 62 | } 63 | } 64 | 65 | impl ModeOffset for Register { 66 | fn offset(&mut self, stack_start: u64, offset: i64) { 67 | if self.0 >= stack_start { 68 | self.0 = (self.0 as i64 + offset) as u64; 69 | } 70 | } 71 | } 72 | 73 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 74 | pub(crate) struct ConstantRegister(pub u64, pub bool); 75 | 76 | impl ModeGet for ConstantRegister { 77 | fn get(value: u64, settings: &Settings, layout: &OperandLayout) -> Self { 78 | let mut value = layout.get(value); 79 | let is_constant = value >= settings.lua50.stack_limit; 80 | 81 | if is_constant { 82 | value -= settings.lua50.stack_limit; 83 | } 84 | 85 | ConstantRegister(value, is_constant) 86 | } 87 | } 88 | 89 | impl ModeGet for ConstantRegister { 90 | fn get(value: u64, settings: &Settings, layout: &OperandLayout) -> Self { 91 | let mut value = layout.get(value); 92 | let constant_bit = settings.lua51.get_constant_bit(); 93 | let is_constant = value & constant_bit != 0; 94 | 95 | if is_constant { 96 | value ^= constant_bit; 97 | } 98 | 99 | ConstantRegister(value, is_constant) 100 | } 101 | } 102 | 103 | impl ModePut for ConstantRegister { 104 | fn put(&self, settings: &Settings, layout: &OperandLayout) -> Result { 105 | if self.0 > settings.output.get_maximum_constant_index() { 106 | return Err(LunifyError::ValueTooBigForOperand); 107 | } 108 | 109 | let value = match self.1 { 110 | true => self.0 | settings.output.get_constant_bit(), 111 | false => self.0, 112 | }; 113 | 114 | layout.put(value) 115 | } 116 | } 117 | 118 | impl ModeOffset for ConstantRegister { 119 | fn offset(&mut self, stack_start: u64, offset: i64) { 120 | if self.0 >= stack_start && !self.1 { 121 | self.0 = (self.0 as i64 + offset) as u64; 122 | } 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::{ConstantRegister, Generic, ModeGet, ModeOffset, Register, Unused}; 129 | use crate::function::instruction::operand::mode::ModePut; 130 | use crate::{lua50, lua51, LunifyError, Settings}; 131 | 132 | fn mode_test_get(value: u64, expected: T) 133 | where 134 | T: ModeGet + Eq + std::fmt::Debug, 135 | { 136 | let settings = Settings::default(); 137 | let result: T = ModeGet::::get(value << 6, &settings, &settings.lua50.layout.c); 138 | assert_eq!(result, expected); 139 | } 140 | 141 | fn mode_test_put(value: T, expected: Result) 142 | where 143 | T: ModePut + Eq + std::fmt::Debug, 144 | { 145 | let settings = Settings::default(); 146 | let result = value.put(&settings, &settings.lua50.layout.c); 147 | assert_eq!(result.map(|value| value >> 6), expected); 148 | } 149 | 150 | fn mode_test_offset(mut value: T, base: u64, offset: i64, expected: T) 151 | where 152 | T: ModeOffset + Eq + std::fmt::Debug, 153 | { 154 | value.offset(base, offset); 155 | assert_eq!(value, expected); 156 | } 157 | 158 | #[test] 159 | fn unused_get() { 160 | mode_test_get::<_, ()>(0, Unused); 161 | } 162 | 163 | #[test] 164 | fn unused_put() { 165 | mode_test_put(Unused, Ok(0)); 166 | } 167 | 168 | #[test] 169 | fn generic_get() { 170 | mode_test_get::<_, ()>(1, Generic(1)); 171 | } 172 | 173 | #[test] 174 | fn generic_put() { 175 | mode_test_put(Generic(1), Ok(1)); 176 | } 177 | 178 | #[test] 179 | fn generic_offset() { 180 | mode_test_offset(Generic(4), 0, 5, Generic(4)); 181 | } 182 | 183 | #[test] 184 | fn register_get() { 185 | mode_test_get::<_, ()>(1, Register(1)); 186 | } 187 | 188 | #[test] 189 | fn register_put() { 190 | mode_test_put(Register(1), Ok(1)); 191 | } 192 | 193 | #[test] 194 | fn register_offset() { 195 | mode_test_offset(Register(4), 0, 5, Register(9)); 196 | } 197 | 198 | #[test] 199 | fn register_offset_below() { 200 | mode_test_offset(Register(4), 5, 5, Register(4)); 201 | } 202 | 203 | #[test] 204 | fn constant_register_get() { 205 | mode_test_get::<_, lua50::Instruction>(1, ConstantRegister(1, false)); 206 | } 207 | 208 | #[test] 209 | fn constant_register_get_constant_lua_50() { 210 | let settings = Settings::default(); 211 | mode_test_get::<_, lua50::Instruction>(1 + settings.lua50.stack_limit, ConstantRegister(1, true)); 212 | } 213 | 214 | #[test] 215 | fn constant_register_get_constant_lua_51() { 216 | let settings = Settings::default(); 217 | mode_test_get::<_, lua51::Instruction>(1 | settings.lua51.get_constant_bit(), ConstantRegister(1, true)); 218 | } 219 | 220 | #[test] 221 | fn constant_register_put() { 222 | mode_test_put(ConstantRegister(1, false), Ok(1)); 223 | } 224 | 225 | #[test] 226 | fn constant_register_put_value_too_big() { 227 | let settings = Settings::default(); 228 | mode_test_put( 229 | ConstantRegister(1 + settings.output.get_maximum_constant_index(), false), 230 | Err(LunifyError::ValueTooBigForOperand), 231 | ); 232 | } 233 | 234 | #[test] 235 | fn constant_register_offset() { 236 | mode_test_offset(ConstantRegister(4, false), 0, 5, ConstantRegister(9, false)); 237 | } 238 | 239 | #[test] 240 | fn constant_register_offset_below() { 241 | mode_test_offset(ConstantRegister(4, false), 5, 5, ConstantRegister(4, false)); 242 | } 243 | 244 | #[test] 245 | fn constant_register_offset_constant() { 246 | mode_test_offset(ConstantRegister(4, true), 0, 5, ConstantRegister(4, true)); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/function/instruction/operand/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{lua50, lua51, LunifyError, Settings}; 2 | 3 | mod layout; 4 | mod mode; 5 | 6 | pub(crate) use self::layout::OperandLayout; 7 | pub use self::layout::{InstructionLayout, OperandType}; 8 | pub(crate) use self::mode::{ConstantRegister, Generic, Register, Unused}; 9 | use self::mode::{ModeGet, ModeOffset, ModePut}; 10 | 11 | pub(crate) trait OperandGet { 12 | fn get(value: u64, settings: &Settings, layout: &InstructionLayout) -> Self; 13 | } 14 | 15 | pub(crate) trait OperandPut { 16 | fn put(self, settings: &Settings) -> Result; 17 | } 18 | 19 | pub(crate) trait OperandOffset { 20 | fn offset(&mut self, _stack_start: u64, _offset: i64) {} 21 | } 22 | 23 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 24 | pub(crate) struct Opcode(pub u64); 25 | 26 | impl OperandGet for Opcode { 27 | fn get(value: u64, _settings: &Settings, layout: &InstructionLayout) -> Self { 28 | Self(layout.opcode.get(value)) 29 | } 30 | } 31 | 32 | impl OperandPut for Opcode { 33 | fn put(self, settings: &Settings) -> Result { 34 | settings.output.layout.opcode.put(self.0) 35 | } 36 | } 37 | 38 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 39 | pub(crate) struct A(pub u64); 40 | 41 | impl OperandGet for A { 42 | fn get(value: u64, _settings: &Settings, layout: &InstructionLayout) -> Self { 43 | Self(layout.a.get(value)) 44 | } 45 | } 46 | 47 | impl OperandPut for A { 48 | fn put(self, settings: &Settings) -> Result { 49 | settings.output.layout.a.put(self.0) 50 | } 51 | } 52 | 53 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 54 | pub(crate) struct BC(pub B, pub C); 55 | 56 | impl OperandGet for BC 57 | where 58 | B: ModeGet, 59 | C: ModeGet, 60 | { 61 | fn get(value: u64, settings: &Settings, layout: &InstructionLayout) -> Self { 62 | let b = B::get(value, settings, &layout.b); 63 | let c = C::get(value, settings, &layout.c); 64 | Self(b, c) 65 | } 66 | } 67 | 68 | impl OperandGet for BC 69 | where 70 | B: ModeGet, 71 | C: ModeGet, 72 | { 73 | fn get(value: u64, settings: &Settings, layout: &InstructionLayout) -> Self { 74 | let b = B::get(value, settings, &layout.b); 75 | let c = C::get(value, settings, &layout.c); 76 | Self(b, c) 77 | } 78 | } 79 | 80 | impl OperandPut for BC 81 | where 82 | B: ModePut, 83 | C: ModePut, 84 | { 85 | fn put(self, settings: &Settings) -> Result { 86 | let b = self.0.put(settings, &settings.output.layout.b)?; 87 | let c = self.1.put(settings, &settings.output.layout.c)?; 88 | Ok(b | c) 89 | } 90 | } 91 | 92 | impl OperandOffset for BC 93 | where 94 | B: ModeOffset, 95 | C: ModeOffset, 96 | { 97 | fn offset(&mut self, stack_start: u64, offset: i64) { 98 | self.0.offset(stack_start, offset); 99 | self.1.offset(stack_start, offset); 100 | } 101 | } 102 | 103 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 104 | pub(crate) struct Bx(pub u64); 105 | 106 | impl OperandGet for Bx { 107 | fn get(value: u64, _settings: &Settings, layout: &InstructionLayout) -> Self { 108 | Self(layout.bx.get(value)) 109 | } 110 | } 111 | 112 | impl OperandPut for Bx { 113 | fn put(self, settings: &Settings) -> Result { 114 | settings.output.layout.bx.put(self.0) 115 | } 116 | } 117 | 118 | impl OperandOffset for Bx {} 119 | 120 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 121 | pub(crate) struct SignedBx(pub i64); 122 | 123 | impl OperandGet for SignedBx { 124 | fn get(value: u64, _settings: &Settings, layout: &InstructionLayout) -> Self { 125 | Self(layout.bx.get(value) as i64 - layout.signed_offset) 126 | } 127 | } 128 | 129 | impl OperandPut for SignedBx { 130 | fn put(self, settings: &Settings) -> Result { 131 | settings 132 | .output 133 | .layout 134 | .bx 135 | .put((self.0 + settings.output.layout.signed_offset) as u64) 136 | } 137 | } 138 | 139 | impl OperandOffset for SignedBx {} 140 | 141 | #[cfg(test)] 142 | mod tests { 143 | use super::{Generic, Opcode, OperandGet, OperandOffset, OperandPut, Register, A}; 144 | use crate::function::instruction::{Bx, ConstantRegister, SignedBx, BC}; 145 | use crate::{lua50, lua51, InstructionLayout, Settings}; 146 | 147 | fn operand_test(operand: T, value: u64) 148 | where 149 | T: OperandGet + OperandPut + Eq + std::fmt::Debug, 150 | { 151 | let settings = Settings::default(); 152 | assert_eq!(T::get(value, &settings, &settings.lua51.layout), operand); 153 | assert_eq!(operand.put(&settings), Ok(value)); 154 | } 155 | 156 | fn asymmetric_operand_test(settings: &Settings, layout: &InstructionLayout, operand: T, input_value: u64, output_value: u64) 157 | where 158 | T: OperandGet + OperandPut + Eq + std::fmt::Debug, 159 | { 160 | assert_eq!(T::get(input_value, settings, layout), operand); 161 | assert_eq!(operand.put(settings), Ok(output_value)); 162 | } 163 | 164 | fn operand_test_offset(mut operand: T, offset: i64, expected: T) 165 | where 166 | T: OperandOffset + Eq + std::fmt::Debug, 167 | { 168 | operand.offset(0, offset); 169 | assert_eq!(operand, expected); 170 | } 171 | 172 | #[test] 173 | fn opcode() { 174 | operand_test(Opcode(1), 1); 175 | } 176 | 177 | #[test] 178 | fn a() { 179 | operand_test(A(1), 1 << 6); 180 | } 181 | 182 | #[test] 183 | fn b() { 184 | operand_test(BC(Generic(1), Generic(0)), 1 << 23); 185 | } 186 | 187 | #[test] 188 | fn b_offset() { 189 | operand_test_offset(BC(Register(4), Register(4)), 5, BC(Register(9), Register(9))); 190 | } 191 | 192 | #[test] 193 | fn c() { 194 | operand_test(BC(Generic(0), Generic(1)), 1 << 14); 195 | } 196 | 197 | #[test] 198 | fn c_const_lua50() { 199 | let settings = Settings::default(); 200 | 201 | asymmetric_operand_test::<_, lua50::Instruction>( 202 | &settings, 203 | &settings.lua50.layout, 204 | BC(ConstantRegister(0, false), ConstantRegister(1, true)), 205 | (1 + settings.lua50.stack_limit) << 6, 206 | (1 | settings.output.get_constant_bit()) << 14, 207 | ); 208 | } 209 | 210 | #[test] 211 | fn c_const_lua51() { 212 | let mut settings = Settings::default(); 213 | // Set the size of B for Lua 5.1 to something smaller than the size of B for 214 | // output, so we can test that the constant bit is cleared and set 215 | // correctly. 216 | settings.lua51.layout.b.size = 5; 217 | 218 | asymmetric_operand_test::<_, lua51::Instruction>( 219 | &settings, 220 | &settings.lua51.layout, 221 | BC(ConstantRegister(0, false), ConstantRegister(1, true)), 222 | (1 | settings.lua51.get_constant_bit()) << 14, 223 | (1 | settings.output.get_constant_bit()) << 14, 224 | ); 225 | } 226 | 227 | #[test] 228 | fn bx() { 229 | operand_test(Bx(1), 1 << 14); 230 | } 231 | 232 | #[test] 233 | fn bx_offset() { 234 | operand_test_offset(Bx(10), 5, Bx(10)); 235 | } 236 | 237 | #[test] 238 | fn signed_bx() { 239 | operand_test(SignedBx(1), 131072 << 14); 240 | } 241 | 242 | #[test] 243 | fn signed_bx_offset() { 244 | operand_test_offset(SignedBx(10), 5, SignedBx(10)); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/serialization/writer.rs: -------------------------------------------------------------------------------- 1 | use crate::number::Number; 2 | use crate::{BitWidth, Endianness, Format, LunifyError}; 3 | 4 | pub(crate) struct ByteWriter<'a> { 5 | data: Vec, 6 | format: &'a Format, 7 | } 8 | 9 | impl<'a> ByteWriter<'a> { 10 | pub fn new(format: &'a Format) -> Self { 11 | let data = Vec::new(); 12 | Self { data, format } 13 | } 14 | 15 | pub fn byte(&mut self, byte: u8) { 16 | self.data.push(byte); 17 | } 18 | 19 | pub fn slice(&mut self, slice: &[u8]) { 20 | self.data.extend_from_slice(slice); 21 | } 22 | 23 | pub fn integer(&mut self, value: i64) { 24 | to_slice!(self, value, integer_width, i32) 25 | } 26 | 27 | pub fn size_t(&mut self, value: i64) { 28 | to_slice!(self, value, size_t_width, i32) 29 | } 30 | 31 | pub fn instruction(&mut self, instruction: u64) { 32 | to_slice!(self, instruction, instruction_width, u32) 33 | } 34 | 35 | pub fn number(&mut self, value: Number) -> Result<(), LunifyError> { 36 | match self.format.is_number_integral { 37 | true => to_slice!(self, value.as_integer()?, number_width, i32), 38 | false => to_slice!(self, value.as_float()?, number_width, f32), 39 | } 40 | Ok(()) 41 | } 42 | 43 | pub fn string(&mut self, value: &str) { 44 | self.size_t(value.len() as i64); 45 | self.slice(value.as_bytes()); 46 | } 47 | 48 | pub fn finalize(self) -> Vec { 49 | self.data 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::ByteWriter; 56 | use crate::number::Number; 57 | use crate::{BitWidth, Endianness, Format, LunifyError}; 58 | 59 | const TEST_FORMAT: Format = Format { 60 | format: 0, 61 | endianness: Endianness::Little, 62 | integer_width: BitWidth::Bit32, 63 | size_t_width: BitWidth::Bit32, 64 | instruction_width: BitWidth::Bit32, 65 | number_width: BitWidth::Bit32, 66 | is_number_integral: false, 67 | }; 68 | 69 | macro_rules! configuration { 70 | ($endianness:ident, $width:expr, $value:expr, $expected:expr) => { 71 | Configuration { 72 | expected: $expected.as_slice(), 73 | endianness: Endianness::$endianness, 74 | width: $width, 75 | is_number_integral: false, 76 | value: $value, 77 | } 78 | }; 79 | 80 | ($endianness:ident, $width:expr, $is_number_integral:expr, $value:expr, $expected:expr) => { 81 | Configuration { 82 | expected: $expected.as_slice(), 83 | endianness: Endianness::$endianness, 84 | width: $width, 85 | is_number_integral: $is_number_integral, 86 | value: $value, 87 | } 88 | }; 89 | } 90 | 91 | struct Configuration<'a, T> { 92 | expected: &'a [u8], 93 | endianness: Endianness, 94 | width: BitWidth, 95 | is_number_integral: bool, 96 | value: T, 97 | } 98 | 99 | impl Configuration<'_, T> { 100 | fn format(&self) -> Format { 101 | Format { 102 | endianness: self.endianness, 103 | integer_width: self.width, 104 | size_t_width: self.width, 105 | instruction_width: self.width, 106 | number_width: self.width, 107 | is_number_integral: self.is_number_integral, 108 | ..Default::default() 109 | } 110 | } 111 | } 112 | 113 | #[test] 114 | fn byte() { 115 | let mut writer = ByteWriter::new(&TEST_FORMAT); 116 | writer.byte(9); 117 | assert_eq!(writer.data, &[9]); 118 | } 119 | 120 | #[test] 121 | fn slice() { 122 | let mut writer = ByteWriter::new(&TEST_FORMAT); 123 | writer.slice(&[7, 8, 9]); 124 | assert_eq!(writer.data, &[7, 8, 9]); 125 | } 126 | 127 | #[test] 128 | fn integer() { 129 | let configurations = [ 130 | configuration!(Little, BitWidth::Bit32, 9, [9, 0, 0, 0]), 131 | configuration!(Big, BitWidth::Bit32, 9, [0, 0, 0, 9]), 132 | configuration!(Little, BitWidth::Bit64, 9, [9, 0, 0, 0, 0, 0, 0, 0]), 133 | configuration!(Big, BitWidth::Bit64, 9, [0, 0, 0, 0, 0, 0, 0, 9]), 134 | ]; 135 | 136 | for configuration in configurations { 137 | let format = configuration.format(); 138 | let mut writer = ByteWriter::new(&format); 139 | writer.integer(configuration.value); 140 | assert_eq!(writer.data, configuration.expected); 141 | } 142 | } 143 | 144 | #[test] 145 | fn size_t() { 146 | let configurations = [ 147 | configuration!(Little, BitWidth::Bit32, 9, [9, 0, 0, 0]), 148 | configuration!(Big, BitWidth::Bit32, 9, [0, 0, 0, 9]), 149 | configuration!(Little, BitWidth::Bit64, 9, [9, 0, 0, 0, 0, 0, 0, 0]), 150 | configuration!(Big, BitWidth::Bit64, 9, [0, 0, 0, 0, 0, 0, 0, 9]), 151 | ]; 152 | 153 | for configuration in configurations { 154 | let format = configuration.format(); 155 | let mut writer = ByteWriter::new(&format); 156 | writer.size_t(configuration.value); 157 | assert_eq!(writer.data, configuration.expected); 158 | } 159 | } 160 | 161 | #[test] 162 | fn instruction() { 163 | let configurations = [ 164 | configuration!(Little, BitWidth::Bit32, 9, [9, 0, 0, 0]), 165 | configuration!(Big, BitWidth::Bit32, 9, [0, 0, 0, 9]), 166 | configuration!(Little, BitWidth::Bit64, 9, [9, 0, 0, 0, 0, 0, 0, 0]), 167 | configuration!(Big, BitWidth::Bit64, 9, [0, 0, 0, 0, 0, 0, 0, 9]), 168 | ]; 169 | 170 | for configuration in configurations { 171 | let format = configuration.format(); 172 | let mut writer = ByteWriter::new(&format); 173 | writer.instruction(configuration.value); 174 | assert_eq!(writer.data, configuration.expected); 175 | } 176 | } 177 | 178 | #[test] 179 | fn number() -> Result<(), LunifyError> { 180 | let configurations = [ 181 | // Integer 182 | configuration!(Little, BitWidth::Bit32, true, Number::Integer(9), [9, 0, 0, 0]), 183 | configuration!(Big, BitWidth::Bit32, true, Number::Integer(9), [0, 0, 0, 9]), 184 | configuration!(Little, BitWidth::Bit64, true, Number::Integer(9), [9, 0, 0, 0, 0, 0, 0, 0]), 185 | configuration!(Big, BitWidth::Bit64, true, Number::Integer(9), [0, 0, 0, 0, 0, 0, 0, 9]), 186 | // Float 187 | configuration!(Little, BitWidth::Bit32, false, Number::Float(9.0), [0, 0, 16, 65]), 188 | configuration!(Big, BitWidth::Bit32, false, Number::Float(9.0), [65, 16, 0, 0]), 189 | configuration!(Little, BitWidth::Bit64, false, Number::Float(9.0), [0, 0, 0, 0, 0, 0, 34, 64]), 190 | configuration!(Big, BitWidth::Bit64, false, Number::Float(9.0), [64, 34, 0, 0, 0, 0, 0, 0]), 191 | ]; 192 | 193 | for configuration in configurations { 194 | let format = configuration.format(); 195 | let mut writer = ByteWriter::new(&format); 196 | writer.number(configuration.value)?; 197 | assert_eq!(writer.data, configuration.expected); 198 | } 199 | 200 | Ok(()) 201 | } 202 | 203 | #[test] 204 | fn string() { 205 | let mut writer = ByteWriter::new(&TEST_FORMAT); 206 | writer.string("LUA"); 207 | assert_eq!(writer.data, &[3, 0, 0, 0, b'L', b'U', b'A']); 208 | } 209 | 210 | #[test] 211 | fn finalize() { 212 | let writer = ByteWriter { 213 | data: vec![7, 8, 9], 214 | format: &TEST_FORMAT, 215 | }; 216 | assert_eq!(writer.finalize(), &[7, 8, 9]); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/format/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | mod endianness; 5 | mod version; 6 | mod width; 7 | 8 | pub use endianness::Endianness; 9 | pub(crate) use version::LuaVersion; 10 | pub use width::BitWidth; 11 | 12 | use crate::serialization::{ByteStream, ByteWriter}; 13 | use crate::{LunifyError, Settings}; 14 | 15 | /// Lua byte code format. 16 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 18 | pub struct Format { 19 | /// The format of the compiler. The standard is 0. 20 | pub format: u8, 21 | /// The endianness of the target system. 22 | pub endianness: Endianness, 23 | /// The width of an integer on the target system. 24 | pub integer_width: BitWidth, 25 | /// The size of a C `type_t` on the target system. Also the pointer width. 26 | pub size_t_width: BitWidth, 27 | /// The size of a Lua instruction inside the binary. 28 | pub instruction_width: BitWidth, 29 | /// The size of the Lua number type. 30 | pub number_width: BitWidth, 31 | /// If a Lua number is stored as an integer or a float. 32 | pub is_number_integral: bool, 33 | } 34 | 35 | impl Default for Format { 36 | fn default() -> Self { 37 | // By default we get the pointer width of the target system. 38 | let size_t_width = match cfg!(target_pointer_width = "64") { 39 | true => BitWidth::Bit64, 40 | false => BitWidth::Bit32, 41 | }; 42 | 43 | // By default we get the endianness of the target system. 44 | let endianness = match unsafe { *(&1u32 as *const u32 as *const u8) } { 45 | 0 => Endianness::Big, 46 | 1 => Endianness::Little, 47 | _ => unreachable!(), 48 | }; 49 | 50 | Self { 51 | format: 0, 52 | endianness, 53 | integer_width: BitWidth::Bit32, 54 | size_t_width, 55 | instruction_width: BitWidth::Bit32, 56 | number_width: BitWidth::Bit64, 57 | is_number_integral: false, 58 | } 59 | } 60 | } 61 | 62 | impl Format { 63 | pub(crate) fn from_byte_stream(byte_stream: &mut ByteStream, version: LuaVersion, settings: &Settings) -> Result { 64 | let format = match version { 65 | LuaVersion::Lua51 => byte_stream.byte()?, 66 | LuaVersion::Lua50 => 0, 67 | }; 68 | 69 | let endianness = byte_stream.byte()?.try_into()?; 70 | let integer_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedIntegerWidth)?; 71 | let size_t_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedSizeTWidth)?; 72 | let instruction_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedInstructionWidth)?; 73 | 74 | if version == LuaVersion::Lua50 { 75 | let instruction_format = byte_stream.slice(4)?; 76 | let expected = [ 77 | settings.lua50.layout.opcode.size as u8, 78 | settings.lua50.layout.a.size as u8, 79 | settings.lua50.layout.b.size as u8, 80 | settings.lua50.layout.c.size as u8, 81 | ]; 82 | 83 | if instruction_format != expected { 84 | return Err(LunifyError::UnsupportedInstructionFormat( 85 | instruction_format.try_into().unwrap(), 86 | )); 87 | } 88 | } 89 | 90 | let number_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedNumberWidth)?; 91 | 92 | let is_number_integral = match version { 93 | LuaVersion::Lua51 => byte_stream.byte()? == 1, 94 | LuaVersion::Lua50 => { 95 | // Is there even a way to get this information from Lua 5.0? 96 | let value = from_slice!(byte_stream, number_width, endianness, f32, f64); 97 | value != 31415926.535897933 98 | } 99 | }; 100 | 101 | #[cfg(feature = "debug")] 102 | { 103 | println!("format: {format}"); 104 | println!("endianness: {endianness:?}"); 105 | println!("integer_width: {integer_width}"); 106 | println!("size_t_width: {size_t_width}"); 107 | println!("instruction_width: {instruction_width}"); 108 | println!("number_width: {number_width}"); 109 | println!("is_number_integral: {is_number_integral}"); 110 | } 111 | 112 | Ok(Self { 113 | format, 114 | endianness, 115 | integer_width, 116 | size_t_width, 117 | instruction_width, 118 | number_width, 119 | is_number_integral, 120 | }) 121 | } 122 | 123 | pub(crate) fn write(&self, byte_writer: &mut ByteWriter) { 124 | byte_writer.byte(self.format); 125 | byte_writer.byte(self.endianness.into()); 126 | byte_writer.byte(self.integer_width.into()); 127 | byte_writer.byte(self.size_t_width.into()); 128 | byte_writer.byte(self.instruction_width.into()); 129 | byte_writer.byte(self.number_width.into()); 130 | byte_writer.byte(self.is_number_integral as u8); 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use super::LuaVersion; 137 | use crate::serialization::{ByteStream, ByteWriter}; 138 | use crate::{BitWidth, Endianness, Format, LunifyError, Settings}; 139 | 140 | const EXPECTED_FORMAT: Format = Format { 141 | format: 0, 142 | endianness: Endianness::Little, 143 | integer_width: BitWidth::Bit32, 144 | size_t_width: BitWidth::Bit64, 145 | instruction_width: BitWidth::Bit32, 146 | number_width: BitWidth::Bit64, 147 | is_number_integral: false, 148 | }; 149 | 150 | fn from_test_data(version: LuaVersion, bytes: &[u8]) -> Result { 151 | let mut byte_stream = ByteStream::new(bytes); 152 | let result = Format::from_byte_stream(&mut byte_stream, version, &Settings::default()); 153 | 154 | assert!(byte_stream.is_empty()); 155 | result 156 | } 157 | 158 | #[test] 159 | fn lua_51() -> Result<(), LunifyError> { 160 | let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 4, 8, 0])?; 161 | assert_eq!(result, EXPECTED_FORMAT); 162 | Ok(()) 163 | } 164 | 165 | #[test] 166 | fn lua50() -> Result<(), LunifyError> { 167 | let result = from_test_data(LuaVersion::Lua50, &[ 168 | 1, 4, 8, 4, 6, 8, 9, 9, 8, 0xB6, 0x09, 0x93, 0x68, 0xE7, 0xF5, 0x7D, 0x41, 169 | ])?; 170 | assert_eq!(result, EXPECTED_FORMAT); 171 | Ok(()) 172 | } 173 | 174 | #[test] 175 | fn write() { 176 | let mut byter_writer = ByteWriter::new(&EXPECTED_FORMAT); 177 | EXPECTED_FORMAT.write(&mut byter_writer); 178 | assert_eq!(byter_writer.finalize(), [0, 1, 4, 8, 4, 8, 0]); 179 | } 180 | 181 | #[test] 182 | fn lua50_unsupported_instruction_format() { 183 | let result = from_test_data(LuaVersion::Lua50, &[1, 4, 8, 4, 6, 9, 8, 9]); 184 | assert_eq!(result, Err(LunifyError::UnsupportedInstructionFormat([6, 9, 8, 9]))); 185 | } 186 | 187 | #[test] 188 | fn unsupported_integer_width() { 189 | let result = from_test_data(LuaVersion::Lua51, &[0, 1, 6]); 190 | assert_eq!(result, Err(LunifyError::UnsupportedIntegerWidth(6))); 191 | } 192 | 193 | #[test] 194 | fn unsupported_size_t_width() { 195 | let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 6]); 196 | assert_eq!(result, Err(LunifyError::UnsupportedSizeTWidth(6))); 197 | } 198 | 199 | #[test] 200 | fn unsupported_instruction_width() { 201 | let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 6]); 202 | assert_eq!(result, Err(LunifyError::UnsupportedInstructionWidth(6))); 203 | } 204 | 205 | #[test] 206 | fn unsupported_number_width() { 207 | let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 4, 6]); 208 | assert_eq!(result, Err(LunifyError::UnsupportedNumberWidth(6))); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![deny(rustdoc::broken_intra_doc_links)] 3 | #![deny(missing_docs)] 4 | 5 | mod error; 6 | mod number; 7 | #[macro_use] 8 | mod serialization; 9 | mod format; 10 | mod function; 11 | 12 | pub use error::LunifyError; 13 | pub use format::{BitWidth, Endianness, Format}; 14 | use function::Function; 15 | pub use function::{lua50, lua51, InstructionLayout, OperandType, Settings}; 16 | 17 | use crate::format::LuaVersion; 18 | use crate::serialization::{ByteStream, ByteWriter}; 19 | 20 | /// Takes Lua byte code in a supported format and converts it to byte code in 21 | /// the specified output [`Format`]. Returns [`LunifyError`] on error. 22 | pub fn unify(input_bytes: &[u8], output_format: &Format, settings: &Settings) -> Result, LunifyError> { 23 | let mut byte_stream = ByteStream::new(input_bytes); 24 | 25 | if !byte_stream.remove_signature(settings.lua50.binary_signature) && !byte_stream.remove_signature(settings.lua51.binary_signature) { 26 | return Err(LunifyError::IncorrectSignature); 27 | } 28 | 29 | let version = byte_stream.byte()?.try_into()?; 30 | 31 | #[cfg(feature = "debug")] 32 | { 33 | println!("\n======== Header ========"); 34 | println!("version: {version}"); 35 | } 36 | 37 | let input_format = Format::from_byte_stream(&mut byte_stream, version, settings)?; 38 | 39 | // If the input is already in the correct format, return it as is. 40 | if input_format == *output_format && !cfg!(test) { 41 | #[cfg(feature = "debug")] 42 | println!("\n======== Done ========\n"); 43 | 44 | return Ok(input_bytes.to_vec()); 45 | } 46 | 47 | byte_stream.set_format(input_format); 48 | 49 | let root_function = Function::from_byte_stream(&mut byte_stream, version, settings)?; 50 | 51 | if !byte_stream.is_empty() { 52 | return Err(LunifyError::InputTooLong); 53 | } 54 | 55 | let mut byte_writer = ByteWriter::new(output_format); 56 | 57 | byte_writer.slice(settings.output.binary_signature.as_bytes()); 58 | byte_writer.byte(LuaVersion::Lua51.into()); 59 | output_format.write(&mut byte_writer); 60 | root_function.write(&mut byte_writer)?; 61 | 62 | #[cfg(feature = "debug")] 63 | println!("======== Done ========\n"); 64 | 65 | Ok(byte_writer.finalize()) 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::{unify, Format, LunifyError}; 71 | use crate::{lua51, BitWidth, Endianness, Settings}; 72 | 73 | #[cfg(feature = "integration")] 74 | fn test_output(byte_code: &[u8]) { 75 | use mlua::prelude::*; 76 | 77 | let lua = Lua::new(); 78 | lua.load(byte_code).exec().unwrap(); 79 | assert_eq!(lua.globals().get::<_, LuaNumber>("result").unwrap(), 9.0); 80 | } 81 | 82 | #[test] 83 | fn _32bit_to_64bit() -> Result<(), LunifyError> { 84 | let input_bytes = include_bytes!("../test_files/32bit.luab"); 85 | let output_format = Format { 86 | size_t_width: BitWidth::Bit64, 87 | ..Default::default() 88 | }; 89 | unify(input_bytes, &output_format, &Default::default())?; 90 | 91 | Ok(()) 92 | } 93 | 94 | #[test] 95 | fn lua50_to_lua51() -> Result<(), LunifyError> { 96 | let input_bytes = include_bytes!("../test_files/lua50.luab"); 97 | let output_format = Format::default(); 98 | let _output_bytes = unify(input_bytes, &output_format, &Default::default())?; 99 | 100 | #[cfg(feature = "integration")] 101 | test_output(&_output_bytes); 102 | Ok(()) 103 | } 104 | 105 | #[test] 106 | fn matching_format_remains_unchanged() -> Result<(), LunifyError> { 107 | let input_bytes = include_bytes!("../test_files/32bit.luab"); 108 | let output_format = Format { 109 | endianness: Endianness::Little, 110 | size_t_width: BitWidth::Bit32, 111 | ..Default::default() 112 | }; 113 | let output_bytes = unify(input_bytes, &output_format, &Default::default())?; 114 | 115 | assert_eq!(&input_bytes[..], &output_bytes); 116 | Ok(()) 117 | } 118 | 119 | #[test] 120 | fn little_endian() -> Result<(), LunifyError> { 121 | let input_bytes = include_bytes!("../test_files/little_endian.luab"); 122 | let output_format = Format::default(); 123 | let _output_bytes = unify(input_bytes, &output_format, &Default::default())?; 124 | 125 | #[cfg(feature = "integration")] 126 | test_output(&_output_bytes); 127 | Ok(()) 128 | } 129 | 130 | #[test] 131 | fn big_endian() -> Result<(), LunifyError> { 132 | let input_bytes = include_bytes!("../test_files/big_endian.luab"); 133 | let output_format = Format::default(); 134 | let _output_bytes = unify(input_bytes, &output_format, &Default::default())?; 135 | 136 | #[cfg(feature = "integration")] 137 | test_output(&_output_bytes); 138 | Ok(()) 139 | } 140 | 141 | #[test] 142 | fn large_table() -> Result<(), LunifyError> { 143 | let input_bytes = include_bytes!("../test_files/large_table.luab"); 144 | let output_format = Format::default(); 145 | let _output_bytes = unify(input_bytes, &output_format, &Default::default())?; 146 | 147 | #[cfg(feature = "integration")] 148 | test_output(&_output_bytes); 149 | Ok(()) 150 | } 151 | 152 | #[test] 153 | fn dynamic_table() -> Result<(), LunifyError> { 154 | let input_bytes = include_bytes!("../test_files/dynamic_table.luab"); 155 | let output_format = Format::default(); 156 | let _output_bytes = unify(input_bytes, &output_format, &Default::default())?; 157 | 158 | #[cfg(feature = "integration")] 159 | test_output(&_output_bytes); 160 | Ok(()) 161 | } 162 | 163 | #[test] 164 | fn variadic() -> Result<(), LunifyError> { 165 | let input_bytes = include_bytes!("../test_files/variadic.luab"); 166 | let output_format = Format::default(); 167 | let _output_bytes = unify(input_bytes, &output_format, &Default::default())?; 168 | 169 | #[cfg(feature = "integration")] 170 | test_output(&_output_bytes); 171 | Ok(()) 172 | } 173 | 174 | #[test] 175 | fn for_loop() -> Result<(), LunifyError> { 176 | let input_bytes = include_bytes!("../test_files/for_loop.luab").to_vec(); 177 | let output_format = Format::default(); 178 | let _output_bytes = unify(&input_bytes, &output_format, &Default::default())?; 179 | 180 | #[cfg(feature = "integration")] 181 | test_output(&_output_bytes); 182 | Ok(()) 183 | } 184 | 185 | #[test] 186 | fn constants() -> Result<(), LunifyError> { 187 | let input_bytes = include_bytes!("../test_files/constants.luab").to_vec(); 188 | let output_format = Format::default(); 189 | let _output_bytes = unify(&input_bytes, &output_format, &Default::default())?; 190 | 191 | #[cfg(feature = "integration")] 192 | test_output(&_output_bytes); 193 | Ok(()) 194 | } 195 | 196 | #[test] 197 | fn custom_signature() -> Result<(), LunifyError> { 198 | let input_bytes = include_bytes!("../test_files/custom_signature.luab").to_vec(); 199 | let output_format = Format::default(); 200 | 201 | let settings = Settings { 202 | lua51: lua51::Settings { 203 | binary_signature: "\x1bLul", 204 | ..Default::default() 205 | }, 206 | ..Default::default() 207 | }; 208 | 209 | let _output_bytes = unify(&input_bytes, &output_format, &settings)?; 210 | 211 | #[cfg(feature = "integration")] 212 | test_output(&_output_bytes); 213 | Ok(()) 214 | } 215 | 216 | #[test] 217 | fn empty() -> Result<(), LunifyError> { 218 | let input_bytes = include_bytes!("../test_files/empty.luab"); 219 | let output_format = Format::default(); 220 | unify(input_bytes, &output_format, &Default::default())?; 221 | Ok(()) 222 | } 223 | 224 | #[test] 225 | fn incorrect_signature() { 226 | let output_format = Format::default(); 227 | let result = unify(b"\x1bLuo", &output_format, &Default::default()); 228 | 229 | assert_eq!(result, Err(LunifyError::IncorrectSignature)); 230 | } 231 | 232 | #[test] 233 | fn input_too_long() { 234 | let mut input_bytes = include_bytes!("../test_files/empty.luab").to_vec(); 235 | input_bytes.extend_from_slice(b"extra bytes"); 236 | 237 | let output_format = Format::default(); 238 | let result = unify(&input_bytes, &output_format, &Default::default()); 239 | 240 | assert_eq!(result, Err(LunifyError::InputTooLong)); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/function/instruction/operand/layout.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::LunifyError; 5 | 6 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 7 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 8 | pub(crate) struct OperandLayout { 9 | pub(crate) size: u64, 10 | pub(crate) position: u64, 11 | pub(crate) bit_mask: u64, 12 | } 13 | 14 | impl OperandLayout { 15 | pub(crate) fn new(size: u64, position: u64) -> Self { 16 | let bit_mask = !0 >> (64 - size); 17 | Self { size, position, bit_mask } 18 | } 19 | 20 | pub(crate) fn get(&self, value: u64) -> u64 { 21 | (value >> self.position) & self.bit_mask 22 | } 23 | 24 | pub(crate) fn put(&self, value: u64) -> Result { 25 | let maximum_value = (1 << self.size) - 1; 26 | 27 | if value > maximum_value { 28 | return Err(LunifyError::ValueTooBigForOperand); 29 | } 30 | 31 | Ok((value & self.bit_mask) << self.position) 32 | } 33 | } 34 | 35 | /// All possible operands of an instruction. 36 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 37 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 38 | pub enum OperandType { 39 | /// Size of the Opcode (`SIZE_O`P). 40 | Opcode(u64), 41 | /// Size of the A operand (`SIZE_A`). 42 | A(u64), 43 | /// Size of the B operand (`SIZE_B`). 44 | B(u64), 45 | /// Size of the C operand (`SIZE_C`). 46 | C(u64), 47 | } 48 | 49 | /// Memory layout of instructions inside the Lua byte code (`SIZE_*`, `POS_*`). 50 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 51 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 52 | pub struct InstructionLayout { 53 | pub(crate) opcode: OperandLayout, 54 | pub(crate) a: OperandLayout, 55 | pub(crate) b: OperandLayout, 56 | pub(crate) c: OperandLayout, 57 | pub(crate) bx: OperandLayout, 58 | pub(crate) signed_offset: i64, 59 | } 60 | 61 | impl InstructionLayout { 62 | /// Create a memory layout from a list of [`OperandType`]s. 63 | ///# Example 64 | /// 65 | ///```rust 66 | /// use lunify::{InstructionLayout, OperandType}; 67 | /// 68 | /// // This is how the Lua 5.0 InstructionLayout is created. 69 | /// let layout = InstructionLayout::from_specification([ 70 | /// OperandType::Opcode(6), 71 | /// OperandType::C(9), 72 | /// OperandType::B(9), 73 | /// OperandType::A(8) 74 | /// ]); 75 | /// ``` 76 | pub fn from_specification(specification: [OperandType; 4]) -> Result { 77 | let mut opcode = None; 78 | let mut a = None; 79 | let mut b = None; 80 | let mut c = None; 81 | let mut offset = 0; 82 | 83 | for operand in specification { 84 | match operand { 85 | OperandType::Opcode(size) => { 86 | // The minimum is 6 because there are 38 instructions in Lua 5.1, so we need a 87 | // minimum of `ceil(log2(38))` bits to represent them all. 88 | if !(6..32).contains(&size) || opcode.is_some() { 89 | return Err(LunifyError::InvalidInstructionLayout); 90 | } 91 | 92 | opcode = Some(OperandLayout::new(size, offset)); 93 | offset += size; 94 | } 95 | OperandType::A(size) => { 96 | // The minimum is 7 because A needs to be able to hold values up to 97 | // Lua 5.1 `MAXSTACK`. 98 | if !(7..32).contains(&size) || a.is_some() { 99 | return Err(LunifyError::InvalidInstructionLayout); 100 | } 101 | 102 | a = Some(OperandLayout::new(size, offset)); 103 | offset += size; 104 | } 105 | OperandType::B(size) => { 106 | // The minimum is 8 because B needs to be able to hold values up to 107 | // Lua 5.1 `MAXSTACK`, plus an additional bit for constant values. 108 | if !(8..32).contains(&size) || b.is_some() { 109 | return Err(LunifyError::InvalidInstructionLayout); 110 | } 111 | 112 | b = Some(OperandLayout::new(size, offset)); 113 | offset += size; 114 | } 115 | OperandType::C(size) => { 116 | // The minimum is 8 because C needs to be able to hold values up to 117 | // Lua 5.1 `MAXSTACK`, plus an additional bit for constant values. 118 | if !(8..32).contains(&size) || c.is_some() { 119 | return Err(LunifyError::InvalidInstructionLayout); 120 | } 121 | 122 | c = Some(OperandLayout::new(size, offset)); 123 | offset += size; 124 | } 125 | } 126 | } 127 | 128 | let opcode = opcode.unwrap(); 129 | let a = a.unwrap(); 130 | let b = b.unwrap(); 131 | let c = c.unwrap(); 132 | 133 | // Make sure that B and C are next to each other. 134 | if c.position + c.size != b.position && b.position + b.size != c.position { 135 | return Err(LunifyError::InvalidInstructionLayout); 136 | } 137 | 138 | let bx_size = b.size + c.size; 139 | let bx_position = u64::min(b.position, c.position); 140 | let bx = OperandLayout::new(bx_size, bx_position); 141 | let signed_offset = (!0u64 >> (64 - bx_size + 1)) as i64; 142 | 143 | Ok(Self { 144 | opcode, 145 | a, 146 | b, 147 | c, 148 | bx, 149 | signed_offset, 150 | }) 151 | } 152 | } 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | use crate::function::instruction::operand::OperandLayout; 157 | use crate::{InstructionLayout, LunifyError, OperandType}; 158 | 159 | #[test] 160 | fn layout_new() { 161 | let layout = OperandLayout::new(8, 6); 162 | let expected = OperandLayout { 163 | size: 8, 164 | position: 6, 165 | bit_mask: 0b11111111, 166 | }; 167 | 168 | assert_eq!(layout, expected); 169 | } 170 | 171 | #[test] 172 | fn layout_get() { 173 | let layout = OperandLayout::new(2, 2); 174 | assert_eq!(layout.get(0b11100), 0b11); 175 | } 176 | 177 | #[test] 178 | fn layout_put() { 179 | let layout = OperandLayout::new(2, 2); 180 | assert_eq!(layout.put(0b11), Ok(0b1100)); 181 | } 182 | 183 | #[test] 184 | fn layout_put_out_of_bounds() { 185 | let layout = OperandLayout::new(2, 2); 186 | assert_eq!(layout.put(0b111), Err(LunifyError::ValueTooBigForOperand)); 187 | } 188 | 189 | #[test] 190 | fn from_specification() -> Result<(), LunifyError> { 191 | let layout = 192 | InstructionLayout::from_specification([OperandType::Opcode(6), OperandType::C(9), OperandType::B(9), OperandType::A(8)])?; 193 | 194 | assert_eq!(layout.opcode.position, 0); 195 | assert_eq!(layout.a.position, 24); 196 | assert_eq!(layout.b.position, 15); 197 | assert_eq!(layout.c.position, 6); 198 | assert_eq!(layout.bx.position, 6); 199 | assert_eq!(layout.bx.size, layout.b.size + layout.c.size); 200 | assert_eq!(layout.signed_offset, 131071); 201 | Ok(()) 202 | } 203 | 204 | #[test] 205 | fn from_specification_opcode_twice() { 206 | let result = 207 | InstructionLayout::from_specification([OperandType::Opcode(6), OperandType::Opcode(6), OperandType::B(9), OperandType::A(8)]); 208 | assert_eq!(result, Err(LunifyError::InvalidInstructionLayout)); 209 | } 210 | 211 | #[test] 212 | fn from_specification_a_twice() { 213 | let result = 214 | InstructionLayout::from_specification([OperandType::Opcode(6), OperandType::A(8), OperandType::B(9), OperandType::A(8)]); 215 | assert_eq!(result, Err(LunifyError::InvalidInstructionLayout)); 216 | } 217 | 218 | #[test] 219 | fn from_specification_b_twice() { 220 | let result = 221 | InstructionLayout::from_specification([OperandType::Opcode(6), OperandType::B(9), OperandType::B(9), OperandType::A(8)]); 222 | assert_eq!(result, Err(LunifyError::InvalidInstructionLayout)); 223 | } 224 | 225 | #[test] 226 | fn from_specification_c_twice() { 227 | let result = 228 | InstructionLayout::from_specification([OperandType::Opcode(6), OperandType::C(9), OperandType::C(9), OperandType::A(8)]); 229 | assert_eq!(result, Err(LunifyError::InvalidInstructionLayout)); 230 | } 231 | 232 | #[test] 233 | fn from_specification_b_and_c_seperated() { 234 | let result = 235 | InstructionLayout::from_specification([OperandType::Opcode(6), OperandType::C(9), OperandType::A(9), OperandType::B(8)]); 236 | assert_eq!(result, Err(LunifyError::InvalidInstructionLayout)); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/function/convert.rs: -------------------------------------------------------------------------------- 1 | use super::builder::FunctionBuilder; 2 | use crate::function::instruction::{Generic, LuaInstruction, BC}; 3 | use crate::{lua51, LunifyError, Settings}; 4 | 5 | pub(crate) fn convert( 6 | instructions: Vec, 7 | line_info: Vec, 8 | maximum_stack_size: &mut u8, 9 | settings: &Settings, 10 | ) -> Result<(Vec, Vec), LunifyError> { 11 | // If `fields_per_flush` is the same, there is nothing to convert, so return 12 | // early. 13 | if settings.lua51.fields_per_flush == settings.output.fields_per_flush { 14 | return Ok((instructions, line_info)); 15 | } 16 | 17 | let mut builder = FunctionBuilder::default(); 18 | 19 | for (instruction, line_number) in instructions.into_iter().zip(line_info) { 20 | #[cfg(feature = "debug")] 21 | println!("[{}] {:?}", builder.get_program_counter(), instruction); 22 | 23 | builder.set_line_number(line_number); 24 | 25 | match instruction { 26 | lua51::Instruction::SetList { a, mode: BC(b, c) } => { 27 | let flat_index = b.0 + (settings.lua51.fields_per_flush * (c.0 - 1)); 28 | let page = flat_index / settings.output.fields_per_flush; 29 | let offset = flat_index % settings.output.fields_per_flush; 30 | 31 | // If b was 0 before, we need to keep it that way. 32 | let b = match b.0 { 33 | 0 => 0, 34 | _ => offset, 35 | }; 36 | 37 | // Good case: we are on the first page and the number of entries is smaller than 38 | // either `LFIELDS_PER_FLUSH`, meaning we can just insert a `SETLIST` 39 | // instruction without any modification to the previous code. 40 | if page == 0 && flat_index <= u64::min(settings.lua51.fields_per_flush, settings.output.fields_per_flush) { 41 | builder.instruction(lua51::Instruction::SetList { 42 | a, 43 | mode: BC(Generic(b), Generic(1)), 44 | }); 45 | continue; 46 | } 47 | 48 | // Go back until we find some instruction that moves data to a stack position 49 | // that is the same as our A, because that is where the setup starts. 50 | for instruction_index in (0..(builder.get_program_counter() - 1)).rev() { 51 | let instruction = builder.get_instruction(instruction_index); 52 | 53 | // It might technically be possible for the element on slot A to be on the stack 54 | // already before any instructions if it is a parameter to a function call. So 55 | // we make sure that at least the first instruction will always match. 56 | // I am unsure that code like this can actually be emitted by the Lua compiler, 57 | // because any assignment of a table should start with a `NEWTABLE` instruction, 58 | // but better safe than sorry. 59 | if matches!(instruction.stack_destination(), Some(destination) if destination.start == a) || instruction_index == 0 { 60 | // Should either be `NEWTABLE` or `SETLIST`. 61 | if let lua51::Instruction::SetList { mode: BC(b, c), .. } = *instruction { 62 | let mut offset = b.0 as i64; 63 | let mut page = c.0; 64 | 65 | // Remove the `SETLIST` instruction. 66 | builder.remove_instruction(instruction_index); 67 | 68 | // Go back up the stack and update the stack positions. 69 | let mut instruction_index = instruction_index; 70 | while instruction_index < builder.get_program_counter() { 71 | let instruction = builder.get_instruction(instruction_index); 72 | 73 | if let Some(stack_destination) = instruction.stack_destination() { 74 | if offset + stack_destination.start as i64 - 1 == (a + settings.output.fields_per_flush) as i64 { 75 | // Add a new `SETLIST` instruction. 76 | builder.insert_extra_instruction(instruction_index, lua51::Instruction::SetList { 77 | a, 78 | mode: BC(Generic(settings.output.fields_per_flush), Generic(page)), 79 | }); 80 | 81 | offset -= settings.output.fields_per_flush as i64; 82 | page += 1; 83 | instruction_index += 1; 84 | continue; 85 | } 86 | } 87 | 88 | builder.get_instruction(instruction_index).move_stack_accesses(a, offset); 89 | instruction_index += 1; 90 | } 91 | } 92 | 93 | break; 94 | } 95 | } 96 | 97 | // Append the original instruction. 98 | builder.instruction(lua51::Instruction::SetList { 99 | a, 100 | mode: BC(Generic(b), Generic(page + 1)), 101 | }); 102 | } 103 | instruction => builder.instruction(instruction), 104 | }; 105 | } 106 | 107 | builder.finalize(maximum_stack_size, settings) 108 | } 109 | 110 | #[cfg(test)] 111 | mod tests { 112 | use super::{lua51, BC}; 113 | use crate::function::convert; 114 | use crate::function::instruction::{Bx, Generic, Unused}; 115 | use crate::{lua50, LunifyError, Settings}; 116 | 117 | fn test_settings() -> Settings<'static> { 118 | let lua50 = lua50::Settings::default(); 119 | 120 | let lua51 = lua51::Settings { 121 | fields_per_flush: 5, 122 | ..lua51::Settings::default() 123 | }; 124 | 125 | let output = lua51::Settings { 126 | fields_per_flush: 8, 127 | ..lua51::Settings::default() 128 | }; 129 | 130 | Settings { lua50, lua51, output } 131 | } 132 | 133 | fn lua51_setlist(size: u64, settings: Settings) -> Vec { 134 | let mut instructions = vec![lua51::Instruction::NewTable { 135 | a: 0, 136 | mode: BC(Unused, Unused), 137 | }]; 138 | 139 | for index in 0..size { 140 | let stack_position = (index % settings.lua51.fields_per_flush) + 1; 141 | let page = (index / settings.lua51.fields_per_flush) + 1; 142 | 143 | instructions.push(lua51::Instruction::LoadK { 144 | a: stack_position, 145 | mode: Bx(0), 146 | }); 147 | 148 | if stack_position == settings.lua51.fields_per_flush || index + 1 == size { 149 | instructions.push(lua51::Instruction::SetList { 150 | a: 0, 151 | mode: BC(Generic(stack_position), Generic(page)), 152 | }); 153 | } 154 | } 155 | 156 | instructions 157 | } 158 | 159 | fn output_setlist(size: u64, settings: Settings) -> Vec { 160 | let mut instructions = vec![lua51::Instruction::NewTable { 161 | a: 0, 162 | mode: BC(Unused, Unused), 163 | }]; 164 | 165 | for index in 0..size { 166 | let stack_position = (index % settings.output.fields_per_flush) + 1; 167 | let page = (index / settings.output.fields_per_flush) + 1; 168 | 169 | instructions.push(lua51::Instruction::LoadK { 170 | a: stack_position, 171 | mode: Bx(0), 172 | }); 173 | 174 | if stack_position == settings.output.fields_per_flush || index + 1 == size { 175 | instructions.push(lua51::Instruction::SetList { 176 | a: 0, 177 | mode: BC(Generic(stack_position), Generic(page)), 178 | }); 179 | } 180 | } 181 | 182 | instructions 183 | } 184 | 185 | fn set_list_test(count: u64) -> Result<(), LunifyError> { 186 | let settings = test_settings(); 187 | let instructions = lua51_setlist(count, settings); 188 | let instruction_count = instructions.len(); 189 | 190 | let (instructions, _) = convert(instructions, vec![0; instruction_count], &mut 2, &settings)?; 191 | let expected = output_setlist(count, settings); 192 | 193 | assert_eq!(instructions, expected); 194 | Ok(()) 195 | } 196 | 197 | #[test] 198 | fn convert_set_list() -> Result<(), LunifyError> { 199 | set_list_test(4) 200 | } 201 | 202 | #[test] 203 | fn convert_set_list_bigger_than_50_flush() -> Result<(), LunifyError> { 204 | set_list_test(6) 205 | } 206 | 207 | #[test] 208 | fn convert_set_list_bigger_than_51_flush() -> Result<(), LunifyError> { 209 | set_list_test(9) 210 | } 211 | 212 | #[test] 213 | fn convert_set_list_large() -> Result<(), LunifyError> { 214 | set_list_test(20) 215 | } 216 | 217 | #[test] 218 | fn convert_set_list_from_parameters_bigger_than_50_flush() -> Result<(), LunifyError> { 219 | let settings = test_settings(); 220 | let instructions = vec![ 221 | lua51::Instruction::LoadK { a: 5, mode: Bx(0) }, 222 | lua51::Instruction::SetList { 223 | a: 0, 224 | mode: BC(Generic(5), Generic(1)), 225 | }, 226 | lua51::Instruction::LoadK { a: 1, mode: Bx(0) }, 227 | lua51::Instruction::SetList { 228 | a: 0, 229 | mode: BC(Generic(1), Generic(2)), 230 | }, 231 | ]; 232 | 233 | let (instructions, _) = convert(instructions, vec![0; 12], &mut 2, &settings)?; 234 | let expected = vec![ 235 | lua51::Instruction::LoadK { a: 5, mode: Bx(0) }, 236 | lua51::Instruction::LoadK { a: 6, mode: Bx(0) }, 237 | lua51::Instruction::SetList { 238 | a: 0, 239 | mode: BC(Generic(6), Generic(1)), 240 | }, 241 | ]; 242 | 243 | assert_eq!(instructions, expected); 244 | Ok(()) 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/serialization/stream.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use crate::number::Number; 4 | use crate::{Endianness, Format, LunifyError}; 5 | 6 | pub(crate) struct ByteStream<'a> { 7 | data: &'a [u8], 8 | offset: usize, 9 | format: Format, 10 | } 11 | 12 | impl<'a> ByteStream<'a> { 13 | pub fn new(data: &'a [u8]) -> Self { 14 | let offset = 0; 15 | let format = Format::default(); 16 | 17 | Self { data, offset, format } 18 | } 19 | 20 | pub fn remove_signature(&mut self, signature: &str) -> bool { 21 | assert_eq!( 22 | self.offset, 0, 23 | "remove_signature can only be called at the beginning of the byte stream" 24 | ); 25 | 26 | if self.data.len() <= signature.len() { 27 | return false; 28 | } 29 | 30 | let signature_found = &self.data[..signature.len()] == signature.as_bytes(); 31 | 32 | if signature_found { 33 | self.offset += signature.len(); 34 | } 35 | 36 | signature_found 37 | } 38 | 39 | pub fn set_format(&mut self, format: Format) { 40 | self.format = format; 41 | } 42 | 43 | pub fn byte(&mut self) -> Result { 44 | let offset = self.offset; 45 | self.offset += 1; 46 | self.data.get(offset).cloned().ok_or(LunifyError::InputTooShort) 47 | } 48 | 49 | pub fn integer(&mut self) -> Result { 50 | Ok(from_slice!(self, self.format.integer_width, self.format.endianness, i32, i64)) 51 | } 52 | 53 | pub fn size_t(&mut self) -> Result { 54 | Ok(from_slice!(self, self.format.size_t_width, self.format.endianness, i32, i64)) 55 | } 56 | 57 | pub fn instruction(&mut self) -> Result { 58 | Ok(from_slice!( 59 | self, 60 | self.format.instruction_width, 61 | self.format.endianness, 62 | u32, 63 | u64 64 | )) 65 | } 66 | 67 | pub fn number(&mut self) -> Result { 68 | match self.format.is_number_integral { 69 | true => Ok(Number::Integer(from_slice!( 70 | self, 71 | self.format.number_width, 72 | self.format.endianness, 73 | i32, 74 | i64 75 | ))), 76 | false => Ok(Number::Float(from_slice!( 77 | self, 78 | self.format.number_width, 79 | self.format.endianness, 80 | f32, 81 | f64 82 | ))), 83 | } 84 | } 85 | 86 | pub fn slice(&mut self, length: usize) -> Result<&[u8], LunifyError> { 87 | let start = self.offset; 88 | self.offset += length; 89 | 90 | if self.offset > self.data.len() { 91 | return Err(LunifyError::InputTooShort); 92 | } 93 | 94 | Ok(&self.data[start..self.offset]) 95 | } 96 | 97 | pub fn string(&mut self) -> Result { 98 | let length = self.size_t()? as usize; 99 | let start = self.offset; 100 | 101 | self.offset += length; 102 | 103 | if self.offset > self.data.len() { 104 | return Err(LunifyError::InputTooShort); 105 | } 106 | 107 | Ok(self.data[start..self.offset].iter().map(|&byte| byte as char).collect()) 108 | } 109 | 110 | pub fn is_empty(&self) -> bool { 111 | self.offset >= self.data.len() 112 | } 113 | } 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use super::ByteStream; 118 | use crate::number::Number; 119 | use crate::{BitWidth, Endianness, Format, LunifyError}; 120 | 121 | const TEST_FORMAT: Format = Format { 122 | format: 80, 123 | endianness: Endianness::Little, 124 | integer_width: BitWidth::Bit32, 125 | size_t_width: BitWidth::Bit64, 126 | instruction_width: BitWidth::Bit32, 127 | number_width: BitWidth::Bit64, 128 | is_number_integral: false, 129 | }; 130 | 131 | macro_rules! configuration { 132 | ($endianness:ident, $width:expr, $expected:expr, $bytes:expr) => { 133 | Configuration { 134 | bytes: $bytes.as_slice(), 135 | endianness: Endianness::$endianness, 136 | width: $width, 137 | is_number_integral: false, 138 | expected: $expected, 139 | } 140 | }; 141 | 142 | ($endianness:ident, $width:expr, $is_number_integral:expr, $expected:expr, $bytes:expr) => { 143 | Configuration { 144 | bytes: $bytes.as_slice(), 145 | endianness: Endianness::$endianness, 146 | width: $width, 147 | is_number_integral: $is_number_integral, 148 | expected: $expected, 149 | } 150 | }; 151 | } 152 | 153 | struct Configuration<'a, T> { 154 | bytes: &'a [u8], 155 | endianness: Endianness, 156 | width: BitWidth, 157 | is_number_integral: bool, 158 | expected: T, 159 | } 160 | 161 | impl Configuration<'_, T> { 162 | fn format(&self) -> Format { 163 | Format { 164 | endianness: self.endianness, 165 | integer_width: self.width, 166 | size_t_width: self.width, 167 | instruction_width: self.width, 168 | number_width: self.width, 169 | is_number_integral: self.is_number_integral, 170 | ..Default::default() 171 | } 172 | } 173 | } 174 | 175 | #[test] 176 | fn set_format() { 177 | let mut stream = ByteStream::new(&[]); 178 | stream.set_format(TEST_FORMAT); 179 | assert_eq!(stream.format, TEST_FORMAT); 180 | } 181 | 182 | #[test] 183 | fn byte() { 184 | let mut stream = ByteStream::new(&[9]); 185 | assert_eq!(stream.byte(), Ok(9)); 186 | assert!(stream.is_empty()); 187 | } 188 | 189 | #[test] 190 | fn byte_too_short() { 191 | let mut stream = ByteStream::new(&[]); 192 | assert_eq!(stream.byte(), Err(LunifyError::InputTooShort)); 193 | assert!(stream.is_empty()); 194 | } 195 | 196 | #[test] 197 | fn integer() { 198 | let configurations = [ 199 | configuration!(Little, BitWidth::Bit32, 9, [9, 0, 0, 0]), 200 | configuration!(Big, BitWidth::Bit32, 9, [0, 0, 0, 9]), 201 | configuration!(Little, BitWidth::Bit64, 9, [9, 0, 0, 0, 0, 0, 0, 0]), 202 | configuration!(Big, BitWidth::Bit64, 9, [0, 0, 0, 0, 0, 0, 0, 9]), 203 | ]; 204 | 205 | for configuration in configurations { 206 | let mut stream = ByteStream::new(configuration.bytes); 207 | stream.set_format(configuration.format()); 208 | 209 | assert_eq!(stream.integer(), Ok(configuration.expected)); 210 | assert!(stream.is_empty()); 211 | } 212 | } 213 | 214 | #[test] 215 | fn size_t() { 216 | let configurations = [ 217 | configuration!(Little, BitWidth::Bit32, 9, [9, 0, 0, 0]), 218 | configuration!(Big, BitWidth::Bit32, 9, [0, 0, 0, 9]), 219 | configuration!(Little, BitWidth::Bit64, 9, [9, 0, 0, 0, 0, 0, 0, 0]), 220 | configuration!(Big, BitWidth::Bit64, 9, [0, 0, 0, 0, 0, 0, 0, 9]), 221 | ]; 222 | 223 | for configuration in configurations { 224 | let mut stream = ByteStream::new(configuration.bytes); 225 | stream.set_format(configuration.format()); 226 | 227 | assert_eq!(stream.size_t(), Ok(configuration.expected)); 228 | assert!(stream.is_empty()); 229 | } 230 | } 231 | 232 | #[test] 233 | fn instruction() { 234 | let configurations = [ 235 | configuration!(Little, BitWidth::Bit32, 9, [9, 0, 0, 0]), 236 | configuration!(Big, BitWidth::Bit32, 9, [0, 0, 0, 9]), 237 | configuration!(Little, BitWidth::Bit64, 9, [9, 0, 0, 0, 0, 0, 0, 0]), 238 | configuration!(Big, BitWidth::Bit64, 9, [0, 0, 0, 0, 0, 0, 0, 9]), 239 | ]; 240 | 241 | for configuration in configurations { 242 | let mut stream = ByteStream::new(configuration.bytes); 243 | stream.set_format(configuration.format()); 244 | 245 | assert_eq!(stream.instruction(), Ok(configuration.expected)); 246 | assert!(stream.is_empty()); 247 | } 248 | } 249 | 250 | #[test] 251 | fn number() { 252 | let configurations = [ 253 | // Integer 254 | configuration!(Little, BitWidth::Bit32, true, Number::Integer(9), [9, 0, 0, 0]), 255 | configuration!(Big, BitWidth::Bit32, true, Number::Integer(9), [0, 0, 0, 9]), 256 | configuration!(Little, BitWidth::Bit64, true, Number::Integer(9), [9, 0, 0, 0, 0, 0, 0, 0]), 257 | configuration!(Big, BitWidth::Bit64, true, Number::Integer(9), [0, 0, 0, 0, 0, 0, 0, 9]), 258 | // Float 259 | configuration!(Little, BitWidth::Bit32, false, Number::Float(9.0), [0, 0, 16, 65]), 260 | configuration!(Big, BitWidth::Bit32, false, Number::Float(9.0), [65, 16, 0, 0]), 261 | configuration!(Little, BitWidth::Bit64, false, Number::Float(9.0), [0, 0, 0, 0, 0, 0, 34, 64]), 262 | configuration!(Big, BitWidth::Bit64, false, Number::Float(9.0), [64, 34, 0, 0, 0, 0, 0, 0]), 263 | ]; 264 | 265 | for configuration in configurations { 266 | let mut stream = ByteStream::new(configuration.bytes); 267 | stream.set_format(configuration.format()); 268 | 269 | assert_eq!(stream.number(), Ok(configuration.expected)); 270 | assert!(stream.is_empty()); 271 | } 272 | } 273 | 274 | #[test] 275 | fn slice() { 276 | let mut stream = ByteStream::new(&[9, 9, 9]); 277 | stream.set_format(TEST_FORMAT); 278 | assert_eq!(stream.slice(3), Ok([9, 9, 9].as_slice())); 279 | assert!(stream.is_empty()); 280 | } 281 | 282 | #[test] 283 | fn empty_slice() { 284 | let mut stream = ByteStream::new(&[]); 285 | stream.set_format(TEST_FORMAT); 286 | assert_eq!(stream.slice(0), Ok([].as_slice())); 287 | assert!(stream.is_empty()); 288 | } 289 | 290 | #[test] 291 | fn slice_too_short() { 292 | let mut stream = ByteStream::new(&[9, 9]); 293 | stream.set_format(TEST_FORMAT); 294 | assert_eq!(stream.slice(3), Err(LunifyError::InputTooShort)); 295 | assert!(stream.is_empty()); 296 | } 297 | 298 | #[test] 299 | fn string() { 300 | let mut stream = ByteStream::new(&[3, 0, 0, 0, 0, 0, 0, 0, b'L', b'U', b'A']); 301 | stream.set_format(TEST_FORMAT); 302 | assert_eq!(stream.string(), Ok("LUA".to_owned())); 303 | assert!(stream.is_empty()); 304 | } 305 | 306 | #[test] 307 | fn string_too_short() { 308 | let mut stream = ByteStream::new(&[3, 0, 0, 0, 0, 0, 0, 0, b'L', b'U']); 309 | stream.set_format(TEST_FORMAT); 310 | assert_eq!(stream.string(), Err(LunifyError::InputTooShort)); 311 | assert!(stream.is_empty()); 312 | } 313 | 314 | #[test] 315 | fn is_empty() { 316 | let stream = ByteStream::new(&[]); 317 | assert!(stream.is_empty()); 318 | } 319 | 320 | #[test] 321 | fn is_not_empty() { 322 | let stream = ByteStream::new(&[0]); 323 | assert!(!stream.is_empty()); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/function/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod constant; 3 | mod convert; 4 | mod instruction; 5 | mod local; 6 | mod upcast; 7 | 8 | use std::fmt::Debug; 9 | 10 | use self::constant::Constant; 11 | use self::convert::convert; 12 | use self::instruction::LuaInstruction; 13 | pub use self::instruction::{lua50, lua51, InstructionLayout, OperandType, Settings}; 14 | use self::local::LocalVariable; 15 | use self::upcast::upcast; 16 | use crate::format::LuaVersion; 17 | use crate::serialization::{ByteStream, ByteWriter}; 18 | use crate::LunifyError; 19 | 20 | pub(crate) struct Function { 21 | source_file: String, 22 | line_defined: i64, 23 | last_line_defined: i64, 24 | parameter_count: u8, 25 | is_variadic: u8, 26 | maximum_stack_size: u8, 27 | instructions: Vec, 28 | constants: Vec, 29 | functions: Vec, 30 | local_variables: Vec, 31 | line_info: Vec, 32 | upvalues: Vec, 33 | } 34 | 35 | impl Function { 36 | fn get_instructions(byte_stream: &mut ByteStream, settings: &Settings, layout: &InstructionLayout) -> Result, LunifyError> 37 | where 38 | T: LuaInstruction + Debug, 39 | { 40 | let instruction_count = byte_stream.integer()?; 41 | let mut instructions = Vec::new(); 42 | 43 | #[cfg(feature = "debug")] 44 | println!("instruction_count: {instruction_count}"); 45 | 46 | #[cfg(feature = "debug")] 47 | println!("\n======== Instructions ========"); 48 | 49 | for _program_counter in 0..instruction_count as usize { 50 | let instruction = T::from_byte_stream(byte_stream, settings, layout)?; 51 | 52 | #[cfg(feature = "debug")] 53 | println!("[{_program_counter}] {instruction:?}"); 54 | 55 | instructions.push(instruction); 56 | } 57 | 58 | Ok(instructions) 59 | } 60 | 61 | fn get_constants(byte_stream: &mut ByteStream) -> Result, LunifyError> { 62 | let constant_count = byte_stream.integer()?; 63 | let mut constants = Vec::new(); 64 | 65 | #[cfg(feature = "debug")] 66 | { 67 | println!("\nconstant_count: {constant_count}"); 68 | println!("\n======== Constants ========"); 69 | } 70 | 71 | for _index in 0..constant_count as usize { 72 | let constant_type = byte_stream.byte()?; 73 | 74 | match constant_type { 75 | 0 => { 76 | constants.push(Constant::Nil); 77 | 78 | #[cfg(feature = "debug")] 79 | println!("constant[{_index}] (nil)"); 80 | } 81 | 82 | 1 => { 83 | let boolean = byte_stream.byte()?; 84 | 85 | #[cfg(feature = "debug")] 86 | println!("constant[{_index}] (bool): {boolean:?}"); 87 | 88 | constants.push(Constant::Boolean(boolean != 0)); 89 | } 90 | 91 | 3 => { 92 | let number = byte_stream.number()?; 93 | 94 | #[cfg(feature = "debug")] 95 | println!("constant[{_index}] (number): {number:?}"); 96 | 97 | constants.push(Constant::Number(number)); 98 | } 99 | 100 | 4 => { 101 | let string = byte_stream.string()?; 102 | 103 | #[cfg(feature = "debug")] 104 | println!("constant[{}] (string) ({}): {:?}", _index, string.len(), string); 105 | 106 | constants.push(Constant::String(string)); 107 | } 108 | 109 | invalid => return Err(LunifyError::InvalidConstantType(invalid)), 110 | } 111 | } 112 | 113 | Ok(constants) 114 | } 115 | 116 | fn get_functions(byte_stream: &mut ByteStream, version: LuaVersion, settings: &Settings) -> Result, LunifyError> { 117 | let function_count = byte_stream.integer()?; 118 | let mut functions = Vec::new(); 119 | 120 | #[cfg(feature = "debug")] 121 | println!("\nfunction_count: {function_count}"); 122 | 123 | for _index in 0..function_count as usize { 124 | let function = Function::from_byte_stream(byte_stream, version, settings)?; 125 | functions.push(function); 126 | } 127 | 128 | Ok(functions) 129 | } 130 | 131 | fn get_local_variables(byte_stream: &mut ByteStream) -> Result, LunifyError> { 132 | let local_variable_count = byte_stream.integer()?; 133 | let mut local_variables = Vec::new(); 134 | 135 | #[cfg(feature = "debug")] 136 | { 137 | println!("local_variable_count: {local_variable_count}"); 138 | println!("\n======== Local variables ========"); 139 | } 140 | 141 | for _index in 0..local_variable_count as usize { 142 | let name = byte_stream.string()?; 143 | let start_program_counter = byte_stream.integer()?; 144 | let end_program_counter = byte_stream.integer()?; 145 | 146 | #[cfg(feature = "debug")] 147 | println!("local variable[{_index}] ({start_program_counter} - {end_program_counter}): {name:?}"); 148 | 149 | let local_variable = LocalVariable { 150 | name, 151 | start_program_counter, 152 | end_program_counter, 153 | }; 154 | 155 | local_variables.push(local_variable); 156 | } 157 | 158 | Ok(local_variables) 159 | } 160 | 161 | fn get_line_info(byte_stream: &mut ByteStream) -> Result, LunifyError> { 162 | let line_info_count = byte_stream.integer()?; 163 | let mut line_info = Vec::new(); 164 | 165 | #[cfg(feature = "debug")] 166 | println!("line_info_count: {line_info_count}"); 167 | 168 | for _index in 0..line_info_count as usize { 169 | let line = byte_stream.integer()?; 170 | line_info.push(line); 171 | } 172 | 173 | Ok(line_info) 174 | } 175 | 176 | fn get_upvalues(byte_stream: &mut ByteStream) -> Result, LunifyError> { 177 | let upvalue_count = byte_stream.integer()?; 178 | let mut upvalues = Vec::new(); 179 | 180 | #[cfg(feature = "debug")] 181 | println!("\nupvalue_count: {upvalue_count}"); 182 | 183 | for _index in 0..upvalue_count as usize { 184 | let upvalue = byte_stream.string()?; 185 | 186 | #[cfg(feature = "debug")] 187 | println!("upvalue[{_index}]: {upvalue:?}"); 188 | 189 | upvalues.push(upvalue); 190 | } 191 | 192 | Ok(upvalues) 193 | } 194 | 195 | fn strip_instructions(instructions: Vec, settings: &Settings) -> Result, LunifyError> { 196 | instructions.into_iter().map(|instruction| instruction.to_u64(settings)).collect() 197 | } 198 | 199 | pub(crate) fn from_byte_stream(byte_stream: &mut ByteStream, version: LuaVersion, settings: &Settings) -> Result { 200 | let source_file = byte_stream.string()?; 201 | let line_defined = byte_stream.integer()?; 202 | 203 | let last_line_defined = match version { 204 | LuaVersion::Lua51 => byte_stream.integer()?, 205 | LuaVersion::Lua50 => line_defined, 206 | }; 207 | 208 | let _upvalue_count = byte_stream.byte()?; 209 | let parameter_count = byte_stream.byte()?; 210 | let mut is_variadic = byte_stream.byte()?; 211 | let mut maximum_stack_size = byte_stream.byte()?; 212 | 213 | #[cfg(feature = "debug")] 214 | { 215 | println!("\n======== Function ========"); 216 | println!("source_file: {source_file}"); 217 | println!("line_defined: {line_defined}"); 218 | println!("last_line_defined: {last_line_defined}"); 219 | println!("upvalue_count: {_upvalue_count}"); 220 | println!("parameter_count: {parameter_count}"); 221 | println!("is_variadic: {is_variadic}"); 222 | println!("maximum_stack_size: {maximum_stack_size}"); 223 | } 224 | 225 | let (instructions, constants, functions, line_info, local_variables, upvalues) = if version == LuaVersion::Lua51 { 226 | let instructions = Self::get_instructions(byte_stream, settings, &settings.lua51.layout)?; 227 | let constants = Self::get_constants(byte_stream)?; 228 | let functions = Self::get_functions(byte_stream, version, settings)?; 229 | let line_info = Self::get_line_info(byte_stream)?; 230 | let local_variables = Self::get_local_variables(byte_stream)?; 231 | let upvalues = Self::get_upvalues(byte_stream)?; 232 | 233 | // Convert from the input Lua 5.1 byte code to the desired output Lua 5.1 234 | // byte code. 235 | let (instructions, line_info) = convert(instructions, line_info, &mut maximum_stack_size, settings)?; 236 | let instructions = Self::strip_instructions(instructions, settings)?; 237 | 238 | (instructions, constants, functions, line_info, local_variables, upvalues) 239 | } else { 240 | let line_info = Self::get_line_info(byte_stream)?; 241 | let local_variables = Self::get_local_variables(byte_stream)?; 242 | let upvalues = Self::get_upvalues(byte_stream)?; 243 | let mut constants = Self::get_constants(byte_stream)?; 244 | let functions = Self::get_functions(byte_stream, version, settings)?; 245 | let instructions = Self::get_instructions(byte_stream, settings, &settings.lua50.layout)?; 246 | 247 | if is_variadic != 0 { 248 | // Lua 5.1 uses an addition flag called `VARARG_ISVARARG` for variadic functions 249 | // which we need to set, otherwise the function will be invalid. 250 | is_variadic |= 2; 251 | } 252 | 253 | // Up-cast instructions from Lua 5.0 to Lua 5.1. 254 | let (instructions, line_info) = upcast( 255 | instructions, 256 | line_info, 257 | &mut constants, 258 | &mut maximum_stack_size, 259 | parameter_count, 260 | is_variadic != 0, 261 | settings, 262 | )?; 263 | 264 | let instructions = Self::strip_instructions(instructions, settings)?; 265 | 266 | (instructions, constants, functions, line_info, local_variables, upvalues) 267 | }; 268 | 269 | Ok(Self { 270 | source_file, 271 | line_defined, 272 | last_line_defined, 273 | parameter_count, 274 | is_variadic, 275 | maximum_stack_size, 276 | instructions, 277 | constants, 278 | functions, 279 | local_variables, 280 | line_info, 281 | upvalues, 282 | }) 283 | } 284 | 285 | pub(crate) fn write(self, byte_writer: &mut ByteWriter) -> Result<(), LunifyError> { 286 | // function 287 | byte_writer.string(&self.source_file); 288 | byte_writer.integer(self.line_defined); 289 | byte_writer.integer(self.last_line_defined); 290 | byte_writer.byte(self.upvalues.len() as u8); 291 | byte_writer.byte(self.parameter_count); 292 | byte_writer.byte(self.is_variadic); 293 | byte_writer.byte(self.maximum_stack_size); 294 | 295 | // instructions 296 | byte_writer.integer(self.instructions.len() as i64); 297 | for instruction in self.instructions { 298 | byte_writer.instruction(instruction); 299 | } 300 | 301 | // constants 302 | byte_writer.integer(self.constants.len() as i64); 303 | for constant in self.constants { 304 | match constant { 305 | Constant::Nil => { 306 | byte_writer.byte(0); 307 | } 308 | 309 | Constant::Boolean(boolean) => { 310 | byte_writer.byte(1); 311 | byte_writer.byte(boolean as u8); 312 | } 313 | 314 | Constant::Number(number) => { 315 | byte_writer.byte(3); 316 | byte_writer.number(number)?; 317 | } 318 | 319 | Constant::String(string) => { 320 | byte_writer.byte(4); 321 | byte_writer.string(&string); 322 | } 323 | } 324 | } 325 | 326 | // functions 327 | byte_writer.integer(self.functions.len() as i64); 328 | for function in self.functions { 329 | function.write(byte_writer)?; 330 | } 331 | 332 | // line info 333 | byte_writer.integer(self.line_info.len() as i64); 334 | for line_info in self.line_info { 335 | byte_writer.integer(line_info); 336 | } 337 | 338 | // local variables 339 | byte_writer.integer(self.local_variables.len() as i64); 340 | for local_variable in self.local_variables { 341 | byte_writer.string(&local_variable.name); 342 | byte_writer.integer(local_variable.start_program_counter); 343 | byte_writer.integer(local_variable.end_program_counter); 344 | } 345 | 346 | // upvalues 347 | byte_writer.integer(self.upvalues.len() as i64); 348 | for upvalue in self.upvalues { 349 | byte_writer.string(&upvalue); 350 | } 351 | 352 | Ok(()) 353 | } 354 | } 355 | 356 | #[cfg(test)] 357 | mod test { 358 | use crate::function::Function; 359 | use crate::serialization::{ByteStream, ByteWriter}; 360 | use crate::{Format, LunifyError}; 361 | 362 | #[test] 363 | fn get_constants_invalid() { 364 | let format = Format::default(); 365 | let mut byte_writer = ByteWriter::new(&format); 366 | 367 | // Constant count. 368 | byte_writer.integer(1); 369 | // Invalid type. 370 | byte_writer.byte(5); 371 | 372 | let bytes = byte_writer.finalize(); 373 | let mut byte_stream = ByteStream::new(&bytes); 374 | 375 | let result = Function::get_constants(&mut byte_stream); 376 | assert_eq!(result, Err(LunifyError::InvalidConstantType(5))); 377 | assert!(byte_stream.is_empty()); 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/function/builder.rs: -------------------------------------------------------------------------------- 1 | use super::Settings; 2 | use crate::lua51::Instruction; 3 | use crate::LunifyError; 4 | 5 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 6 | struct InstructionContext { 7 | instruction: Instruction, 8 | line_weight: i64, 9 | final_offset: i64, 10 | is_fixed: bool, 11 | } 12 | 13 | impl InstructionContext { 14 | pub fn new(instruction: Instruction) -> Self { 15 | Self { 16 | instruction, 17 | line_weight: 0, 18 | final_offset: 0, 19 | is_fixed: false, 20 | } 21 | } 22 | 23 | pub fn new_extra(instruction: Instruction) -> Self { 24 | Self { 25 | instruction, 26 | line_weight: 1, 27 | final_offset: 0, 28 | is_fixed: false, 29 | } 30 | } 31 | } 32 | 33 | #[derive(Default)] 34 | pub(super) struct FunctionBuilder { 35 | contexts: Vec, 36 | line_info: Vec, 37 | line_number: i64, 38 | } 39 | 40 | impl FunctionBuilder { 41 | pub(super) fn set_line_number(&mut self, line_number: i64) { 42 | self.line_number = line_number; 43 | } 44 | 45 | pub(super) fn instruction(&mut self, instruction: Instruction) { 46 | self.contexts.push(InstructionContext::new(instruction)); 47 | self.line_info.push(self.line_number); 48 | } 49 | 50 | pub(super) fn extra_instruction(&mut self, instruction: Instruction) { 51 | self.contexts.push(InstructionContext::new_extra(instruction)); 52 | self.line_info.push(self.line_number); 53 | } 54 | 55 | pub(super) fn insert_extra_instruction(&mut self, index: usize, instruction: Instruction) { 56 | let line_number = self.line_info[index]; 57 | self.contexts.insert(index, InstructionContext::new_extra(instruction)); 58 | self.line_info.insert(index, line_number); 59 | } 60 | 61 | pub(super) fn remove_instruction(&mut self, index: usize) { 62 | let removed = self.contexts.remove(index); 63 | self.line_info.remove(index); 64 | self.contexts[index].line_weight += removed.line_weight - 1; 65 | } 66 | 67 | pub(super) fn last_instruction_fixed(&mut self) { 68 | self.contexts.last_mut().unwrap().is_fixed = true; 69 | } 70 | 71 | pub(super) fn last_instruction_offset(&mut self, final_offset: i64) { 72 | self.contexts.last_mut().unwrap().final_offset = final_offset; 73 | } 74 | 75 | pub(super) fn get_instruction(&mut self, index: usize) -> &mut Instruction { 76 | &mut self.contexts[index].instruction 77 | } 78 | 79 | pub(super) fn get_program_counter(&self) -> usize { 80 | self.contexts.len() 81 | } 82 | 83 | fn jump_destination(&self, context_index: usize, mut destination: i64, final_offset: i64) -> i64 { 84 | let (mut steps, mut offset) = match destination.is_positive() { 85 | true => (destination + 1, 1), 86 | false => (destination.abs(), 0), 87 | }; 88 | 89 | while steps != 0 { 90 | let index = match destination.is_positive() { 91 | true => context_index + offset, 92 | false => context_index - offset, 93 | }; 94 | let context = self.contexts[index]; 95 | 96 | destination += context.line_weight * destination.signum(); 97 | steps += context.line_weight - 1; 98 | offset += 1; 99 | } 100 | 101 | destination + final_offset 102 | } 103 | 104 | pub(super) fn adjusted_jump_destination(&self, bx: i64) -> Result { 105 | if bx.is_positive() { 106 | return Err(LunifyError::UnexpectedForwardJump); 107 | } 108 | 109 | let program_counter = self.get_program_counter(); 110 | let new_bx = self.jump_destination(program_counter - 1, bx, 0); 111 | 112 | Ok(((program_counter as i64) + new_bx) as usize) 113 | } 114 | 115 | pub(super) fn finalize( 116 | mut self, 117 | maximum_stack_size: &mut u8, 118 | settings: &Settings, 119 | ) -> Result<(Vec, Vec), LunifyError> { 120 | #[cfg(feature = "debug")] 121 | println!("\n======== Output ========"); 122 | 123 | for context_index in 0..self.contexts.len() { 124 | // The stack positions might have changed significantly, so go over every 125 | // instruction and make sure that the maximum stack size is big enough. If the 126 | // stack had to be popped out too much in the conversion, we return 127 | // an error. We also know that values on the stack will only be used 128 | // after they have been put there by anther instruction, meaning if 129 | // we make space for the instructions that push the values onto the 130 | // stack, the stack will never overflow. 131 | if let Some(destination) = self.contexts[context_index].instruction.stack_destination() { 132 | let new_stack_size = destination.end + 1; 133 | match new_stack_size <= settings.output.stack_limit { 134 | true => *maximum_stack_size = (*maximum_stack_size).max(new_stack_size as u8), 135 | false => return Err(LunifyError::StackTooLarge(new_stack_size)), 136 | } 137 | } 138 | 139 | let new_bx = { 140 | let context = &self.contexts[context_index]; 141 | let is_fixed = context.is_fixed; 142 | let final_offset = context.final_offset; 143 | 144 | match &context.instruction { 145 | Instruction::Jump { mode, .. } | Instruction::ForLoop { mode, .. } | Instruction::ForPrep { mode, .. } if !is_fixed => { 146 | Some(self.jump_destination(context_index, mode.0, final_offset)) 147 | } 148 | _ => None, 149 | } 150 | }; 151 | 152 | if let Some(bx) = new_bx { 153 | match &mut self.contexts[context_index].instruction { 154 | Instruction::Jump { mode, .. } | Instruction::ForLoop { mode, .. } | Instruction::ForPrep { mode, .. } => { 155 | mode.0 = bx; 156 | } 157 | _ => unreachable!(), 158 | } 159 | } 160 | 161 | #[cfg(feature = "debug")] 162 | { 163 | let context = &self.contexts[context_index]; 164 | println!("[{}] {:?}", context_index, context.instruction); 165 | println!(" -> {:?}", context.line_weight); 166 | println!(); 167 | } 168 | } 169 | 170 | let instructions = self.contexts.into_iter().map(|context| context.instruction).collect(); 171 | Ok((instructions, self.line_info)) 172 | } 173 | } 174 | 175 | #[cfg(test)] 176 | mod tests { 177 | use super::FunctionBuilder; 178 | use crate::function::builder::InstructionContext; 179 | use crate::function::instruction::{Bx, SignedBx}; 180 | use crate::{lua51, LunifyError}; 181 | 182 | #[test] 183 | fn instruction_context_new() { 184 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 185 | let context = InstructionContext::new(instruction); 186 | let expected = InstructionContext { 187 | instruction, 188 | line_weight: 0, 189 | final_offset: 0, 190 | is_fixed: false, 191 | }; 192 | 193 | assert_eq!(context, expected); 194 | } 195 | 196 | #[test] 197 | fn instruction_context_new_extra() { 198 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 199 | let context = InstructionContext::new_extra(instruction); 200 | let expected = InstructionContext { 201 | instruction, 202 | line_weight: 1, 203 | final_offset: 0, 204 | is_fixed: false, 205 | }; 206 | 207 | assert_eq!(context, expected); 208 | } 209 | 210 | #[test] 211 | fn set_line_number() { 212 | let mut builder = FunctionBuilder::default(); 213 | 214 | builder.set_line_number(9); 215 | 216 | assert_eq!(builder.line_number, 9); 217 | } 218 | 219 | #[test] 220 | fn line_number_applies() { 221 | let mut builder = FunctionBuilder::default(); 222 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 223 | 224 | builder.set_line_number(9); 225 | builder.instruction(instruction); 226 | 227 | assert_eq!(&builder.line_info[..], &[9]); 228 | } 229 | 230 | #[test] 231 | fn instruction() { 232 | let mut builder = FunctionBuilder::default(); 233 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 234 | 235 | builder.instruction(instruction); 236 | 237 | assert_eq!(&builder.contexts[..], &[InstructionContext::new(instruction)]); 238 | assert_eq!(&builder.line_info[..], &[0]); 239 | } 240 | 241 | #[test] 242 | fn extra_instruction() { 243 | let mut builder = FunctionBuilder::default(); 244 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 245 | 246 | builder.extra_instruction(instruction); 247 | 248 | assert_eq!(&builder.contexts[..], &[InstructionContext::new_extra(instruction)]); 249 | assert_eq!(&builder.line_info[..], &[0]); 250 | } 251 | 252 | #[test] 253 | fn insert_extra_instruction() { 254 | let mut builder = FunctionBuilder::default(); 255 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 256 | let extra_instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(10) }; 257 | 258 | builder.instruction(instruction); 259 | builder.set_line_number(9); 260 | builder.instruction(instruction); 261 | builder.insert_extra_instruction(1, extra_instruction); 262 | 263 | let expected = [ 264 | InstructionContext::new(instruction), 265 | InstructionContext::new_extra(extra_instruction), 266 | InstructionContext::new(instruction), 267 | ]; 268 | 269 | assert_eq!(&builder.contexts[..], &expected); 270 | assert_eq!(&builder.line_info[..], &[0, 9, 9]); 271 | } 272 | 273 | #[test] 274 | fn remove_instruction() { 275 | let mut builder = FunctionBuilder::default(); 276 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 277 | let removed_instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(10) }; 278 | 279 | builder.instruction(instruction); 280 | builder.instruction(removed_instruction); 281 | builder.instruction(instruction); 282 | builder.remove_instruction(1); 283 | 284 | let expected = [InstructionContext::new(instruction), InstructionContext { 285 | line_weight: -1, 286 | ..InstructionContext::new(instruction) 287 | }]; 288 | 289 | assert_eq!(&builder.contexts[..], &expected); 290 | assert_eq!(&builder.line_info[..], &[0, 0]); 291 | } 292 | 293 | #[test] 294 | fn remove_extra_instruction() { 295 | let mut builder = FunctionBuilder::default(); 296 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 297 | let removed_instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(10) }; 298 | 299 | builder.instruction(instruction); 300 | builder.extra_instruction(removed_instruction); 301 | builder.instruction(instruction); 302 | builder.remove_instruction(1); 303 | 304 | let expected = [InstructionContext::new(instruction), InstructionContext::new(instruction)]; 305 | assert_eq!(&builder.contexts[..], &expected); 306 | assert_eq!(&builder.line_info[..], &[0, 0]); 307 | } 308 | 309 | #[test] 310 | fn last_instruction_fixed() { 311 | let mut builder = FunctionBuilder::default(); 312 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 313 | 314 | builder.instruction(instruction); 315 | builder.last_instruction_fixed(); 316 | 317 | assert!(builder.contexts.last().unwrap().is_fixed); 318 | } 319 | 320 | #[test] 321 | fn last_instruction_offset() { 322 | let mut builder = FunctionBuilder::default(); 323 | let instruction = lua51::Instruction::Jump { a: 0, mode: SignedBx(-4) }; 324 | 325 | builder.instruction(instruction); 326 | builder.last_instruction_offset(-9); 327 | 328 | assert_eq!(builder.contexts.last().unwrap().final_offset, -9); 329 | } 330 | 331 | #[test] 332 | fn jump_destination_negative() { 333 | let mut builder = FunctionBuilder::default(); 334 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 335 | 336 | builder.instruction(instruction); 337 | builder.extra_instruction(instruction); 338 | 339 | let result = builder.jump_destination(builder.get_program_counter() - 1, -1, 0); 340 | assert_eq!(result, -2); 341 | } 342 | 343 | #[test] 344 | fn jump_destination_zero() { 345 | let mut builder = FunctionBuilder::default(); 346 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 347 | 348 | builder.extra_instruction(instruction); 349 | 350 | let result = builder.jump_destination(0, 0, 0); 351 | assert_eq!(result, 0); 352 | } 353 | 354 | #[test] 355 | fn jump_destination_positive() { 356 | let mut builder = FunctionBuilder::default(); 357 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 358 | 359 | builder.extra_instruction(instruction); 360 | builder.instruction(instruction); 361 | builder.instruction(instruction); 362 | 363 | let result = builder.jump_destination(0, 1, 0); 364 | assert_eq!(result, 1); 365 | } 366 | 367 | #[test] 368 | fn jump_destination_offset_applies() { 369 | let mut builder = FunctionBuilder::default(); 370 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 371 | 372 | builder.instruction(instruction); 373 | builder.instruction(instruction); 374 | 375 | let result = builder.jump_destination(builder.get_program_counter() - 1, -2, 2); 376 | assert_eq!(result, 0); 377 | } 378 | 379 | #[test] 380 | fn adjusted_jump_destination() { 381 | let mut builder = FunctionBuilder::default(); 382 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 383 | 384 | builder.instruction(instruction); 385 | builder.extra_instruction(instruction); 386 | builder.instruction(instruction); 387 | 388 | assert_eq!(builder.adjusted_jump_destination(-2), Ok(0)); 389 | } 390 | 391 | #[test] 392 | fn adjusted_jump_destination_positive() { 393 | let mut builder = FunctionBuilder::default(); 394 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 395 | 396 | builder.instruction(instruction); 397 | builder.extra_instruction(instruction); 398 | builder.instruction(instruction); 399 | 400 | assert_eq!(builder.adjusted_jump_destination(1), Err(LunifyError::UnexpectedForwardJump)); 401 | } 402 | 403 | #[test] 404 | fn finalize_expands_stack() -> Result<(), LunifyError> { 405 | let mut builder = FunctionBuilder::default(); 406 | let instruction = lua51::Instruction::LoadK { a: 10, mode: Bx(1) }; 407 | let mut maximum_stack_size = 0; 408 | 409 | builder.instruction(instruction); 410 | builder.finalize(&mut maximum_stack_size, &Default::default())?; 411 | 412 | assert_eq!(maximum_stack_size, 11); 413 | Ok(()) 414 | } 415 | 416 | #[test] 417 | fn finalize_expands_stack_too_large() { 418 | let mut builder = FunctionBuilder::default(); 419 | let instruction = lua51::Instruction::LoadK { a: 250, mode: Bx(1) }; 420 | let mut maximum_stack_size = 0; 421 | 422 | builder.instruction(instruction); 423 | 424 | let result = builder.finalize(&mut maximum_stack_size, &Default::default()); 425 | assert_eq!(result, Result::Err(LunifyError::StackTooLarge(251))); 426 | } 427 | 428 | #[test] 429 | fn finalize_adjusts_jump_destinations() -> Result<(), LunifyError> { 430 | let mut builder = FunctionBuilder::default(); 431 | let instruction = lua51::Instruction::LoadK { a: 0, mode: Bx(1) }; 432 | let jump_instruction = lua51::Instruction::Jump { a: 0, mode: SignedBx(-1) }; 433 | 434 | builder.instruction(instruction); 435 | builder.extra_instruction(instruction); 436 | builder.extra_instruction(jump_instruction); 437 | let (instructions, _) = builder.finalize(&mut 0, &Default::default())?; 438 | 439 | let lua51::Instruction::Jump { mode, .. } = instructions.last().unwrap() else { 440 | panic!() 441 | }; 442 | assert_eq!(mode.0, -3); 443 | Ok(()) 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /src/function/upcast.rs: -------------------------------------------------------------------------------- 1 | use super::builder::FunctionBuilder; 2 | use super::constant::{Constant, ConstantManager}; 3 | use super::instruction::{lua50, lua51, Bx, ConstantRegister, Generic, LuaInstruction, Register, Settings, SignedBx, Unused, BC}; 4 | use crate::LunifyError; 5 | 6 | pub(crate) fn upcast( 7 | instructions: Vec, 8 | line_info: Vec, 9 | constants: &mut Vec, 10 | maximum_stack_size: &mut u8, 11 | parameter_count: u8, 12 | is_variadic: bool, 13 | settings: &Settings, 14 | ) -> Result<(Vec, Vec), LunifyError> { 15 | let mut builder = FunctionBuilder::default(); 16 | let mut constant_manager = ConstantManager { constants }; 17 | 18 | for (instruction, line_number) in instructions.into_iter().zip(line_info) { 19 | builder.set_line_number(line_number); 20 | 21 | match instruction { 22 | lua50::Instruction::Move { a, mode } => builder.instruction(lua51::Instruction::Move { a, mode }), 23 | lua50::Instruction::LoadK { a, mode } => builder.instruction(lua51::Instruction::LoadK { a, mode }), 24 | lua50::Instruction::LoadBool { a, mode } => builder.instruction(lua51::Instruction::LoadBool { a, mode }), 25 | lua50::Instruction::LoadNil { a, mode } => builder.instruction(lua51::Instruction::LoadNil { a, mode }), 26 | lua50::Instruction::GetUpValue { a, mode } => builder.instruction(lua51::Instruction::GetUpValue { a, mode }), 27 | lua50::Instruction::GetGlobal { a, mode } => builder.instruction(lua51::Instruction::GetGlobal { a, mode }), 28 | lua50::Instruction::GetTable { a, mode } => builder.instruction(lua51::Instruction::GetTable { a, mode }), 29 | lua50::Instruction::SetGlobal { a, mode } => builder.instruction(lua51::Instruction::SetGlobal { a, mode }), 30 | lua50::Instruction::SetUpValue { a, mode } => builder.instruction(lua51::Instruction::SetUpValue { a, mode }), 31 | lua50::Instruction::SetTable { a, mode } => builder.instruction(lua51::Instruction::SetTable { a, mode }), 32 | lua50::Instruction::NewTable { a, mode } => builder.instruction(lua51::Instruction::NewTable { a, mode }), 33 | lua50::Instruction::_Self { a, mode } => builder.instruction(lua51::Instruction::_Self { a, mode }), 34 | lua50::Instruction::Add { a, mode } => builder.instruction(lua51::Instruction::Add { a, mode }), 35 | lua50::Instruction::Subtract { a, mode } => builder.instruction(lua51::Instruction::Subtract { a, mode }), 36 | lua50::Instruction::Multiply { a, mode } => builder.instruction(lua51::Instruction::Multiply { a, mode }), 37 | lua50::Instruction::Divide { a, mode } => builder.instruction(lua51::Instruction::Divide { a, mode }), 38 | lua50::Instruction::Power { a, mode } => builder.instruction(lua51::Instruction::Power { a, mode }), 39 | lua50::Instruction::Unary { a, mode } => builder.instruction(lua51::Instruction::Unary { a, mode }), 40 | lua50::Instruction::Not { a, mode } => builder.instruction(lua51::Instruction::Not { a, mode }), 41 | lua50::Instruction::Concatinate { a, mode } => builder.instruction(lua51::Instruction::Concatinate { a, mode }), 42 | lua50::Instruction::Jump { a, mode } => builder.instruction(lua51::Instruction::Jump { a, mode }), 43 | lua50::Instruction::Equals { a, mode } => builder.instruction(lua51::Instruction::Equals { a, mode }), 44 | lua50::Instruction::LessThan { a, mode } => builder.instruction(lua51::Instruction::LessThan { a, mode }), 45 | lua50::Instruction::LessEquals { a, mode } => builder.instruction(lua51::Instruction::LessEquals { a, mode }), 46 | lua50::Instruction::Test { a, mode: BC(b, c) } => builder.instruction(lua51::Instruction::TestSet { 47 | a, 48 | mode: BC(ConstantRegister(b.0, false), c), 49 | }), 50 | lua50::Instruction::Call { a, mode } => builder.instruction(lua51::Instruction::Call { a, mode }), 51 | lua50::Instruction::TailCall { a, mode } => builder.instruction(lua51::Instruction::TailCall { a, mode }), 52 | lua50::Instruction::Return { a, mode } => builder.instruction(lua51::Instruction::Return { a, mode }), 53 | lua50::Instruction::ForLoop { a, mode } => { 54 | // Lua 5.1 additionally saves the loop index in RA+3, which Lua 5.0 does 55 | // not. Therefore we save RA+3 to a global value and restore it afterwards. 56 | 57 | // Create a new constant to hold an identifier to the global that saves the 58 | // value in RA+3. 59 | let global_constant = constant_manager.create_unique(builder.get_program_counter()); 60 | 61 | // Instruction to save RA+3. 62 | builder.instruction(lua51::Instruction::SetGlobal { 63 | a: a + 3, 64 | mode: Bx(global_constant), 65 | }); 66 | 67 | // Original instruction, but since we will insert another instruction before the 68 | // destination of our jump, we also pass it an offset that will be applied after 69 | // adjusting the jump position. 70 | builder.extra_instruction(lua51::Instruction::ForLoop { a, mode }); 71 | builder.last_instruction_offset(-1); 72 | 73 | // Get the *adjusted* position of the instruction we want to 74 | // jump to. It is very important that we take the adjusted position because 75 | // we might have added or remove instructions inside the for loop, which would 76 | // make the old Bx invalid. 77 | let position = builder.adjusted_jump_destination(mode.0)?; 78 | 79 | // Instruction to restore RA+3 if we take the jump. 80 | // This instruction is actually inserted *before* the `SETGLOBAL` instruction, 81 | // but this works because of the way that for loops are 82 | // generated in Lua 5.0. There is an initial `JMP` instruction 83 | // that moves the program counter to the `FORLOOP` instruction, 84 | // meaning this `GetGlobal` will *always* be called after we 85 | // already saved RA+3 with our `SETGLOBAL` instruction. 86 | builder.insert_extra_instruction(position, lua51::Instruction::GetGlobal { 87 | a: a + 3, 88 | mode: Bx(global_constant), 89 | }); 90 | } 91 | lua50::Instruction::TForLoop { a, mode: BC(_, c) } => { 92 | // The `TFORLOOP` instruction in Lua 5.0 can move multiple results to the stack 93 | // below the call base, but Lua 5.1 can only move one result and 94 | // the subsequent moves are done by `MOVE` instructions. 95 | 96 | // If the argument count is 1 (`argument count = c - 1`), we can just map 97 | // directly to Lua 5.1 `TFORLOOP`. 98 | if c.0 == 0 { 99 | builder.instruction(lua51::Instruction::TForLoop { 100 | a, 101 | mode: BC(Unused, Generic(c.0 + 1)), 102 | }); 103 | } else { 104 | // In order to mimic the Lua 5.0 `TFORLOOP` instruction we can't insert `MOVE` 105 | // instructions after the `TFORLOOP` jump like Lua 5.1 does, because that won't 106 | // move our results to the stack after the last iteration. 107 | 108 | let variable_count = c.0 + 1; 109 | let call_base = a + variable_count + 2; 110 | let constant_nil = constant_manager.constant_nil(); 111 | 112 | // Move the iterator function, the table and the index to our call base. 113 | builder.instruction(lua51::Instruction::Move { 114 | a: call_base, 115 | mode: BC(Register(a), Unused), 116 | }); 117 | builder.extra_instruction(lua51::Instruction::Move { 118 | a: call_base + 1, 119 | mode: BC(Register(a + 1), Unused), 120 | }); 121 | builder.extra_instruction(lua51::Instruction::Move { 122 | a: call_base + 2, 123 | mode: BC(Register(a + 2), Unused), 124 | }); 125 | 126 | // Call to iterator function (e.g. `ipairs`). 127 | builder.extra_instruction(lua51::Instruction::Call { 128 | a: call_base, 129 | mode: BC(Generic(3), Generic(variable_count + 1)), 130 | }); 131 | 132 | // Move the results of our call back to our control variables. After the call, 133 | // our results will be at the call base and upwards and our control variables 134 | // are located at A+2 and upwards. 135 | for offset in (0..variable_count).rev() { 136 | builder.extra_instruction(lua51::Instruction::Move { 137 | a: a + offset + 2, 138 | mode: BC(Register(call_base + offset), Unused), 139 | }); 140 | } 141 | 142 | // Instead of using the the constant nil in the `EQ` instruction directly, we 143 | // load in on to the stack using `LOADK` so that we don't have to worry about 144 | // the maximum constant index for the B and C registers. 145 | builder.extra_instruction(lua51::Instruction::LoadK { 146 | a: call_base, 147 | mode: Bx(constant_nil), 148 | }); 149 | 150 | // The control variable for the key/index is located at A+2, so as soon as it 151 | // is nil, we are done with the iteration. If it is not nil we jump back and 152 | // iterate again. It's not obvious from the code here but following this 153 | // instruction will always be a `JMP` instruction that specifies the destination 154 | // of the jump. That `JMP` instruction doesn't need any modification here. 155 | builder.extra_instruction(lua51::Instruction::Equals { 156 | a: 0, 157 | mode: BC(ConstantRegister(a + 2, false), ConstantRegister(call_base, false)), 158 | }); 159 | } 160 | } 161 | lua50::Instruction::TForPrep { a, mode } => { 162 | // Globals for saving RA+1 and RA+2. 163 | let ra1_constant = constant_manager.create_unique(builder.get_program_counter()); 164 | let ra2_constant = constant_manager.create_unique(builder.get_program_counter() + 1); 165 | 166 | let type_global_constant = constant_manager.constant_for_str("type"); 167 | let table_global_constant = constant_manager.constant_for_str("table"); 168 | let next_global_constant = constant_manager.constant_for_str("next"); 169 | 170 | // Instructions to save RA+1 and RA+2. 171 | builder.instruction(lua51::Instruction::SetGlobal { 172 | a: a + 1, 173 | mode: Bx(ra1_constant), 174 | }); 175 | builder.extra_instruction(lua51::Instruction::SetGlobal { 176 | a: a + 2, 177 | mode: Bx(ra2_constant), 178 | }); 179 | 180 | // Prepare arguments and call the "type" function on the value in RA. 181 | builder.extra_instruction(lua51::Instruction::GetGlobal { 182 | a: a + 1, 183 | mode: Bx(type_global_constant), 184 | }); 185 | builder.extra_instruction(lua51::Instruction::Move { 186 | a: a + 2, 187 | mode: BC(Register(a), Unused), 188 | }); 189 | builder.extra_instruction(lua51::Instruction::Call { 190 | a: a + 1, 191 | mode: BC(Generic(2), Generic(2)), 192 | }); 193 | 194 | // Load the string "table" to compare the result of the previous type to. 195 | builder.extra_instruction(lua51::Instruction::LoadK { 196 | a: a + 2, 197 | mode: Bx(table_global_constant), 198 | }); 199 | 200 | // If it's not a table we want to restore RA+1 and RA+2, so we jump to that 201 | // instruction. 202 | builder.extra_instruction(lua51::Instruction::Equals { 203 | a: 0, 204 | mode: BC(ConstantRegister(a + 1, false), ConstantRegister(a + 2, false)), 205 | }); 206 | // Because of the way the builder works, the jump destination in Bx would be 207 | // moved when re-emitting the instructions. Therefore we fix the jump 208 | // destination so we land on the correct instruction. 209 | builder.extra_instruction(lua51::Instruction::Jump { a, mode: SignedBx(2) }); 210 | builder.last_instruction_fixed(); 211 | 212 | // Move RA to RA+1 and put the global "next" into RA, exactly like `TForPrep` 213 | // does. Since we restore RA+1 from `ra1_constant` afterwards, we don't move the 214 | // value to the stack directly but rather to `ra1_constant`. 215 | builder.extra_instruction(lua51::Instruction::SetGlobal { a, mode: Bx(ra1_constant) }); 216 | builder.extra_instruction(lua51::Instruction::GetGlobal { 217 | a, 218 | mode: Bx(next_global_constant), 219 | }); 220 | 221 | // Restore RA+1 and RA+2. 222 | builder.extra_instruction(lua51::Instruction::GetGlobal { 223 | a: a + 1, 224 | mode: Bx(ra1_constant), 225 | }); 226 | builder.extra_instruction(lua51::Instruction::GetGlobal { 227 | a: a + 2, 228 | mode: Bx(ra2_constant), 229 | }); 230 | 231 | // Technically this jump could be removed if it lands on the very next 232 | // instruction, which will happen it the next instruction is a 233 | // `TForLoop`. But I think it's better to keep this here for 234 | // simplicity. 235 | builder.extra_instruction(lua51::Instruction::Jump { a, mode }); 236 | } 237 | lua50::Instruction::SetList { a, mode: Bx(bx) } | lua50::Instruction::SetListO { a, mode: Bx(bx) } => { 238 | let flat_index = bx + 1; 239 | let page = flat_index / settings.output.fields_per_flush; 240 | let offset = flat_index % settings.output.fields_per_flush; 241 | 242 | // In Lua 5.1 `SETLISTO` and `SETLIST` became a single instruction. The behavior 243 | // of `SETLISTO` is used when b is equal to zero. 244 | let b = match matches!(instruction, lua50::Instruction::SetListO { .. }) { 245 | true => 0, 246 | false => offset, 247 | }; 248 | 249 | // Good case: we are on the first page and the number of entries is smaller than 250 | // either `LFIELDS_PER_FLUSH`, meaning we can just insert a `SETLIST` 251 | // instruction without any modification to the previous code. 252 | if page == 0 && flat_index <= u64::min(settings.lua50.fields_per_flush, settings.output.fields_per_flush) { 253 | builder.instruction(lua51::Instruction::SetList { 254 | a, 255 | mode: BC(Generic(b), Generic(1)), 256 | }); 257 | continue; 258 | } 259 | 260 | // Go back until we find some instruction that moves data to a stack position 261 | // that is the same as our A, because that is where the setup starts. 262 | for instruction_index in (0..(builder.get_program_counter() - 1)).rev() { 263 | let instruction = builder.get_instruction(instruction_index); 264 | 265 | // It might technically be possible for the element on slot A to be on the stack 266 | // already before any instructions if it is a parameter to a function call. So 267 | // we make sure that at least the first instruction will always match. 268 | // I am unsure that code like this can actually be emitted by the Lua compiler, 269 | // because any assignment of a table should start with a `NEWTABLE` instruction, 270 | // but better safe than sorry. 271 | if matches!(instruction.stack_destination(), Some(destination) if destination.start == a) || instruction_index == 0 { 272 | // Should either be `NEWTABLE` or `SETLIST`. 273 | if let lua51::Instruction::SetList { mode: BC(b, c), .. } = *instruction { 274 | let mut offset = b.0 as i64; 275 | let mut page = c.0; 276 | 277 | // Remove the `SETLIST` instruction. 278 | builder.remove_instruction(instruction_index); 279 | 280 | // Go back up the stack and update the stack positions. 281 | let mut instruction_index = instruction_index; 282 | while instruction_index < builder.get_program_counter() { 283 | let instruction = builder.get_instruction(instruction_index); 284 | 285 | if let Some(stack_destination) = instruction.stack_destination() { 286 | if offset + stack_destination.start as i64 - 1 == (a + settings.output.fields_per_flush) as i64 { 287 | // Add a new `SETLIST` instruction. 288 | builder.insert_extra_instruction(instruction_index, lua51::Instruction::SetList { 289 | a, 290 | mode: BC(Generic(settings.output.fields_per_flush), Generic(page)), 291 | }); 292 | 293 | offset -= settings.output.fields_per_flush as i64; 294 | page += 1; 295 | instruction_index += 1; 296 | continue; 297 | } 298 | } 299 | 300 | builder.get_instruction(instruction_index).move_stack_accesses(a, offset); 301 | instruction_index += 1; 302 | } 303 | } 304 | 305 | break; 306 | } 307 | } 308 | 309 | // Append the original instruction. 310 | builder.instruction(lua51::Instruction::SetList { 311 | a, 312 | mode: BC(Generic(b), Generic(page + 1)), 313 | }); 314 | } 315 | lua50::Instruction::Close { a, mode } => builder.instruction(lua51::Instruction::Close { a, mode }), 316 | lua50::Instruction::Closure { a, mode } => builder.instruction(lua51::Instruction::Closure { a, mode }), 317 | }; 318 | } 319 | 320 | // Lua 5.0 used to collect variadic arguments in a table and store them in a 321 | // local variable `arg`. Lua 5.1 does things a bit differently, so for 322 | // variadic functions we insert instructions that are the equivalent of 323 | // `local arg = {...}`. Since we are at the very beginning of our function 324 | // call, we don't need to worry about saving the stack above our 325 | // arguments. Lua 5.1 has a flag called `VARARG_NEEDSARG` that can be set on the 326 | // function header to achieve the same result, but it is behind a 327 | // compatibility feature flag. Even though that feature should be turned on 328 | // most of the time, I chose this approach because it will always work. 329 | if is_variadic { 330 | let arg_stack_position = parameter_count as u64; 331 | 332 | // Create a new empty table to hold our arguments. 333 | builder.insert_extra_instruction(0, lua51::Instruction::NewTable { 334 | a: arg_stack_position + 1, 335 | mode: BC(Unused, Unused), 336 | }); 337 | 338 | // Push all variadic arguments onto the stack. 339 | builder.insert_extra_instruction(1, lua51::Instruction::VarArg { 340 | a: arg_stack_position + 2, 341 | mode: BC(Generic(0), Unused), 342 | }); 343 | 344 | // Add all values from the stack to the table. 345 | builder.insert_extra_instruction(2, lua51::Instruction::SetList { 346 | a: arg_stack_position + 1, 347 | mode: BC(Generic(0), Generic(1)), 348 | }); 349 | 350 | // Move the table to the location of the argument. 351 | builder.insert_extra_instruction(3, lua51::Instruction::Move { 352 | a: arg_stack_position, 353 | mode: BC(Register(arg_stack_position + 1), Unused), 354 | }); 355 | } 356 | 357 | builder.finalize(maximum_stack_size, settings) 358 | } 359 | 360 | #[cfg(test)] 361 | mod tests { 362 | use super::{lua50, lua51, Bx, BC}; 363 | use crate::function::constant::Constant; 364 | use crate::function::instruction::{ConstantRegister, Generic, Register, SignedBx, Unused}; 365 | use crate::function::upcast; 366 | use crate::{LunifyError, Settings}; 367 | 368 | fn test_settings() -> Settings<'static> { 369 | let lua50 = lua50::Settings { 370 | fields_per_flush: 5, 371 | ..lua50::Settings::default() 372 | }; 373 | 374 | let lua51 = lua51::Settings::default(); 375 | 376 | let output = lua51::Settings { 377 | fields_per_flush: 8, 378 | ..lua51::Settings::default() 379 | }; 380 | 381 | Settings { lua50, lua51, output } 382 | } 383 | 384 | fn lua50_setlist(size: u64, settings: Settings) -> Vec { 385 | let mut instructions = vec![lua50::Instruction::NewTable { 386 | a: 0, 387 | mode: BC(Unused, Unused), 388 | }]; 389 | 390 | for index in 0..size { 391 | let stack_position = (index % settings.lua50.fields_per_flush) + 1; 392 | 393 | instructions.push(lua50::Instruction::LoadK { 394 | a: stack_position, 395 | mode: Bx(0), 396 | }); 397 | 398 | if stack_position == settings.lua50.fields_per_flush || index + 1 == size { 399 | instructions.push(lua50::Instruction::SetList { a: 0, mode: Bx(index) }); 400 | } 401 | } 402 | 403 | instructions 404 | } 405 | 406 | fn output_setlist(size: u64, settings: Settings) -> Vec { 407 | let mut instructions = vec![lua51::Instruction::NewTable { 408 | a: 0, 409 | mode: BC(Unused, Unused), 410 | }]; 411 | 412 | for index in 0..size { 413 | let stack_position = (index % settings.output.fields_per_flush) + 1; 414 | let page = (index / settings.output.fields_per_flush) + 1; 415 | 416 | instructions.push(lua51::Instruction::LoadK { 417 | a: stack_position, 418 | mode: Bx(0), 419 | }); 420 | 421 | if stack_position == settings.output.fields_per_flush || index + 1 == size { 422 | instructions.push(lua51::Instruction::SetList { 423 | a: 0, 424 | mode: BC(Generic(stack_position), Generic(page)), 425 | }); 426 | } 427 | } 428 | 429 | instructions 430 | } 431 | 432 | fn set_list_test(count: u64) -> Result<(), LunifyError> { 433 | let settings = test_settings(); 434 | let instructions = lua50_setlist(count, settings); 435 | let instruction_count = instructions.len(); 436 | 437 | let (instructions, _) = upcast( 438 | instructions, 439 | vec![0; instruction_count], 440 | &mut Vec::new(), 441 | &mut 2, 442 | 0, 443 | false, 444 | &settings, 445 | )?; 446 | 447 | let expected = output_setlist(count, settings); 448 | 449 | assert_eq!(instructions, expected); 450 | Ok(()) 451 | } 452 | 453 | #[test] 454 | fn upcast_test() -> Result<(), LunifyError> { 455 | let settings = test_settings(); 456 | let instructions = vec![lua50::Instruction::Test { 457 | a: 0, 458 | mode: BC(Register(0), Generic(0)), 459 | }]; 460 | 461 | let (instructions, _) = upcast(instructions, vec![0; 1], &mut Vec::new(), &mut 2, 0, false, &settings)?; 462 | let expected = vec![lua51::Instruction::TestSet { 463 | a: 0, 464 | mode: BC(ConstantRegister(0, false), Generic(0)), 465 | }]; 466 | 467 | assert_eq!(instructions, expected); 468 | Ok(()) 469 | } 470 | 471 | #[test] 472 | fn upcast_for_loop() -> Result<(), LunifyError> { 473 | let settings = test_settings(); 474 | let instructions = vec![lua50::Instruction::ForLoop { a: 0, mode: SignedBx(-1) }]; 475 | 476 | let (instructions, _) = upcast(instructions, vec![0; 1], &mut Vec::new(), &mut 2, 0, false, &settings)?; 477 | let expected = vec![ 478 | lua51::Instruction::GetGlobal { a: 3, mode: Bx(0) }, 479 | lua51::Instruction::SetGlobal { a: 3, mode: Bx(0) }, 480 | lua51::Instruction::ForLoop { a: 0, mode: SignedBx(-3) }, 481 | ]; 482 | 483 | assert_eq!(instructions, expected); 484 | Ok(()) 485 | } 486 | 487 | #[test] 488 | fn upcast_t_for_loop() -> Result<(), LunifyError> { 489 | let settings = test_settings(); 490 | let instructions = vec![lua50::Instruction::TForLoop { 491 | a: 0, 492 | mode: BC(Unused, Generic(0)), 493 | }]; 494 | 495 | let (instructions, _) = upcast(instructions, vec![0; 1], &mut Vec::new(), &mut 2, 0, false, &settings)?; 496 | let expected = vec![lua51::Instruction::TForLoop { 497 | a: 0, 498 | mode: BC(Unused, Generic(1)), 499 | }]; 500 | 501 | assert_eq!(instructions, expected); 502 | Ok(()) 503 | } 504 | 505 | #[test] 506 | fn upcast_t_for_loop_c_bigger_zero() -> Result<(), LunifyError> { 507 | let settings = test_settings(); 508 | let instructions = vec![lua50::Instruction::TForLoop { 509 | a: 0, 510 | mode: BC(Unused, Generic(1)), 511 | }]; 512 | let mut constants = Vec::new(); 513 | 514 | let (instructions, _) = upcast(instructions, vec![0; 1], &mut constants, &mut 2, 0, false, &settings)?; 515 | let expected = vec![ 516 | lua51::Instruction::Move { 517 | a: 4, 518 | mode: BC(Register(0), Unused), 519 | }, 520 | lua51::Instruction::Move { 521 | a: 5, 522 | mode: BC(Register(1), Unused), 523 | }, 524 | lua51::Instruction::Move { 525 | a: 6, 526 | mode: BC(Register(2), Unused), 527 | }, 528 | lua51::Instruction::Call { 529 | a: 4, 530 | mode: BC(Generic(3), Generic(3)), 531 | }, 532 | lua51::Instruction::Move { 533 | a: 3, 534 | mode: BC(Register(5), Unused), 535 | }, 536 | lua51::Instruction::Move { 537 | a: 2, 538 | mode: BC(Register(4), Unused), 539 | }, 540 | lua51::Instruction::LoadK { a: 4, mode: Bx(0) }, 541 | lua51::Instruction::Equals { 542 | a: 0, 543 | mode: BC(ConstantRegister(2, false), ConstantRegister(4, false)), 544 | }, 545 | ]; 546 | 547 | assert_eq!(instructions, expected); 548 | assert_eq!(&constants, [Constant::Nil].as_slice()); 549 | Ok(()) 550 | } 551 | 552 | #[test] 553 | fn upcast_t_for_prep() -> Result<(), LunifyError> { 554 | let settings = test_settings(); 555 | let instructions = vec![lua50::Instruction::TForPrep { a: 0, mode: SignedBx(-1) }]; 556 | let mut constants = Vec::new(); 557 | 558 | let (instructions, _) = upcast(instructions, vec![0; 1], &mut constants, &mut 2, 0, false, &settings)?; 559 | let expected = vec![ 560 | lua51::Instruction::SetGlobal { a: 1, mode: Bx(0) }, 561 | lua51::Instruction::SetGlobal { a: 2, mode: Bx(1) }, 562 | lua51::Instruction::GetGlobal { a: 1, mode: Bx(2) }, 563 | lua51::Instruction::Move { 564 | a: 2, 565 | mode: BC(Register(0), Unused), 566 | }, 567 | lua51::Instruction::Call { 568 | a: 1, 569 | mode: BC(Generic(2), Generic(2)), 570 | }, 571 | lua51::Instruction::LoadK { a: 2, mode: Bx(3) }, 572 | lua51::Instruction::Equals { 573 | a: 0, 574 | mode: BC(ConstantRegister(1, false), ConstantRegister(2, false)), 575 | }, 576 | lua51::Instruction::Jump { a: 0, mode: SignedBx(2) }, 577 | lua51::Instruction::SetGlobal { a: 0, mode: Bx(0) }, 578 | lua51::Instruction::GetGlobal { a: 0, mode: Bx(4) }, 579 | lua51::Instruction::GetGlobal { a: 1, mode: Bx(0) }, 580 | lua51::Instruction::GetGlobal { a: 2, mode: Bx(1) }, 581 | lua51::Instruction::Jump { a: 0, mode: SignedBx(-13) }, 582 | ]; 583 | let expected_constants = [ 584 | Constant::String("type\0".to_owned()), 585 | Constant::String("table\0".to_owned()), 586 | Constant::String("next\0".to_owned()), 587 | ]; 588 | 589 | assert_eq!(instructions, expected); 590 | assert_eq!(&constants[2..], expected_constants.as_slice()); 591 | Ok(()) 592 | } 593 | 594 | #[test] 595 | fn upcast_set_list() -> Result<(), LunifyError> { 596 | set_list_test(4) 597 | } 598 | 599 | #[test] 600 | fn upcast_set_list_bigger_than_50_flush() -> Result<(), LunifyError> { 601 | set_list_test(6) 602 | } 603 | 604 | #[test] 605 | fn upcast_set_list_bigger_than_51_flush() -> Result<(), LunifyError> { 606 | set_list_test(9) 607 | } 608 | 609 | #[test] 610 | fn upcast_set_list_large() -> Result<(), LunifyError> { 611 | set_list_test(20) 612 | } 613 | 614 | #[test] 615 | fn upcast_set_list_from_parameters() -> Result<(), LunifyError> { 616 | let settings = test_settings(); 617 | let instructions = vec![lua50::Instruction::LoadK { a: 5, mode: Bx(0) }, lua50::Instruction::SetList { 618 | a: 0, 619 | mode: Bx(4), 620 | }]; 621 | 622 | let (instructions, _) = upcast(instructions, vec![0; 2], &mut Vec::new(), &mut 2, 0, false, &settings)?; 623 | let expected = vec![lua51::Instruction::LoadK { a: 5, mode: Bx(0) }, lua51::Instruction::SetList { 624 | a: 0, 625 | mode: BC(Generic(5), Generic(1)), 626 | }]; 627 | 628 | assert_eq!(instructions, expected); 629 | Ok(()) 630 | } 631 | 632 | #[test] 633 | fn upcast_set_list_from_parameters_bigger_than_50_flush() -> Result<(), LunifyError> { 634 | let settings = test_settings(); 635 | let instructions = vec![ 636 | lua50::Instruction::LoadK { a: 5, mode: Bx(0) }, 637 | lua50::Instruction::SetList { a: 0, mode: Bx(4) }, 638 | lua50::Instruction::LoadK { a: 1, mode: Bx(0) }, 639 | lua50::Instruction::SetList { a: 0, mode: Bx(5) }, 640 | ]; 641 | 642 | let (instructions, _) = upcast(instructions, vec![0; 12], &mut Vec::new(), &mut 2, 0, false, &settings)?; 643 | let expected = vec![ 644 | lua51::Instruction::LoadK { a: 5, mode: Bx(0) }, 645 | lua51::Instruction::LoadK { a: 6, mode: Bx(0) }, 646 | lua51::Instruction::SetList { 647 | a: 0, 648 | mode: BC(Generic(6), Generic(1)), 649 | }, 650 | ]; 651 | 652 | assert_eq!(instructions, expected); 653 | Ok(()) 654 | } 655 | 656 | #[test] 657 | fn variadic() -> Result<(), LunifyError> { 658 | let settings = test_settings(); 659 | let instructions = vec![lua50::Instruction::LoadK { a: 1, mode: Bx(0) }]; 660 | 661 | let (instructions, _) = upcast(instructions, vec![0; 1], &mut Vec::new(), &mut 2, 0, true, &settings)?; 662 | let expected = vec![ 663 | lua51::Instruction::NewTable { 664 | a: 1, 665 | mode: BC(Unused, Unused), 666 | }, 667 | lua51::Instruction::VarArg { 668 | a: 2, 669 | mode: BC(Generic(0), Unused), 670 | }, 671 | lua51::Instruction::SetList { 672 | a: 1, 673 | mode: BC(Generic(0), Generic(1)), 674 | }, 675 | lua51::Instruction::Move { 676 | a: 0, 677 | mode: BC(Register(1), Unused), 678 | }, 679 | lua51::Instruction::LoadK { a: 1, mode: Bx(0) }, 680 | ]; 681 | 682 | assert_eq!(instructions, expected); 683 | Ok(()) 684 | } 685 | } 686 | --------------------------------------------------------------------------------