From 85768336b5364752a0e5d07e31814d2f95f8e2ba Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 24 Apr 2026 01:44:07 +0300 Subject: [PATCH] wasm: fix wasm backend as a shared library (fixes #17621) --- vlib/v/gen/wasm/mem.v | 2 +- .../gen/wasm/tests/browser_empty_main_test.v | 120 ++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/vlib/v/gen/wasm/mem.v b/vlib/v/gen/wasm/mem.v index f9ce1f1e4..6d0830835 100644 --- a/vlib/v/gen/wasm/mem.v +++ b/vlib/v/gen/wasm/mem.v @@ -900,7 +900,7 @@ pub fn (mut g Gen) housekeeping() { g.mod.assign_global_init(hp, wasm.constexpr_value(heap_base)) } - if g.pref.os == .wasi { + if g.pref.os == .wasi && !g.pref.is_shared { mut fn_start := g.mod.new_function('_start', [], []) { fn_start.call('_vinit') diff --git a/vlib/v/gen/wasm/tests/browser_empty_main_test.v b/vlib/v/gen/wasm/tests/browser_empty_main_test.v index 02c50090d..afa0181b3 100644 --- a/vlib/v/gen/wasm/tests/browser_empty_main_test.v +++ b/vlib/v/gen/wasm/tests/browser_empty_main_test.v @@ -1,5 +1,16 @@ import os +struct WasmVarUint { + value u32 + next_idx int +} + +struct WasmModuleSummary { +mut: + exports []string + has_start_section bool +} + fn test_wasm_browser_target_allows_empty_main() { vexe := os.quoted_path(@VEXE) wrkdir := os.join_path(os.vtmp_dir(), 'wasm_browser_tests') @@ -24,3 +35,112 @@ fn test_wasm_browser_target_allows_empty_main() { assert os.exists(output_path), 'missing output for `${flags}`' } } + +fn test_wasm_shared_library_exports_custom_names_without_main() { + vexe := os.quoted_path(@VEXE) + wrkdir := os.join_path(os.vtmp_dir(), 'wasm_shared_library_tests') + os.mkdir_all(wrkdir)! + defer { + os.rmdir_all(wrkdir) or {} + } + + source_path := os.join_path(wrkdir, 'my_wasm_lib.v') + output_path := os.join_path(wrkdir, 'my_wasm_lib.wasm') + source := [ + 'module my_wasm_lib', + '', + "@[export: 'myFunction']", + 'fn my_function(a int, b int) int {', + '\treturn a + b', + '}', + ].join_lines() + os.write_file(source_path, source)! + + res := + os.execute('${vexe} -b wasm -shared -o ${os.quoted_path(output_path)} ${os.quoted_path(source_path)}') + assert res.exit_code == 0, 'compilation failed: ${res.output}' + assert os.exists(output_path), 'missing output for shared wasm library' + + wasm_bytes := os.read_bytes(output_path)! + summary := inspect_wasm_module(wasm_bytes) or { panic(err) } + + assert 'myFunction' in summary.exports + assert '_start' !in summary.exports + assert summary.has_start_section +} + +fn inspect_wasm_module(wasm_bytes []u8) !WasmModuleSummary { + if wasm_bytes.len < 8 { + return error('wasm module is too short') + } + if wasm_bytes[0] != 0x00 || wasm_bytes[1] != 0x61 || wasm_bytes[2] != 0x73 + || wasm_bytes[3] != 0x6d || wasm_bytes[4] != 0x01 || wasm_bytes[5] != 0x00 + || wasm_bytes[6] != 0x00 || wasm_bytes[7] != 0x00 { + return error('invalid wasm header') + } + + mut summary := WasmModuleSummary{} + mut idx := 8 + for idx < wasm_bytes.len { + section_id := wasm_bytes[idx] + idx++ + section_len := read_wasm_u32(wasm_bytes, idx)! + idx = section_len.next_idx + section_end := idx + int(section_len.value) + if section_end > wasm_bytes.len { + return error('wasm section exceeds module bounds') + } + match section_id { + u8(7) { + mut export_idx := idx + exports_len := read_wasm_u32(wasm_bytes, export_idx)! + export_idx = exports_len.next_idx + for _ in 0 .. int(exports_len.value) { + name_len := read_wasm_u32(wasm_bytes, export_idx)! + export_idx = name_len.next_idx + name_end := export_idx + int(name_len.value) + if name_end > section_end { + return error('wasm export name exceeds section bounds') + } + summary.exports << wasm_bytes[export_idx..name_end].bytestr() + export_idx = name_end + if export_idx >= section_end { + return error('wasm export kind is missing') + } + export_idx++ + export_ref := read_wasm_u32(wasm_bytes, export_idx)! + export_idx = export_ref.next_idx + } + } + u8(8) { + summary.has_start_section = true + } + else {} + } + + idx = section_end + } + return summary +} + +fn read_wasm_u32(wasm_bytes []u8, start int) !WasmVarUint { + mut value := u32(0) + mut shift := u32(0) + mut idx := start + for idx < wasm_bytes.len { + b := wasm_bytes[idx] + value |= u32(b & 0x7f) << shift + idx++ + if b & 0x80 == 0 { + return WasmVarUint{ + value: value + next_idx: idx + } + } + shift += 7 + if shift >= 35 { + break + } + } + return error('invalid wasm varuint32 encoding') +} -- 2.39.5