swamp_script_vm_test/
util.rs

1use seq_map::SeqMap;
2use std::ptr;
3use swamp_script_code_gen::alloc::ConstantMemoryRegion;
4use swamp_script_code_gen::alloc_util::type_size_and_alignment;
5use swamp_script_code_gen::{CodeGenState, Error, GenOptions};
6use swamp_script_compile::Program;
7use swamp_script_compile::compile_string;
8use swamp_script_semantic::{ConstantId, Function};
9use swamp_script_types::Type;
10use swamp_vm::host::HostArgs;
11use swamp_vm::{Vm, VmSetup};
12use swamp_vm_disasm::{disasm_instructions_color, disasm_instructions_no_color};
13use swamp_vm_types::ConstantMemoryAddress;
14use tracing::info;
15
16fn gen_internal(code: &str) -> Result<(CodeGenState, Program), Error> {
17    let (program, main_module, _source_map) = compile_string(code).unwrap();
18
19    let mut code_gen = CodeGenState::new();
20
21    code_gen.reserve_space_for_constants(&program.state.constants_in_dependency_order)?;
22
23    let debug_expr = main_module.main_expression.as_ref().unwrap();
24    let main_expression = main_module.main_expression.as_ref().unwrap();
25    let halt_function = GenOptions {
26        is_halt_function: true,
27    };
28
29    code_gen.gen_main_function(main_expression, &halt_function)?;
30
31    let normal_function = GenOptions {
32        is_halt_function: false,
33    };
34
35    for internal_function_def in &main_module.symbol_table.internal_functions() {
36        code_gen.gen_function_def(internal_function_def, &normal_function)?;
37    }
38
39    for (associated_on_type, impl_functions) in
40        &program.state.instantiator.associated_impls.functions
41    {
42        if !associated_on_type.is_concrete() {
43            continue;
44        }
45        if associated_on_type == &Type::Int
46            || associated_on_type == &Type::Float
47            || associated_on_type == &Type::Bool
48            || associated_on_type == &Type::String
49        {
50            continue;
51        }
52
53        for (_name, func) in &impl_functions.functions {
54            if func.name().clone().starts_with("instantiated ") {
55                continue;
56            }
57            match &**func {
58                Function::Internal(int_fn) => {
59                    code_gen.gen_function_def(int_fn, &normal_function)?;
60                }
61
62                Function::External(_ext_fn) => {}
63            }
64        }
65    }
66
67    code_gen.gen_constants_expression_functions_in_order(
68        &program.state.constants_in_dependency_order,
69    )?;
70
71    code_gen.finalize();
72
73    Ok((code_gen, program))
74}
75
76fn gen_internal_debug(code: &str) -> Result<(CodeGenState, Program), Error> {
77    let (code_gen, program) = gen_internal(code)?;
78    let disassembler_output = disasm_instructions_color(
79        code_gen.instructions(),
80        code_gen.comments(),
81        &code_gen.create_function_sections(),
82    );
83
84    eprintln!("{disassembler_output}");
85
86    Ok((code_gen, program))
87}
88
89fn exec_code_gen_state(code_gen_state: CodeGenState) -> Vm {
90    let (instructions, mut constants_memory, constant_functions) =
91        code_gen_state.take_instructions_and_constants();
92
93    for (_constant_id, constant_func) in constant_functions {
94        let setup = VmSetup {
95            frame_memory_size: 1024,
96            heap_memory_size: 1024,
97            constant_memory: constants_memory.clone(),
98        };
99
100        let mut vm = Vm::new(instructions.clone(), setup);
101
102        vm.execute_from_ip(&constant_func.ip);
103
104        unsafe {
105            ptr::copy_nonoverlapping(
106                vm.frame_memory().as_ptr(),
107                constants_memory
108                    .as_mut_ptr()
109                    .add(constant_func.target_constant_memory.addr.0 as usize),
110                constant_func.target_constant_memory.size.0 as usize,
111            );
112        }
113    }
114
115    let setup = VmSetup {
116        frame_memory_size: 1024,
117        heap_memory_size: 1024,
118        constant_memory: constants_memory,
119    };
120    let mut vm = Vm::new(instructions, setup);
121
122    vm.execute();
123
124    vm
125}
126
127pub fn exec_internal(code: &str) -> Result<Vm, Error> {
128    let (code_gen, _program) = gen_internal_debug(code)?;
129
130    Ok(exec_code_gen_state(code_gen))
131}
132
133fn exec_internal_debug(code: &str) -> Result<Vm, Error> {
134    exec_internal(code)
135}
136
137fn trim_lines(text: &str) -> String {
138    text.lines()
139        .map(|line| {
140            // Ignore comments that starts with ;
141            line.split(';').next().unwrap_or("").trim()
142        })
143        .filter(|line| !line.is_empty())
144        .collect::<Vec<_>>()
145        .join("\n")
146}
147
148fn compare_line_outputs(encountered: &str, expected: &str) {
149    let encountered_trimmed = trim_lines(encountered);
150    let expected_trimmed = trim_lines(expected);
151
152    eprintln!("{encountered}");
153    assert_eq!(encountered_trimmed, expected_trimmed);
154}
155
156fn compare_hex_outputs(memory: &[u8], expected_hex: &str) {
157    let encountered_hexed = hexify::format_hex(memory);
158    let expected_hex_trimmed = expected_hex.trim();
159
160    compare_line_outputs(&encountered_hexed, expected_hex_trimmed);
161}
162
163fn exec(code: &str, expected_hex: &str) {
164    let vm = exec_internal_debug(code).expect("should work");
165
166    compare_hex_outputs(&vm.stack_memory()[..16], expected_hex);
167}
168
169pub fn exec_with_assembly(code: &str, expected_assembly: &str, expected_hex: &str) {
170    let (generator, _program) = gen_internal_debug(code).expect("should work");
171
172    let disassembler_output = disasm_instructions_no_color(
173        generator.instructions(),
174        generator.comments(),
175        &generator.create_function_sections(),
176        false,
177    );
178    compare_line_outputs(&disassembler_output, expected_assembly);
179
180    let vm = exec_code_gen_state(generator);
181
182    compare_hex_outputs(&vm.frame_memory()[..16], expected_hex);
183}
184
185/// # Panics
186///
187pub fn exec_with_host_function<F>(
188    code: &str,
189    expected_assembly: &str,
190    expected_hex: &str,
191    id: &str,
192    callback: F,
193) where
194    F: 'static + FnMut(HostArgs),
195{
196    let vm = exec_with_host_function_internal(code, expected_assembly, id, callback);
197    compare_hex_outputs(&vm.frame_memory()[..16], expected_hex);
198}
199
200/// # Panics
201///
202pub fn exec_with_host_function_internal<F>(
203    code: &str,
204    expected_assembly: &str,
205    id: &str,
206    callback: F,
207) -> Vm
208where
209    F: 'static + FnMut(HostArgs),
210{
211    let (generator, program) = gen_internal_debug(code).expect("should work");
212
213    let disassembler_output = disasm_instructions_no_color(
214        generator.instructions(),
215        generator.comments(),
216        &generator.create_function_sections(),
217        false,
218    );
219    compare_line_outputs(&disassembler_output, expected_assembly);
220
221    let module = program
222        .modules
223        .get(&["crate".to_string(), "test".to_string()])
224        .unwrap();
225
226    let external_id = module
227        .symbol_table
228        .get_external_function_declaration(id)
229        .unwrap();
230
231    let (instructions, constants, constant_infos) = generator.take_instructions_and_constants();
232    let setup = VmSetup {
233        frame_memory_size: 1024,
234        heap_memory_size: 1024,
235        constant_memory: constants,
236    };
237    let mut vm = Vm::new(instructions, setup);
238
239    vm.add_host_function(external_id.id as u16, callback);
240
241    vm.execute();
242
243    vm
244}
245
246/// # Panics
247///
248pub fn exec_with_host_function_show_heap<F>(
249    code: &str,
250    expected_assembly: &str,
251    start: usize,
252    count: usize,
253    expected_hex: &str,
254    id: &str,
255    callback: F,
256) where
257    F: 'static + FnMut(HostArgs),
258{
259    let vm = exec_with_host_function_internal(code, expected_assembly, id, callback);
260    compare_hex_outputs(&vm.heap_memory()[start..start + count], expected_hex);
261}
262
263fn exec_vars(code: &str, expected_hex: &str) {
264    let vm = exec_internal_debug(code);
265
266    compare_hex_outputs(&vm.unwrap().frame_memory()[..16], expected_hex);
267}
268
269fn gen_code(code: &str, expected_output: &str) {
270    let (generator, _program) = gen_internal_debug(code).expect("should work");
271
272    let disassembler_output = disasm_instructions_no_color(
273        generator.instructions(),
274        generator.comments(),
275        &generator.create_function_sections(),
276        false,
277    );
278
279    compare_line_outputs(&disassembler_output, expected_output);
280}
281
282pub fn exec_show_constants(code: &str, expected_hex: &str, expected_constants: &str) {
283    let vm = exec_internal_debug(code).unwrap();
284
285    compare_hex_outputs(&vm.stack_memory()[..16], expected_hex);
286    compare_hex_outputs(&vm.constants()[..16], expected_constants);
287}