luaur-compiler 0.1.1

Luau source-to-bytecode compiler (Rust).
Documentation
use crate::enums::global::Global;
use crate::functions::analyze_builtins::analyze_builtins;
use crate::functions::assign_mutable::assign_mutable;
use crate::functions::build_table_constant_map::build_table_constant_map;
use crate::functions::build_type_map::build_type_map;
use crate::functions::fold_constants::fold_constants;
use crate::functions::get_global_state::get_global_state;
use crate::functions::predict_table_shapes::predict_table_shapes;
use crate::functions::set_compile_options_for_native_compilation::set_compile_options_for_native_compilation;
use crate::functions::track_values::track_values;
use crate::records::compile_error::CompileError;
use crate::records::compile_options::CompileOptions;
use crate::records::compiler::Compiler;
use crate::records::fenv_visitor::FenvVisitor;
use crate::records::function_visitor::FunctionVisitor;
use luaur_ast::records::ast_array::AstArray;
use luaur_ast::records::ast_expr_function::AstExprFunction;
use luaur_ast::records::ast_name::AstName;
use luaur_ast::records::ast_name_table::AstNameTable;
use luaur_ast::records::ast_node::AstNode;
use luaur_ast::records::ast_stat::AstStat;
use luaur_ast::records::parse_result::ParseResult;
use luaur_bytecode::records::bytecode_builder::BytecodeBuilder;
use luaur_common::enums::luau_bytecode_type::{
    LBC_TYPE_TAGGED_USERDATA_BASE, LBC_TYPE_TAGGED_USERDATA_END,
};
use luaur_common::enums::luau_proto_flag::LuauProtoFlag;
use luaur_common::macros::luau_assert::LUAU_ASSERT;
use luaur_common::macros::luau_timetrace_scope::LUAU_TIMETRACE_SCOPE;

pub fn compile_or_throw_bytecode_builder_parse_result_ast_name_table_compile_options(
    bytecode: &mut BytecodeBuilder,
    parse_result: &ParseResult,
    names: &mut AstNameTable,
    input_options: &CompileOptions,
) {
    LUAU_TIMETRACE_SCOPE!("compileOrThrow", "Compiler");

    LUAU_ASSERT!(!parse_result.root.is_null());
    LUAU_ASSERT!(parse_result.errors.is_empty());

    let mut options = *input_options;
    let mut main_flags = 0u8;

    for hc in &parse_result.hotcomments {
        if hc.header {
            if let Some(value) = hc.content.strip_prefix("optimize ") {
                let level = value.parse::<i32>().unwrap_or(0).clamp(0, 2);
                options.optimization_level = level;
            }

            if hc.content == "native" {
                main_flags |= LuauProtoFlag::LPF_NATIVE_MODULE as u8;
                set_compile_options_for_native_compilation(&mut options);
            }
        }
    }

    let root = parse_result.root;
    let root_node = root as *mut AstNode;

    let mut functions = Vec::<*mut AstExprFunction>::new();
    {
        let mut function_visitor = FunctionVisitor::new(&mut functions);
        unsafe {
            luaur_ast::visit::ast_stat_visit(root as *mut AstStat, &mut function_visitor);
        }

        if function_visitor.has_native_function {
            set_compile_options_for_native_compilation(&mut options);
        }
    }

    let mut compiler = Compiler::compiler(bytecode, &options, names);

    assign_mutable(&mut compiler.globals, names, options.mutable_globals);
    track_values(&mut compiler.globals, &mut compiler.variables, root_node);

    if options.optimization_level >= 1
        && (!names.get(c"getfenv".as_ptr()).value.is_null()
            || !names.get(c"setfenv".as_ptr()).value.is_null())
    {
        let mut fenv_visitor =
            FenvVisitor::fenv_visitor(&mut compiler.getfenv_used, &mut compiler.setfenv_used);
        unsafe {
            luaur_ast::visit::ast_stat_visit(root as *mut AstStat, &mut fenv_visitor);
        }
    }

    if options.optimization_level >= 2 && !compiler.getfenv_used && !compiler.setfenv_used {
        compiler.builtins_fold = &compiler.builtins as *const _;

        let math = names.get(c"math".as_ptr());
        if !math.value.is_null() && get_global_state(&compiler.globals, math) == Global::Default {
            compiler.builtins_fold_library_k = true;
        } else if !options.libraries_with_known_members.is_null() {
            unsafe {
                let mut ptr = options.libraries_with_known_members;
                while !(*ptr).is_null() {
                    let name = names.get(*ptr);
                    if !name.value.is_null()
                        && get_global_state(&compiler.globals, name) == Global::Default
                    {
                        compiler.builtins_fold_library_k = true;
                        break;
                    }
                    ptr = ptr.add(1);
                }
            }
        }
    }

    if options.optimization_level >= 1 {
        analyze_builtins(
            &mut compiler.builtins,
            &compiler.globals,
            &compiler.variables,
            &options,
            root_node,
            names,
        );

        if luaur_common::FFlag::LuauCompilePropagateTableProps2.get()
            && luaur_common::FFlag::LuauCompileFoldOptimize.get()
        {
            build_table_constant_map(
                &mut compiler.table_constants,
                &compiler.variables,
                root_node,
            );
        }

        fold_constants(
            &mut compiler.constants,
            &mut compiler.variables,
            &mut compiler.locstants,
            compiler.builtins_fold,
            compiler.builtins_fold_library_k,
            options.library_member_constant_cb,
            root_node,
            names,
            &compiler.table_constants,
            core::ptr::null_mut(),
            core::ptr::null_mut(),
        );

        predict_table_shapes(&mut compiler.table_shapes, root_node);
    }

    if !options.userdata_types.is_null() {
        unsafe {
            let mut ptr = options.userdata_types;
            while !(*ptr).is_null() {
                let name = names.get(*ptr);
                if !name.value.is_null() {
                    let name_str = core::ffi::CStr::from_ptr(name.value).to_string_lossy();
                    *compiler.userdata_types.get_or_insert(name) =
                        bytecode.add_userdata_type(&name_str) as u8;
                }
                ptr = ptr.add(1);
            }

            let count = ptr.offset_from(options.userdata_types) as u16;
            if count > (LBC_TYPE_TAGGED_USERDATA_END.0 - LBC_TYPE_TAGGED_USERDATA_BASE.0) {
                CompileError::raise(
                    &(*root).base.base.location,
                    format_args!("Exceeded userdata type limit in the compilation options"),
                );
            }
        }
    }

    if options.type_info_level >= 1 || options.optimization_level >= 2 {
        build_type_map(
            &mut compiler.function_types,
            &mut compiler.local_types,
            &mut compiler.expr_types,
            root_node,
            options.vector_type,
            &compiler.userdata_types,
            &compiler.builtin_types,
            &compiler.builtins,
            &compiler.globals,
            options.library_member_type_cb,
            bytecode,
        );
    }

    for expr in functions {
        let mut protoflags = 0u8;
        compiler.compile_function(expr, &mut protoflags);

        if (protoflags & LuauProtoFlag::LPF_NATIVE_FUNCTION as u8) != 0
            && (main_flags & LuauProtoFlag::LPF_NATIVE_MODULE as u8) == 0
        {
            main_flags |= LuauProtoFlag::LPF_NATIVE_FUNCTION as u8;
        }
    }

    let mut main = unsafe {
        AstExprFunction::new(
            (*root).base.base.location,
            AstArray::default(),
            AstArray::default(),
            AstArray::default(),
            core::ptr::null_mut(),
            AstArray::default(),
            true,
            Default::default(),
            root,
            0,
            AstName::default(),
            core::ptr::null_mut(),
            core::ptr::null_mut(),
            None,
        )
    };

    let mainid = compiler.compile_function(&mut main, &mut main_flags);

    let main_ptr = &mut main as *mut AstExprFunction;
    let mainf = compiler.functions.find(&main_ptr);
    LUAU_ASSERT!(mainf.map_or(false, |f| f.upvals.is_empty()));

    bytecode.set_main_function(mainid);
    bytecode.finalize();
}