Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standalone Compiler #140

Merged
merged 25 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5d58ba7
feat: initial standalone executable builder impl
CompeyDev Nov 20, 2023
dc2bab3
feat: initial unfinished rewrite for perf
CompeyDev Nov 21, 2023
441a1ea
fix: finalize updated standalone runtime system
CompeyDev Nov 22, 2023
2bf68c1
refactor: polish a few things and clean up code
CompeyDev Nov 22, 2023
4bb0eba
feat: disable unneeded CLI args for standalone
CompeyDev Nov 22, 2023
6087069
fix(windows): write file differently for windows
CompeyDev Nov 22, 2023
cf2f93d
fix: conditionally compile fs writing system for windows
CompeyDev Nov 23, 2023
2af8ed3
feat: proper args support for standalone binaries
CompeyDev Nov 23, 2023
1e43f70
feat: SUPER fast standalone binaries using jemalloc & rayon
CompeyDev Nov 23, 2023
9c615ad
refactor: cleanup code & include logging
CompeyDev Jan 2, 2024
207de2f
Merge branch 'lune-org:main' into feat/standalone-executable
CompeyDev Jan 2, 2024
a5d118d
fix: improper trait bounds
CompeyDev Jan 4, 2024
5f68fee
fix: add rustdoc comments for build arg
CompeyDev Jan 4, 2024
75152bd
fix: panic on failure to get current exe
CompeyDev Jan 4, 2024
53b53a2
fix: avoid collecting to unneeded VecDequeue
CompeyDev Jan 4, 2024
6f4b2f4
fix: remove redundant multi-threading code
CompeyDev Jan 4, 2024
94b27d8
fix: make build option visible to user
CompeyDev Jan 4, 2024
6f3d11c
Merge branch 'lune-org:main' into feat/standalone-executable
CompeyDev Jan 5, 2024
3c2464d
feat: store magic signature as a meaningful constant
CompeyDev Jan 5, 2024
e425581
Merge branch 'feat/standalone-executable' of https://github.com/0x5ea…
CompeyDev Jan 5, 2024
35c5a3c
fix: use fixed (little) endianness
CompeyDev Jan 5, 2024
b071db3
feat: initial META chunk (de)serialization impl
CompeyDev Jan 5, 2024
94fd549
refactor: impl discovery logic as trait
CompeyDev Jan 13, 2024
55fe033
refactor: move most shared logic to executor.rs
CompeyDev Jan 13, 2024
ddff536
Make standalone compilation more minimal for initial release, minor p…
filiptibell Jan 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
mlua = { version = "0.9.1", features = ["luau", "luau-jit", "serialize"] }
tokio = { version = "1.24", features = ["full", "tracing"] }
os_str_bytes = { version = "6.4", features = ["conversions"] }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = "0.5"

### SERDE

Expand Down Expand Up @@ -124,6 +126,7 @@ regex = { optional = true, version = "1.7", default-features = false, features =
"unicode-perl",
] }
rustyline = { optional = true, version = "12.0" }
rayon = "1.8"

### ROBLOX

Expand Down
84 changes: 84 additions & 0 deletions src/cli/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use console::Style;
use std::{
env,
path::{Path, PathBuf},
process::ExitCode,
};
use tokio::{
fs::{self, OpenOptions},
io::AsyncWriteExt,
};

use anyhow::Result;
use mlua::Compiler as LuaCompiler;

/**
Compiles and embeds the bytecode of a requested lua file to form a standalone binary,
then writes it to an output file, with the required permissions.
*/
#[allow(clippy::similar_names)]
pub async fn build_standalone<T: AsRef<Path> + Into<PathBuf>>(
CompeyDev marked this conversation as resolved.
Show resolved Hide resolved
script_path: String,
output_path: T,
code: impl AsRef<[u8]>,
) -> Result<ExitCode> {
let log_output_path = output_path.as_ref().display();

let prefix_style = Style::new().green().bold();
let compile_prefix = prefix_style.apply_to("Compile");
let bytecode_prefix = prefix_style.apply_to("Bytecode");
let write_prefix = prefix_style.apply_to("Write");
let compiled_prefix = prefix_style.apply_to("Compiled");

println!("{compile_prefix} {script_path}");

// First, we read the contents of the lune interpreter as our starting point
let mut patched_bin = fs::read(env::current_exe()?).await?;
let base_bin_offset = u64::try_from(patched_bin.len())?;

// The signature which separates indicates the presence of bytecode to execute
// If a binary contains this signature, that must mean it is a standalone binary
let signature: Vec<u8> = vec![0x4f, 0x3e, 0xf8, 0x41, 0xc3, 0x3a, 0x52, 0x16];
CompeyDev marked this conversation as resolved.
Show resolved Hide resolved

// Compile luau input into bytecode
let bytecode = LuaCompiler::new()
.set_optimization_level(2)
.set_coverage_level(0)
.set_debug_level(0)
.compile(code);

println!(" {bytecode_prefix} {script_path}");

patched_bin.append(&mut bytecode.clone());

let mut meta = base_bin_offset.to_ne_bytes().to_vec();

// Include metadata in the META chunk, each field is 8 bytes
meta.append(&mut (bytecode.len() as u64).to_ne_bytes().to_vec()); // Size of bytecode, used to calculate end offset at runtime
meta.append(&mut 1_u64.to_ne_bytes().to_vec()); // Number of files, padded with null bytes - for future use
CompeyDev marked this conversation as resolved.
Show resolved Hide resolved

patched_bin.append(&mut meta);

// Append the signature to the base binary
patched_bin.append(&mut signature.clone());

// Write the compiled binary to file
#[cfg(target_family = "unix")]
OpenOptions::new()
.write(true)
.create(true)
.mode(0o770) // read, write and execute permissions for user and group
.open(&output_path)
.await?
.write_all(&patched_bin)
.await?;

#[cfg(target_family = "windows")]
fs::write(&output_path, &patched_bin).await?;

println!(" {write_prefix} {log_output_path}");

println!("{compiled_prefix} {log_output_path}");

Ok(ExitCode::SUCCESS)
}
33 changes: 30 additions & 3 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{fmt::Write as _, process::ExitCode};
use std::{env, fmt::Write as _, path::PathBuf, process::ExitCode};

use anyhow::{Context, Result};
use clap::Parser;
Expand All @@ -9,6 +9,7 @@ use tokio::{
io::{stdin, AsyncReadExt},
};

pub(crate) mod build;
pub(crate) mod gen;
pub(crate) mod repl;
pub(crate) mod setup;
Expand All @@ -20,6 +21,8 @@ use utils::{
listing::{find_lune_scripts, sort_lune_scripts, write_lune_scripts_list},
};

use self::build::build_standalone;

/// A Luau script runner
#[derive(Parser, Debug, Default, Clone)]
#[command(version, long_about = None)]
Expand All @@ -44,6 +47,8 @@ pub struct Cli {
/// Generate a Lune documentation file for Luau LSP
#[clap(long, hide = true)]
generate_docs_file: bool,
#[clap(long, hide = true)]
build: bool,
}
CompeyDev marked this conversation as resolved.
Show resolved Hide resolved

#[allow(dead_code)]
Expand Down Expand Up @@ -116,6 +121,7 @@ impl Cli {

return Ok(ExitCode::SUCCESS);
}

// Generate (save) definition files, if wanted
let generate_file_requested = self.setup
|| self.generate_luau_types
Expand Down Expand Up @@ -143,14 +149,17 @@ impl Cli {
if generate_file_requested {
return Ok(ExitCode::SUCCESS);
}
// If we did not generate any typedefs we know that the user did not
// provide any other options, and in that case we should enter the REPL

// If not in a standalone context and we don't have any arguments
// display the interactive REPL interface
return repl::show_interface().await;
}

// Figure out if we should read from stdin or from a file,
// reading from stdin is marked by passing a single "-"
// (dash) as the script name to run to the cli
let script_path = self.script_path.unwrap();

let (script_display_name, script_contents) = if script_path == "-" {
let mut stdin_contents = Vec::new();
stdin()
Expand All @@ -165,6 +174,24 @@ impl Cli {
let file_display_name = file_path.with_extension("").display().to_string();
(file_display_name, file_contents)
};

if self.build {
let output_path =
PathBuf::from(script_path.clone()).with_extension(env::consts::EXE_EXTENSION);

println!("Building {script_path} to {}...\n", output_path.display());

return Ok(
match build_standalone(script_path, output_path, script_contents).await {
Ok(exitcode) => exitcode,
Err(err) => {
eprintln!("{err}");
ExitCode::FAILURE
}
},
);
}

// Create a new lune object with all globals & run the script
let result = Lune::new()
.with_args(self.script_args)
Expand Down
Loading
Loading