diff --git a/vlib/v/gen/wasm/tests/advanced_flow.vv b/vlib/v/gen/wasm/tests/advanced_flow.vv new file mode 100644 index 00000000000000..83de50f9028af3 --- /dev/null +++ b/vlib/v/gen/wasm/tests/advanced_flow.vv @@ -0,0 +1,152 @@ +// Test advanced control flow and function pointers simulation +// This exercises complex conditionals and branching + +fn operation_add(a i64, b i64) i64 { + return a + b +} + +fn operation_sub(a i64, b i64) i64 { + return a - b +} + +fn operation_mul(a i64, b i64) i64 { + return a * b +} + +fn operation_div(a i64, b i64) i64 { + if b == 0 { + return 0 + } + return a / b +} + +// Simulate function dispatch using if-else chain +fn calculate(op int, a i64, b i64) i64 { + if op == 0 { + return operation_add(a, b) + } else if op == 1 { + return operation_sub(a, b) + } else if op == 2 { + return operation_mul(a, b) + } else if op == 3 { + return operation_div(a, b) + } else { + return 0 + } +} + +// Complex nested control flow +fn categorize_number(n i64) int { + if n < 0 { + if n < -100 { + return -3 // very negative + } else if n < -10 { + return -2 // negative + } else { + return -1 // slightly negative + } + } else if n == 0 { + return 0 // zero + } else { + if n > 100 { + return 3 // very positive + } else if n > 10 { + return 2 // positive + } else { + return 1 // slightly positive + } + } +} + +// Test various comparison operations +fn compare_values(a i64, b i64) int { + if a == b { + return 0 // equal + } else if a < b { + return -1 // less + } else if a > b { + return 1 // greater + } else { + return 99 // unknown + } +} + +// Nested loops with breaks and continues +fn sum_with_skip(limit int, skip int) i64 { + mut sum := i64(0) + for i := 0; i < limit; i++ { + if i == skip { + continue + } + if i > limit / 2 && i % 2 == 0 { + break + } + sum += i + } + return sum +} + +// Multiple return paths +fn classify_range(val i64) int { + if val < 0 { + return -1 + } + if val == 0 { + return 0 + } + if val < 10 { + return 1 + } + if val < 100 { + return 2 + } + return 3 +} + +fn abs_diff(a i64, b i64) i64 { + if a > b { + return a - b + } + return b - a +} + +fn main() { + println('=== Advanced Control Flow Tests ===') + + println('--- Calculator Dispatch') + println(calculate(0, 10, 5)) + println(calculate(1, 10, 5)) + println(calculate(2, 10, 5)) + println(calculate(3, 10, 5)) + println(calculate(99, 10, 5)) + + println('--- Number Categorization') + println(categorize_number(-150)) + println(categorize_number(-50)) + println(categorize_number(-5)) + println(categorize_number(0)) + println(categorize_number(5)) + println(categorize_number(50)) + println(categorize_number(150)) + + println('--- Value Comparison') + println(compare_values(10, 10)) + println(compare_values(5, 10)) + println(compare_values(15, 10)) + + println('--- Loop with Skip') + println(sum_with_skip(10, 3)) + println(sum_with_skip(20, 5)) + + println('--- Range Classification') + println(classify_range(-5)) + println(classify_range(0)) + println(classify_range(5)) + println(classify_range(50)) + println(classify_range(500)) + + println('--- Absolute Difference') + println(abs_diff(10, 20)) + println(abs_diff(20, 10)) + println(abs_diff(5, 5)) +} diff --git a/vlib/v/gen/wasm/tests/advanced_flow.vv.out b/vlib/v/gen/wasm/tests/advanced_flow.vv.out new file mode 100644 index 00000000000000..c6f1ac9ed68d6d --- /dev/null +++ b/vlib/v/gen/wasm/tests/advanced_flow.vv.out @@ -0,0 +1,32 @@ +=== Advanced Control Flow Tests === +--- Calculator Dispatch +15 +5 +50 +2 +0 +--- Number Categorization +-3 +-2 +-1 +0 +1 +2 +3 +--- Value Comparison +0 +-1 +1 +--- Loop with Skip +6 +15 +--- Range Classification +-1 +0 +1 +2 +3 +--- Absolute Difference +10 +10 +0 diff --git a/vlib/v/gen/wasm/tests/advanced_flow.wasm b/vlib/v/gen/wasm/tests/advanced_flow.wasm new file mode 100644 index 00000000000000..cabc72bb37689c Binary files /dev/null and b/vlib/v/gen/wasm/tests/advanced_flow.wasm differ diff --git a/vlib/v/gen/wasm/tests/memory_ops.vv b/vlib/v/gen/wasm/tests/memory_ops.vv new file mode 100644 index 00000000000000..9b152dd060b5c1 --- /dev/null +++ b/vlib/v/gen/wasm/tests/memory_ops.vv @@ -0,0 +1,136 @@ +// Test memory operations and bitwise algorithms +// This tests the wasm memory model and complex data manipulation + +fn abs_i64(n i64) i64 { + if n < 0 { + return -n + } + return n +} + +fn max_i64(a i64, b i64) i64 { + if a > b { + return a + } + return b +} + +fn min_i64(a i64, b i64) i64 { + if a < b { + return a + } + return b +} + +fn clamp(val i64, min_val i64, max_val i64) i64 { + if val < min_val { + return min_val + } + if val > max_val { + return max_val + } + return val +} + +fn count_bits(n i64) int { + mut count := 0 + mut num := n + for num != 0 { + count += int(num & 1) + num >>= 1 + } + return count +} + +fn reverse_bits_32(n u32) u32 { + mut result := u32(0) + mut val := n + for i := 0; i < 32; i++ { + result <<= 1 + result |= val & 1 + val >>= 1 + } + return result +} + +fn is_power_of_two(n i64) bool { + if n <= 0 { + return false + } + return (n & (n - 1)) == 0 +} + +fn next_power_of_two(n i64) i64 { + if n <= 1 { + return 1 + } + mut power := i64(1) + for power < n { + power <<= 1 + } + return power +} + +fn hamming_distance(a i64, b i64) int { + xor_val := a ^ b + return count_bits(xor_val) +} + +fn rotate_left_32(n u32, shift int) u32 { + s := u32(shift) & 31 + return (n << s) | (n >> (32 - s)) +} + +fn rotate_right_32(n u32, shift int) u32 { + s := u32(shift) & 31 + return (n >> s) | (n << (32 - s)) +} + +fn swap_bytes_32(n u32) u32 { + return ((n & 0xFF) << 24) | ((n & 0xFF00) << 8) | ((n & 0xFF0000) >> 8) | ((n & 0xFF000000) >> 24) +} + +fn sign_extend_8(n u8) i64 { + if n & 0x80 != 0 { + return i64(n) | i64(0xFFFFFFFFFFFFFF00) + } + return i64(n) +} + +fn main() { + println('=== Memory and Bit Operation Tests ===') + + println('--- Basic Math Operations') + println(abs_i64(-42)) + println(abs_i64(42)) + println(max_i64(10, 20)) + println(min_i64(10, 20)) + println(clamp(15, 10, 20)) + println(clamp(5, 10, 20)) + println(clamp(25, 10, 20)) + + println('--- Bit Counting Operations') + println(count_bits(15)) + println(count_bits(255)) + println(count_bits(0)) + println(count_bits(1023)) + + println('--- Power of Two Operations') + println(is_power_of_two(16)) + println(is_power_of_two(15)) + println(next_power_of_two(10)) + println(next_power_of_two(16)) + + println('--- Hamming Distance') + println(hamming_distance(7, 15)) + println(hamming_distance(0, 255)) + + println('--- Bit Rotation') + println(rotate_left_32(0x12345678, 8)) + println(rotate_right_32(0x12345678, 8)) + + println('--- Byte Operations') + println(swap_bytes_32(0x12345678)) + println(sign_extend_8(0x7F)) + println(sign_extend_8(0xFF)) +} diff --git a/vlib/v/gen/wasm/tests/memory_ops.vv.out b/vlib/v/gen/wasm/tests/memory_ops.vv.out new file mode 100644 index 00000000000000..3087c8deb1c644 --- /dev/null +++ b/vlib/v/gen/wasm/tests/memory_ops.vv.out @@ -0,0 +1,29 @@ +=== Memory and Bit Operation Tests === +--- Basic Math Operations +42 +42 +20 +10 +15 +10 +20 +--- Bit Counting Operations +4 +8 +0 +10 +--- Power of Two Operations +true +false +16 +16 +--- Hamming Distance +4 +8 +--- Bit Rotation +305419896 +2018915346 +--- Byte Operations +2018915346 +127 +-1 diff --git a/vlib/v/gen/wasm/tests/memory_ops.wasm b/vlib/v/gen/wasm/tests/memory_ops.wasm new file mode 100644 index 00000000000000..54282abf05d045 Binary files /dev/null and b/vlib/v/gen/wasm/tests/memory_ops.wasm differ diff --git a/vlib/v/gen/wasm/tests/realworld.vv b/vlib/v/gen/wasm/tests/realworld.vv new file mode 100644 index 00000000000000..5c97601d6973ab --- /dev/null +++ b/vlib/v/gen/wasm/tests/realworld.vv @@ -0,0 +1,126 @@ +// Test real-world scenarios using algorithms and computations +// This demonstrates complex calculations and logic + +fn factorial_recursive(n i64) i64 { + if n <= 1 { + return 1 + } + return n * factorial_recursive(n - 1) +} + +fn fibonacci(n int) i64 { + if n <= 1 { + return i64(n) + } + mut a := i64(0) + mut b := i64(1) + for i := 2; i <= n; i++ { + tmp := a + b + a = b + b = tmp + } + return b +} + +fn is_prime(n i64) bool { + if n < 2 { + return false + } + if n == 2 { + return true + } + if n % 2 == 0 { + return false + } + mut i := i64(3) + for i * i <= n { + if n % i == 0 { + return false + } + i += 2 + } + return true +} + +fn prime_count(limit int) int { + mut count := 0 + for i := 2; i < limit; i++ { + if is_prime(i64(i)) { + count++ + } + } + return count +} + +fn gcd(a_ i64, b_ i64) i64 { + mut a := a_ + mut b := b_ + if a < 0 { + a = -a + } + if b < 0 { + b = -b + } + for b != 0 { + a %= b + if a == 0 { + return b + } + b %= a + } + return a +} + +fn lcm(a i64, b i64) i64 { + if a == 0 { + return a + } + res := a * (b / gcd(b, a)) + if res < 0 { + return -res + } + return res +} + +fn power(base i64, exp i64) i64 { + mut result := i64(1) + mut e := exp + mut b := base + for e > 0 { + if e & 1 > 0 { + result *= b + } + b *= b + e >>= 1 + } + return result +} + +fn main() { + println('=== Real World WASM Tests ===') + + println('--- Factorial Tests') + println(factorial_recursive(5)) + println(factorial_recursive(10)) + println(factorial_recursive(0)) + + println('--- Fibonacci Tests') + println(fibonacci(10)) + println(fibonacci(20)) + println(fibonacci(0)) + + println('--- Prime Number Tests') + println(is_prime(17)) + println(is_prime(18)) + println(prime_count(100)) + + println('--- GCD and LCM Tests') + println(gcd(48, 18)) + println(lcm(12, 18)) + println(gcd(17, 19)) + + println('--- Power Tests') + println(power(2, 10)) + println(power(3, 5)) + println(power(5, 0)) +} diff --git a/vlib/v/gen/wasm/tests/realworld.vv.out b/vlib/v/gen/wasm/tests/realworld.vv.out new file mode 100644 index 00000000000000..4cd701767bc76b --- /dev/null +++ b/vlib/v/gen/wasm/tests/realworld.vv.out @@ -0,0 +1,21 @@ +=== Real World WASM Tests === +--- Factorial Tests +120 +3628800 +1 +--- Fibonacci Tests +55 +6765 +0 +--- Prime Number Tests +true +false +25 +--- GCD and LCM Tests +6 +36 +1 +--- Power Tests +1024 +243 +1 diff --git a/vlib/v/gen/wasm/tests/realworld.wasm b/vlib/v/gen/wasm/tests/realworld.wasm new file mode 100644 index 00000000000000..d7e975b40a5272 Binary files /dev/null and b/vlib/v/gen/wasm/tests/realworld.wasm differ diff --git a/vlib/wasm/README.md b/vlib/wasm/README.md index 66e9af42a4cca9..575627ddd4a4ee 100644 --- a/vlib/wasm/README.md +++ b/vlib/wasm/README.md @@ -37,3 +37,94 @@ fn main() { This module does not perform verification of the WebAssembly output. Use a tool like `wasm-validate` to validate, and `wasm-dis` to show a decompiled form. + +## New Features (Phase 1 - 2026) + +### Tables and Indirect Calls + +Tables allow dynamic function dispatch through indirect calls: + +```v +mut m := wasm.Module{} + +// Create a function to call indirectly +mut target := m.new_function('target', [], [.i32_t]) +{ + target.i32_const(42) +} +m.commit(target, false) + +// Create a table +table_idx := m.new_table('func_table', false, 1, 10) + +// Initialize table with the target function +mut offset := wasm.ConstExpression{} +offset.i32_const(0) +m.new_element_segment(none, u32(table_idx), offset, ['target']) + +// Call indirectly through the table +mut caller := m.new_function('caller', [], [.i32_t]) +{ + caller.i32_const(0) // table index + caller.call_indirect(0, u32(table_idx)) +} +m.commit(caller, true) +``` + +### WASI Support + +Easily import WASI functions for system interactions: + +```v +mut m := wasm.Module{} + +// Add WASI imports +m.add_wasi_import('fd_write') +m.add_wasi_import('proc_exit') + +mut func := m.new_function('hello', [], []) +{ + // Use fd_write to print to stdout + func.i32_const(1) // fd (stdout) + func.i32_const(0) // iovs pointer + func.i32_const(1) // iovs_len + func.i32_const(0) // nwritten pointer + func.call_import('wasi_snapshot_preview1', 'fd_write') +} +m.commit(func, true) +``` + +Supported WASI functions: `fd_write`, `proc_exit`, `args_get`, `args_sizes_get`, `environ_get`, `environ_sizes_get`, `clock_time_get`, `random_get`. + +### Enhanced Control Flow + +New branch instructions for more flexible control flow: + +```v +mut func := m.new_function('branch_demo', [.i32_t], [.i32_t]) +{ + // Direct branch + func.br(0) + + // Conditional branch + func.local_get(0) + func.br_if(0) + + // Branch table (switch-case) + func.local_get(0) + func.br_table([u32(0), u32(1)], u32(2)) +} +``` + +### i32 Comparison Shortcuts + +Convenient shortcuts for common i32 comparisons: + +```v +func.local_get(0) +func.local_get(1) +func.i32_eq() // i32.eq +func.i32_lt_s() // i32.lt_s (signed) +func.i32_gt_u() // i32.gt_u (unsigned) +// Also available: i32_ne, i32_le_s, i32_le_u, i32_ge_s, i32_ge_u +``` diff --git a/vlib/wasm/encoding.v b/vlib/wasm/encoding.v index af8d909156ac14..53456eafb5c110 100644 --- a/vlib/wasm/encoding.v +++ b/vlib/wasm/encoding.v @@ -182,6 +182,26 @@ pub fn (mut mod Module) compile() []u8 { } mod.end_section(tpatch) } + // https://webassembly.github.io/spec/core/binary/modules.html#table-section + // + if mod.tables.len > 0 { + tpatch := mod.start_section(.table_section) + { + mod.u32(u32(mod.tables.len)) + for tbl in mod.tables { + mod.buf << u8(tbl.elem_type) // element type (funcref or externref) + if max := tbl.max { + mod.buf << 0x01 // limit, max present + mod.u32(tbl.min) + mod.u32(max) + } else { + mod.buf << 0x00 // limit, max not present + mod.u32(tbl.min) + } + } + } + mod.end_section(tpatch) + } // https://webassembly.github.io/spec/core/binary/modules.html#binary-memsec // if memory := mod.memory { @@ -257,6 +277,15 @@ pub fn (mut mod Module) compile() []u8 { mod.buf << 0x03 // global mod.u32(u32(idx + mod.global_imports.len)) } + for idx, tbl in mod.tables { + if !tbl.export { + continue + } + lsz++ + mod.name(tbl.name) + mod.buf << 0x01 // table + mod.u32(u32(idx)) + } mod.patch_u32(lpatch, u32(lsz)) } mod.end_section(tpatch) @@ -271,6 +300,41 @@ pub fn (mut mod Module) compile() []u8 { } mod.end_section(tpatch) } + // https://webassembly.github.io/spec/core/binary/modules.html#element-section + // + if mod.elements.len > 0 { + tpatch := mod.start_section(.element_section) + { + mod.u32(u32(mod.elements.len)) + for elem in mod.elements { + // Element kind: active with table index (0x00) + mod.buf << 0x00 + // Table index + mod.u32(elem.table_idx) + // Offset expression + { + mut ptr := 0 + for patch in elem.offset.call_patches { + idx := mod.get_function_idx(patch) + mod.buf << elem.offset.code[ptr..patch.pos] + mod.u32(u32(idx)) + ptr = patch.pos + } + mod.buf << elem.offset.code[ptr..] + } + mod.buf << 0x0B // END expression opcode + // Element count and function indices + mod.u32(u32(elem.func_names.len)) + for fname in elem.func_names { + ftt := mod.functions[fname] or { + panic('element function ${fname} does not exist') + } + mod.u32(u32(ftt.idx + mod.fn_imports.len)) + } + } + } + mod.end_section(tpatch) + } // https://webassembly.github.io/spec/core/binary/modules.html#data-count-section // if mod.segments.len > 0 { diff --git a/vlib/wasm/instructions.v b/vlib/wasm/instructions.v index 91edb2438e31ea..449a2fea451e0b 100644 --- a/vlib/wasm/instructions.v +++ b/vlib/wasm/instructions.v @@ -1007,6 +1007,99 @@ pub fn (mut func Function) call_import(mod string, name string) { }) } +// call_indirect calls a function indirectly through a table. +// WebAssembly instruction: `call_indirect`. +pub fn (mut func Function) call_indirect(type_idx u32, table_idx u32) { + func.code << 0x11 // call_indirect + func.u32(type_idx) + func.u32(table_idx) +} + +// br branches to a loop or block at the given depth. +// WebAssembly instruction: `br`. +pub fn (mut func Function) br(depth u32) { + func.code << 0x0C // br + func.u32(depth) +} + +// br_if conditionally branches to a loop or block at the given depth. +// WebAssembly instruction: `br_if`. +pub fn (mut func Function) br_if(depth u32) { + func.code << 0x0D // br_if + func.u32(depth) +} + +// br_table performs an indirect branch through a label table. +// WebAssembly instruction: `br_table`. +pub fn (mut func Function) br_table(labels []u32, default_label u32) { + func.code << 0x0E // br_table + func.u32(u32(labels.len)) + for label in labels { + func.u32(label) + } + func.u32(default_label) +} + +// i32_eq checks if two i32 values are equal. +// WebAssembly instruction: `i32.eq`. +pub fn (mut func Function) i32_eq() { + func.code << 0x46 // i32.eq +} + +// i32_ne checks if two i32 values are not equal. +// WebAssembly instruction: `i32.ne`. +pub fn (mut func Function) i32_ne() { + func.code << 0x47 // i32.ne +} + +// i32_lt_s checks if first i32 value is less than second (signed). +// WebAssembly instruction: `i32.lt_s`. +pub fn (mut func Function) i32_lt_s() { + func.code << 0x48 // i32.lt_s +} + +// i32_lt_u checks if first i32 value is less than second (unsigned). +// WebAssembly instruction: `i32.lt_u`. +pub fn (mut func Function) i32_lt_u() { + func.code << 0x49 // i32.lt_u +} + +// i32_gt_s checks if first i32 value is greater than second (signed). +// WebAssembly instruction: `i32.gt_s`. +pub fn (mut func Function) i32_gt_s() { + func.code << 0x4A // i32.gt_s +} + +// i32_gt_u checks if first i32 value is greater than second (unsigned). +// WebAssembly instruction: `i32.gt_u`. +pub fn (mut func Function) i32_gt_u() { + func.code << 0x4B // i32.gt_u +} + +// i32_le_s checks if first i32 value is less than or equal to second (signed). +// WebAssembly instruction: `i32.le_s`. +pub fn (mut func Function) i32_le_s() { + func.code << 0x4C // i32.le_s +} + +// i32_le_u checks if first i32 value is less than or equal to second (unsigned). +// WebAssembly instruction: `i32.le_u`. +pub fn (mut func Function) i32_le_u() { + func.code << 0x4D // i32.le_u +} + +// i32_ge_s checks if first i32 value is greater than or equal to second (signed). +// WebAssembly instruction: `i32.ge_s`. +pub fn (mut func Function) i32_ge_s() { + func.code << 0x4E // i32.ge_s +} + +// i32_ge_u checks if first i32 value is greater than or equal to second (unsigned). +// WebAssembly instruction: `i32.ge_u`. +pub fn (mut func Function) i32_ge_u() { + func.code << 0x4F // i32.ge_u +} + // load loads a value with type `typ` from memory. // WebAssembly instruction: `i32|i64|f32|f64.load`. pub fn (mut func Function) load(typ NumType, align int, offset int) { diff --git a/vlib/wasm/module.v b/vlib/wasm/module.v index 4829510a8d3176..1c7f2b8a9e9700 100644 --- a/vlib/wasm/module.v +++ b/vlib/wasm/module.v @@ -69,6 +69,8 @@ mut: fn_imports []FunctionImport global_imports []GlobalImport segments []DataSegment + tables []Table + elements []Element debug bool mod_name ?string } @@ -108,6 +110,21 @@ struct DataSegment { name ?string } +struct Table { + name string + export bool + min u32 + max ?u32 + elem_type RefType = .funcref_t +} + +struct Element { + table_idx u32 + offset ConstExpression + func_names []string + name ?string +} + pub type LocalIndex = int pub type GlobalIndex = int pub type GlobalImportIndex = int @@ -278,3 +295,101 @@ pub fn (mut mod Module) new_global_import(modn string, name string, typ ValType, pub fn (mut mod Module) assign_global_init(global GlobalIndex, init ConstExpression) { mod.globals[global].init = init } + +// new_table creates a new table and returns its index. +pub fn (mut mod Module) new_table(name string, export bool, min u32, max ?u32) int { + len := mod.tables.len + mod.tables << Table{ + name: name + export: export + min: min + max: max + elem_type: .funcref_t + } + return len +} + +// new_element_segment creates a new element segment for initializing tables. +pub fn (mut mod Module) new_element_segment(name ?string, table_idx u32, offset ConstExpression, func_names []string) int { + len := mod.elements.len + mod.elements << Element{ + table_idx: table_idx + offset: offset + func_names: func_names + name: name + } + return len +} + +// wasi_import_signature returns the function signature for a WASI preview1 function. +pub fn wasi_import_signature(name string) ?FuncType { + return match name { + 'fd_write' { + FuncType{ + parameters: [.i32_t, .i32_t, .i32_t, .i32_t] + results: [.i32_t] + name: none + } + } + 'proc_exit' { + FuncType{ + parameters: [.i32_t] + results: [] + name: none + } + } + 'args_get' { + FuncType{ + parameters: [.i32_t, .i32_t] + results: [.i32_t] + name: none + } + } + 'args_sizes_get' { + FuncType{ + parameters: [.i32_t, .i32_t] + results: [.i32_t] + name: none + } + } + 'environ_get' { + FuncType{ + parameters: [.i32_t, .i32_t] + results: [.i32_t] + name: none + } + } + 'environ_sizes_get' { + FuncType{ + parameters: [.i32_t, .i32_t] + results: [.i32_t] + name: none + } + } + 'clock_time_get' { + FuncType{ + parameters: [.i32_t, .i64_t, .i32_t] + results: [.i32_t] + name: none + } + } + 'random_get' { + FuncType{ + parameters: [.i32_t, .i32_t] + results: [.i32_t] + name: none + } + } + else { + none + } + } +} + +// add_wasi_import adds a WASI preview1 import to the module. +pub fn (mut mod Module) add_wasi_import(name string) int { + sig := wasi_import_signature(name) or { panic('Unknown WASI function: ${name}') } + mod.new_function_import('wasi_snapshot_preview1', name, sig.parameters, sig.results) + // Return the index of the imported function + return mod.fn_imports.len - 1 +} diff --git a/vlib/wasm/tests/branch_test.v b/vlib/wasm/tests/branch_test.v new file mode 100644 index 00000000000000..9798295ce33f13 --- /dev/null +++ b/vlib/wasm/tests/branch_test.v @@ -0,0 +1,104 @@ +module main + +import wasm + +fn test_br_instructions() { + mut m := wasm.Module{} + + // Test direct br instruction + mut func1 := m.new_function('test_br', [], [.i32_t]) + { + func1.i32_const(1) + blk := func1.c_block([], [.i32_t]) + { + func1.i32_const(2) + func1.br(0) // branch to block + func1.i32_const(3) // unreachable + } + func1.c_end(blk) + } + m.commit(func1, true) + + // Test br_if instruction + mut func2 := m.new_function('test_br_if', [.i32_t], [.i32_t]) + { + func2.i32_const(10) + blk := func2.c_block([], [.i32_t]) + { + func2.i32_const(20) + func2.local_get(0) + func2.br_if(0) // conditional branch + func2.drop() + func2.i32_const(30) + } + func2.c_end(blk) + } + m.commit(func2, true) + + // Test br_table instruction + mut func3 := m.new_function('test_br_table', [.i32_t], [.i32_t]) + { + blk0 := func3.c_block([], [.i32_t]) + { + blk1 := func3.c_block([], []) + { + blk2 := func3.c_block([], []) + { + func3.local_get(0) + func3.br_table([u32(0), u32(1)], u32(2)) + } + func3.c_end(blk2) + func3.i32_const(2) + func3.c_br(blk0) + } + func3.c_end(blk1) + func3.i32_const(1) + func3.c_br(blk0) + } + func3.c_end(blk0) + func3.i32_const(0) + } + m.commit(func3, true) + + code := m.compile() + assert code.len > 0 + + validate(code) or { panic(err) } +} + +fn test_i32_comparison_shortcuts() { + mut m := wasm.Module{} + + // Test various comparison shortcuts + mut func := m.new_function('test_comparisons', [.i32_t, .i32_t], [.i32_t]) + { + // Test i32_eq + func.local_get(0) + func.local_get(1) + func.i32_eq() + + // Test i32_ne + func.local_get(0) + func.local_get(1) + func.i32_ne() + func.b_and(.i32_t) + + // Test i32_lt_s + func.local_get(0) + func.local_get(1) + func.i32_lt_s() + func.b_or(.i32_t) + + // Test i32_gt_s + func.local_get(0) + func.local_get(1) + func.i32_gt_s() + func.b_or(.i32_t) + } + m.commit(func, true) + + code := m.compile() + assert code.len > 0 + + validate(code) or { panic(err) } +} diff --git a/vlib/wasm/tests/table_test.v b/vlib/wasm/tests/table_test.v new file mode 100644 index 00000000000000..bbb8fde0ca0335 --- /dev/null +++ b/vlib/wasm/tests/table_test.v @@ -0,0 +1,61 @@ +module main + +import wasm + +fn test_table_section() { + mut m := wasm.Module{} + + // Create a simple function to reference in the table + mut func1 := m.new_function('table_func', [], [.i32_t]) + { + func1.i32_const(42) + } + m.commit(func1, false) + + // Create a table + table_idx := m.new_table('my_table', true, 1, 10) + + // Create an element segment to initialize the table + mut offset_expr := wasm.ConstExpression{} + offset_expr.i32_const(0) + m.new_element_segment(none, u32(table_idx), offset_expr, ['table_func']) + + code := m.compile() + assert code.len > 0 + + validate(code) or { panic(err) } +} + +fn test_call_indirect() { + mut m := wasm.Module{} + + // Create a function to call indirectly + mut target := m.new_function('target', [], [.i32_t]) + { + target.i32_const(99) + } + m.commit(target, false) + + // Create a table + table_idx := m.new_table('func_table', false, 1, 10) + + // Initialize table with the target function + mut offset_expr := wasm.ConstExpression{} + offset_expr.i32_const(0) + m.new_element_segment(none, u32(table_idx), offset_expr, ['target']) + + // Create a function that does an indirect call + // Use the same type as 'target': [] -> [.i32_t] + mut caller := m.new_function('caller', [], [.i32_t]) + { + caller.i32_const(0) // table index + // The type index should be 0 (first function type registered) + caller.call_indirect(0, u32(table_idx)) + } + m.commit(caller, true) + + code := m.compile() + assert code.len > 0 + + validate(code) or { panic(err) } +} diff --git a/vlib/wasm/tests/wasi_test.v b/vlib/wasm/tests/wasi_test.v new file mode 100644 index 00000000000000..0fd602254463f9 --- /dev/null +++ b/vlib/wasm/tests/wasi_test.v @@ -0,0 +1,68 @@ +module main + +import wasm + +fn test_wasi_fd_write() { + mut m := wasm.Module{} + + // Add WASI fd_write import + fd_write_idx := m.add_wasi_import('fd_write') + + // Create a function that uses fd_write + mut func := m.new_function('write_test', [], [.i32_t]) + { + func.i32_const(1) // fd (stdout) + func.i32_const(0) // iovs pointer + func.i32_const(1) // iovs_len + func.i32_const(0) // nwritten pointer + func.call_import('wasi_snapshot_preview1', 'fd_write') + } + m.commit(func, true) + + code := m.compile() + assert code.len > 0 + + validate(code) or { panic(err) } +} + +fn test_wasi_proc_exit() { + mut m := wasm.Module{} + + // Add WASI proc_exit import + m.add_wasi_import('proc_exit') + + // Create a function that exits + mut func := m.new_function('exit_test', [], []) + { + func.i32_const(0) // exit code + func.call_import('wasi_snapshot_preview1', 'proc_exit') + } + m.commit(func, true) + + code := m.compile() + assert code.len > 0 + + validate(code) or { panic(err) } +} + +fn test_wasi_multiple_imports() { + mut m := wasm.Module{} + + // Add multiple WASI imports + m.add_wasi_import('fd_write') + m.add_wasi_import('proc_exit') + m.add_wasi_import('args_get') + m.add_wasi_import('environ_get') + + // Create a simple function + mut func := m.new_function('test', [], []) + { + func.nop() + } + m.commit(func, true) + + code := m.compile() + assert code.len > 0 + + validate(code) or { panic(err) } +}