#![cfg(test)] use super::{const_value, create_exported_function_no_locals, default_state}; use crate::{instance::Action, ImportDispatcher, Instance, ValueStack, DEFAULT_IMPORTS}; use bumpalo::{collections::Vec, Bump}; use roc_wasm_module::sections::{Import, ImportDesc}; use roc_wasm_module::{ opcodes::OpCode, sections::ElementSegment, Export, ExportType, SerialBuffer, Serialize, Signature, Value, ValueType, WasmModule, }; #[test] fn test_loop() { test_loop_help(10, 55); } fn test_loop_help(end: i32, expected: i32) { let arena = Bump::new(); let mut module = WasmModule::new(&arena); let buf = &mut module.code.bytes; // Loop from 0 to end, adding the loop variable to a total let var_i = 0; let var_total = 1; // (local i32 i32) buf.push(1); // one group of the given type buf.push(2); // two locals in the group buf.push(ValueType::I32 as u8); // loop buf.push(OpCode::LOOP as u8); buf.push(ValueType::VOID as u8); // local.get $i buf.push(OpCode::GETLOCAL as u8); buf.encode_u32(var_i); // i32.const 1 buf.push(OpCode::I32CONST as u8); buf.encode_i32(1); // i32.add buf.push(OpCode::I32ADD as u8); // local.tee $i buf.push(OpCode::TEELOCAL as u8); buf.encode_u32(var_i); // local.get $total buf.push(OpCode::GETLOCAL as u8); buf.encode_u32(var_total); // i32.add buf.push(OpCode::I32ADD as u8); // local.set $total buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(var_total); // local.get $i buf.push(OpCode::GETLOCAL as u8); buf.encode_u32(var_i); // i32.const $end buf.push(OpCode::I32CONST as u8); buf.encode_i32(end); // i32.lt_s buf.push(OpCode::I32LTS as u8); // br_if 0 buf.push(OpCode::BRIF as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // local.get $total buf.push(OpCode::GETLOCAL as u8); buf.encode_u32(var_total); // end function buf.push(OpCode::END as u8); let mut state = default_state(&arena); state .call_stack .push_frame( 0, 0, &[], &mut state.value_stack, &module.code.bytes, &mut state.program_counter, ) .unwrap(); while let Ok(Action::Continue) = state.execute_next_instruction(&module) {} assert_eq!(state.value_stack.pop_i32(), Ok(expected)); } #[test] fn test_if_else() { test_if_else_help(0, 222); test_if_else_help(1, 111); test_if_else_help(-123, 111); } fn test_if_else_help(condition: i32, expected: i32) { let arena = Bump::new(); let mut module = WasmModule::new(&arena); let buf = &mut module.code.bytes; buf.push(1); // one group of the given type buf.push(1); // one local in the group buf.push(ValueType::I32 as u8); // i32.const buf.push(OpCode::I32CONST as u8); buf.encode_i32(condition); // if buf.push(OpCode::IF as u8); buf.push(ValueType::VOID as u8); // i32.const 111 buf.push(OpCode::I32CONST as u8); buf.encode_i32(111); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // else buf.push(OpCode::ELSE as u8); // i32.const 222 buf.push(OpCode::I32CONST as u8); buf.encode_i32(222); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // local.get 0 buf.push(OpCode::GETLOCAL as u8); buf.encode_u32(0); // end function buf.push(OpCode::END as u8); let mut state = default_state(&arena); state .call_stack .push_frame( 0, 0, &[], &mut state.value_stack, &module.code.bytes, &mut state.program_counter, ) .unwrap(); while let Ok(Action::Continue) = state.execute_next_instruction(&module) {} assert_eq!(state.value_stack.pop_i32(), Ok(expected)); } #[test] fn test_br() { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); let buf = &mut module.code.bytes; // (local i32) buf.encode_u32(1); buf.encode_u32(1); buf.push(ValueType::I32 as u8); // i32.const 111 buf.push(OpCode::I32CONST as u8); buf.encode_i32(111); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // block ;; label = @1 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // block ;; label = @2 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // block ;; label = @3 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // br 2 (;@1;) buf.push(OpCode::BR as u8); buf.encode_u32(2); // i32.const 444 buf.push(OpCode::I32CONST as u8); buf.encode_i32(444); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // i32.const 333 buf.push(OpCode::I32CONST as u8); buf.encode_i32(333); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // i32.const 222 buf.push(OpCode::I32CONST as u8); buf.encode_i32(222); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // local.get 0) buf.push(OpCode::GETLOCAL as u8); buf.encode_u32(0); buf.push(OpCode::END as u8); state .call_stack .push_frame( 0, 0, &[], &mut state.value_stack, &module.code.bytes, &mut state.program_counter, ) .unwrap(); while let Ok(Action::Continue) = state.execute_next_instruction(&module) {} assert_eq!(state.value_stack.pop(), Value::I32(111)) } #[test] fn test_br_if() { test_br_if_help(0, 222); test_br_if_help(1, 111); } fn test_br_if_help(condition: i32, expected: i32) { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); let buf = &mut module.code.bytes; // (local i32) buf.encode_u32(1); buf.encode_u32(1); buf.push(ValueType::I32 as u8); // i32.const 111 buf.push(OpCode::I32CONST as u8); buf.encode_i32(111); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // block ;; label = @1 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // block ;; label = @2 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // block ;; label = @3 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // i32.const buf.push(OpCode::I32CONST as u8); buf.encode_i32(condition); // br_if 2 (;@1;) buf.push(OpCode::BRIF as u8); buf.encode_u32(2); // i32.const 444 buf.push(OpCode::I32CONST as u8); buf.encode_i32(444); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // i32.const 333 buf.push(OpCode::I32CONST as u8); buf.encode_i32(333); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // i32.const 222 buf.push(OpCode::I32CONST as u8); buf.encode_i32(222); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // local.get 0) buf.push(OpCode::GETLOCAL as u8); buf.encode_u32(0); buf.push(OpCode::END as u8); state .call_stack .push_frame( 0, 0, &[], &mut state.value_stack, &module.code.bytes, &mut state.program_counter, ) .unwrap(); while let Ok(Action::Continue) = state.execute_next_instruction(&module) {} assert_eq!(state.value_stack.pop(), Value::I32(expected)) } #[test] fn test_br_table() { test_br_table_help(0, 333); test_br_table_help(1, 222); test_br_table_help(2, 111); } fn test_br_table_help(condition: i32, expected: i32) { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); let buf = &mut module.code.bytes; // (local i32) buf.encode_u32(1); buf.encode_u32(1); buf.push(ValueType::I32 as u8); // i32.const 111 buf.push(OpCode::I32CONST as u8); buf.encode_i32(111); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // block ;; label = @1 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // block ;; label = @2 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // block ;; label = @3 buf.push(OpCode::BLOCK as u8); buf.push(ValueType::VOID); // i32.const buf.push(OpCode::I32CONST as u8); buf.encode_i32(condition); // br_table 0 1 2 (;@1;) buf.push(OpCode::BRTABLE as u8); buf.encode_u32(2); // number of non-fallback branches buf.encode_u32(0); buf.encode_u32(1); buf.encode_u32(2); // end buf.push(OpCode::END as u8); // i32.const 333 buf.push(OpCode::I32CONST as u8); buf.encode_i32(333); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // br 1 buf.push(OpCode::BR as u8); buf.encode_u32(1); // end buf.push(OpCode::END as u8); // i32.const 222 buf.push(OpCode::I32CONST as u8); buf.encode_i32(222); // local.set 0 buf.push(OpCode::SETLOCAL as u8); buf.encode_u32(0); // br 0 buf.push(OpCode::BR as u8); buf.encode_u32(0); // end buf.push(OpCode::END as u8); // local.get 0) buf.push(OpCode::GETLOCAL as u8); buf.encode_u32(0); buf.push(OpCode::END as u8); println!("{:02x?}", buf); state .call_stack .push_frame( 0, 0, &[], &mut state.value_stack, &module.code.bytes, &mut state.program_counter, ) .unwrap(); while let Ok(Action::Continue) = state.execute_next_instruction(&module) {} assert_eq!(state.value_stack.pop(), Value::I32(expected)) } struct TestDispatcher { internal_state: i32, } impl ImportDispatcher for TestDispatcher { fn dispatch( &mut self, module_name: &str, function_name: &str, arguments: &[Value], _memory: &mut [u8], ) -> Option { assert_eq!(module_name, "env"); assert_eq!(function_name, "increment_state"); assert_eq!(arguments.len(), 1); let val = arguments[0].expect_i32().unwrap(); self.internal_state += val; dbg!(val, self.internal_state); Some(Value::I32(self.internal_state)) } } #[test] fn test_call_import() { let arena = Bump::new(); let mut module = WasmModule::new(&arena); let start_fn_name = "test"; // User-provided non-Wasm code, with state let import_dispatcher = TestDispatcher { internal_state: 100, }; // Function 0 is the import module.import.imports.push(Import { module: "env", name: "increment_state", description: ImportDesc::Func { signature_index: 0 }, }); module.types.insert(Signature { param_types: bumpalo::vec![in &arena; ValueType::I32], ret_type: Some(ValueType::I32), }); // Function 1, which calls the import module.code.function_count = 1; let func0_offset = module.code.bytes.len() as u32; module.code.function_offsets.push(func0_offset); module.add_function_signature(Signature { param_types: Vec::new_in(&arena), ret_type: Some(ValueType::I32), }); module.export.append(Export { name: start_fn_name, ty: ExportType::Func, index: 1, }); [ 0, // no locals OpCode::I32CONST as u8, 11, // argument to increment_state OpCode::CALL as u8, 0, // function 0 OpCode::I32CONST as u8, 12, // argument to increment_state OpCode::CALL as u8, 0, // function 0 OpCode::I32ADD as u8, OpCode::END as u8, ] .serialize(&mut module.code.bytes); if false { let mut buf = Vec::new_in(&arena); module.serialize(&mut buf); let filename = "/tmp/roc/call-return.wasm"; std::fs::write(filename, buf).unwrap(); println!("Wrote to {}", filename); } let mut inst = Instance::for_module(&arena, &module, import_dispatcher, true).unwrap(); let return_val = inst .call_export(&module, start_fn_name, []) .unwrap() .unwrap(); assert_eq!(return_val, Value::I32(234)); } #[test] fn test_call_return_no_args() { let arena = Bump::new(); let mut module = WasmModule::new(&arena); let start_fn_name = "test"; module.code.function_count = 2; // Function 0 let func0_offset = module.code.bytes.len() as u32; module.code.function_offsets.push(func0_offset); module.add_function_signature(Signature { param_types: Vec::new_in(&arena), ret_type: Some(ValueType::I32), }); module.export.append(Export { name: start_fn_name, ty: ExportType::Func, index: 0, }); [ 1, // 1 group of locals 1, // 1 local ValueType::I32 as u8, OpCode::BLOCK as u8, /* */ // call from inside a block. callee's implicit return should still work correctly. ValueType::VOID as u8, OpCode::CALL as u8, 1, // function 1 OpCode::SETLOCAL as u8, 0, // local 0 OpCode::END as u8, OpCode::GETLOCAL as u8, 0, // local 0 OpCode::END as u8, ] .serialize(&mut module.code.bytes); // Function 1 let func1_offset = module.code.bytes.len() as u32; module.code.function_offsets.push(func1_offset); module.add_function_signature(Signature { param_types: Vec::new_in(&arena), ret_type: Some(ValueType::I32), }); [ 0, // no locals OpCode::I32CONST as u8, 42, // constant value (<64 so that LEB-128 is just one byte) OpCode::END as u8, ] .serialize(&mut module.code.bytes); if false { let mut buf = Vec::new_in(&arena); module.serialize(&mut buf); let filename = "/tmp/roc/call-return.wasm"; std::fs::write(filename, buf).unwrap(); println!("Wrote to {}", filename); } let mut inst = Instance::for_module(&arena, &module, DEFAULT_IMPORTS, true).unwrap(); let return_val = inst .call_export(&module, start_fn_name, []) .unwrap() .unwrap(); assert_eq!(return_val, Value::I32(42)); } #[test] fn test_call_return_with_args() { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); // Function 0: calculate 2+2 let func0_offset = module.code.bytes.len() as u32; module.code.function_offsets.push(func0_offset); module.add_function_signature(Signature { param_types: bumpalo::vec![in &arena;], ret_type: Some(ValueType::I32), }); [ 0, // no locals OpCode::I32CONST as u8, 2, OpCode::I32CONST as u8, 2, OpCode::CALL as u8, 1, OpCode::END as u8, ] .serialize(&mut module.code.bytes); let func0_first_instruction = func0_offset + 2; // skip function length and locals length // Function 1: add two numbers let func1_offset = module.code.bytes.len() as u32; module.code.function_offsets.push(func1_offset); module.add_function_signature(Signature { param_types: bumpalo::vec![in &arena; ValueType::I32, ValueType::I32], ret_type: Some(ValueType::I32), }); [ 0, // no locals OpCode::GETLOCAL as u8, 0, OpCode::GETLOCAL as u8, 1, OpCode::I32ADD as u8, OpCode::END as u8, ] .serialize(&mut module.code.bytes); state.program_counter = func0_first_instruction as usize; while let Ok(Action::Continue) = state.execute_next_instruction(&module) {} assert_eq!(state.value_stack.peek(), Value::I32(4)); } #[test] fn test_call_indirect_ok() { let result = test_call_indirect_help(0, 0); assert_eq!(result, Value::I32(111)); } #[test] #[should_panic(expected = "Expected signature")] fn test_call_indirect_wrong_signature() { test_call_indirect_help(0, 1); } #[test] #[should_panic(expected = "element index")] fn test_call_indirect_index_out_of_bounds() { test_call_indirect_help(0, 2); } #[test] #[should_panic(expected = "Table index")] fn test_call_indirect_unsupported_table() { test_call_indirect_help(1, 0); } fn test_call_indirect_help(table_index: u32, elem_index: u32) -> Value { let arena = Bump::new(); let mut module = WasmModule::new(&arena); let is_debug_mode = true; let start_fn_name = "test"; // function 0: caller let signature0 = || Signature { param_types: bumpalo::vec![in &arena], ret_type: Some(ValueType::I32), }; create_exported_function_no_locals(&mut module, start_fn_name, signature0(), |buf| { buf.append_u8(OpCode::I32CONST as u8); buf.encode_u32(elem_index); buf.append_u8(OpCode::CALLINDIRECT as u8); buf.encode_u32(0); // signature index buf.encode_u32(table_index); buf.append_u8(OpCode::END as u8); }); // function 1: callee, right signature create_exported_function_no_locals(&mut module, "callee1", signature0(), |buf| { buf.append_u8(OpCode::I32CONST as u8); buf.encode_i32(111); buf.append_u8(OpCode::END as u8); }); // function 2: callee, wrong signature let signature1 = Signature { param_types: bumpalo::vec![in &arena], ret_type: Some(ValueType::F32), }; create_exported_function_no_locals(&mut module, "callee2", signature1, |buf| { buf.append_u8(OpCode::F32CONST as u8); buf.encode_f32(2.22); buf.append_u8(OpCode::END as u8); }); // Put functions 1 and 2 in the function table module.element.segments.push(ElementSegment::new(&arena)); assert_eq!(module.element.get_or_insert_fn(1), 0); assert_eq!(module.element.get_or_insert_fn(2), 1); if false { let mut outfile_buf = Vec::new_in(&arena); module.serialize(&mut outfile_buf); std::fs::write( format!("/tmp/roc/call_indirect_{}_{}.wasm", table_index, elem_index), outfile_buf, ) .unwrap(); } let mut inst = Instance::for_module(&arena, &module, DEFAULT_IMPORTS, is_debug_mode).unwrap(); inst.call_export(&module, start_fn_name, []) .unwrap() .unwrap() } // #[test] // fn test_drop() {} #[test] fn test_select() { test_select_help(Value::F32(1.11), Value::F32(2.22), -100, Value::F32(1.11)); test_select_help(Value::F64(1.11), Value::F64(2.22), 0, Value::F64(2.22)); } fn test_select_help(first: Value, second: Value, condition: i32, expected: Value) { let arena = Bump::new(); let mut module = WasmModule::new(&arena); let buf = &mut module.code.bytes; buf.push(0); // no locals const_value(buf, first); const_value(buf, second); const_value(buf, Value::I32(condition)); buf.push(OpCode::SELECT as u8); buf.push(OpCode::END as u8); let mut state = default_state(&arena); state .call_stack .push_frame( 0, 0, &[], &mut state.value_stack, &module.code.bytes, &mut state.program_counter, ) .unwrap(); while let Ok(Action::Continue) = state.execute_next_instruction(&module) {} assert_eq!(state.value_stack.pop(), expected); } #[test] fn test_set_get_local() { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); let mut vs = ValueStack::new(&arena); let mut buffer = vec![]; let mut cursor = 0; [ (1u32, ValueType::F32), (1u32, ValueType::F64), (1u32, ValueType::I32), (1u32, ValueType::I64), ] .serialize(&mut buffer); state .call_stack .push_frame(0x1234, 0, &[], &mut vs, &buffer, &mut cursor) .unwrap(); module.code.bytes.push(OpCode::I32CONST as u8); module.code.bytes.encode_i32(12345); module.code.bytes.push(OpCode::SETLOCAL as u8); module.code.bytes.encode_u32(2); module.code.bytes.push(OpCode::GETLOCAL as u8); module.code.bytes.encode_u32(2); state.execute_next_instruction(&module).unwrap(); state.execute_next_instruction(&module).unwrap(); state.execute_next_instruction(&module).unwrap(); assert_eq!(state.value_stack.depth(), 1); assert_eq!(state.value_stack.pop(), Value::I32(12345)); } #[test] fn test_tee_get_local() { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); let mut vs = ValueStack::new(&arena); let mut buffer = vec![]; let mut cursor = 0; [ (1u32, ValueType::F32), (1u32, ValueType::F64), (1u32, ValueType::I32), (1u32, ValueType::I64), ] .serialize(&mut buffer); state .call_stack .push_frame(0x1234, 0, &[], &mut vs, &buffer, &mut cursor) .unwrap(); module.code.bytes.push(OpCode::I32CONST as u8); module.code.bytes.encode_i32(12345); module.code.bytes.push(OpCode::TEELOCAL as u8); module.code.bytes.encode_u32(2); module.code.bytes.push(OpCode::GETLOCAL as u8); module.code.bytes.encode_u32(2); state.execute_next_instruction(&module).unwrap(); state.execute_next_instruction(&module).unwrap(); state.execute_next_instruction(&module).unwrap(); assert_eq!(state.value_stack.depth(), 2); assert_eq!(state.value_stack.pop(), Value::I32(12345)); assert_eq!(state.value_stack.pop(), Value::I32(12345)); } #[test] fn test_global() { let arena = Bump::new(); let mut state = default_state(&arena); state .globals .extend_from_slice(&[Value::F64(1.11), Value::I32(222), Value::F64(3.33)]); let mut module = WasmModule::new(&arena); module.code.bytes.push(OpCode::GETGLOBAL as u8); module.code.bytes.encode_u32(1); module.code.bytes.push(OpCode::I32CONST as u8); module.code.bytes.encode_i32(555); module.code.bytes.push(OpCode::SETGLOBAL as u8); module.code.bytes.encode_u32(1); module.code.bytes.push(OpCode::GETGLOBAL as u8); module.code.bytes.encode_u32(1); state.execute_next_instruction(&module).unwrap(); state.execute_next_instruction(&module).unwrap(); state.execute_next_instruction(&module).unwrap(); state.execute_next_instruction(&module).unwrap(); assert_eq!(state.value_stack.depth(), 2); assert_eq!(state.value_stack.pop(), Value::I32(555)); assert_eq!(state.value_stack.pop(), Value::I32(222)); } #[test] fn test_i32const() { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); module.code.bytes.push(OpCode::I32CONST as u8); module.code.bytes.encode_i32(12345); state.execute_next_instruction(&module).unwrap(); assert_eq!(state.value_stack.pop(), Value::I32(12345)) } #[test] fn test_i64const() { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); module.code.bytes.push(OpCode::I64CONST as u8); module.code.bytes.encode_i64(1234567890); state.execute_next_instruction(&module).unwrap(); assert_eq!(state.value_stack.pop(), Value::I64(1234567890)) } #[test] fn test_f32const() { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); module.code.bytes.push(OpCode::F32CONST as u8); module.code.bytes.encode_f32(123.45); state.execute_next_instruction(&module).unwrap(); assert_eq!(state.value_stack.pop(), Value::F32(123.45)) } #[test] fn test_f64const() { let arena = Bump::new(); let mut state = default_state(&arena); let mut module = WasmModule::new(&arena); module.code.bytes.push(OpCode::F64CONST as u8); module.code.bytes.encode_f64(12345.67890); state.execute_next_instruction(&module).unwrap(); assert_eq!(state.value_stack.pop(), Value::F64(12345.67890)) }