| 1 | import os |
| 2 | |
| 3 | struct WasmVarUint { |
| 4 | value u32 |
| 5 | next_idx int |
| 6 | } |
| 7 | |
| 8 | struct WasmModuleSummary { |
| 9 | mut: |
| 10 | exports []string |
| 11 | has_start_section bool |
| 12 | } |
| 13 | |
| 14 | fn test_wasm_browser_target_allows_empty_main() { |
| 15 | vexe := os.quoted_path(@VEXE) |
| 16 | wrkdir := os.join_path(os.vtmp_dir(), 'wasm_browser_tests') |
| 17 | os.mkdir_all(wrkdir)! |
| 18 | defer { |
| 19 | os.rmdir_all(wrkdir) or {} |
| 20 | } |
| 21 | |
| 22 | source_path := os.join_path(wrkdir, 'empty_main.wasm.v') |
| 23 | os.write_file(source_path, 'pub fn main() {}\n')! |
| 24 | |
| 25 | flags_sets := [ |
| 26 | '-no-bounds-checking -b wasm -os browser', |
| 27 | '-no-bounds-checking -enable-globals -b wasm -os browser', |
| 28 | ] |
| 29 | |
| 30 | for idx, flags in flags_sets { |
| 31 | output_path := os.join_path(wrkdir, 'empty_main_${idx}.wasm') |
| 32 | res := |
| 33 | os.execute('${vexe} ${flags} -o ${os.quoted_path(output_path)} ${os.quoted_path(source_path)}') |
| 34 | assert res.exit_code == 0, 'compilation failed for `${flags}`: ${res.output}' |
| 35 | assert os.exists(output_path), 'missing output for `${flags}`' |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | fn test_wasm_shared_library_exports_custom_names_without_main() { |
| 40 | vexe := os.quoted_path(@VEXE) |
| 41 | wrkdir := os.join_path(os.vtmp_dir(), 'wasm_shared_library_tests') |
| 42 | os.mkdir_all(wrkdir)! |
| 43 | defer { |
| 44 | os.rmdir_all(wrkdir) or {} |
| 45 | } |
| 46 | |
| 47 | source_path := os.join_path(wrkdir, 'my_wasm_lib.v') |
| 48 | output_path := os.join_path(wrkdir, 'my_wasm_lib.wasm') |
| 49 | source := [ |
| 50 | 'module my_wasm_lib', |
| 51 | '', |
| 52 | "@[export: 'myFunction']", |
| 53 | 'fn my_function(a int, b int) int {', |
| 54 | '\treturn a + b', |
| 55 | '}', |
| 56 | ].join_lines() |
| 57 | os.write_file(source_path, source)! |
| 58 | |
| 59 | res := |
| 60 | os.execute('${vexe} -b wasm -shared -o ${os.quoted_path(output_path)} ${os.quoted_path(source_path)}') |
| 61 | assert res.exit_code == 0, 'compilation failed: ${res.output}' |
| 62 | assert os.exists(output_path), 'missing output for shared wasm library' |
| 63 | |
| 64 | wasm_bytes := os.read_bytes(output_path)! |
| 65 | summary := inspect_wasm_module(wasm_bytes) or { panic(err) } |
| 66 | |
| 67 | assert 'myFunction' in summary.exports |
| 68 | assert '_start' !in summary.exports |
| 69 | assert summary.has_start_section |
| 70 | } |
| 71 | |
| 72 | fn inspect_wasm_module(wasm_bytes []u8) !WasmModuleSummary { |
| 73 | if wasm_bytes.len < 8 { |
| 74 | return error('wasm module is too short') |
| 75 | } |
| 76 | if wasm_bytes[0] != 0x00 || wasm_bytes[1] != 0x61 || wasm_bytes[2] != 0x73 |
| 77 | || wasm_bytes[3] != 0x6d || wasm_bytes[4] != 0x01 || wasm_bytes[5] != 0x00 |
| 78 | || wasm_bytes[6] != 0x00 || wasm_bytes[7] != 0x00 { |
| 79 | return error('invalid wasm header') |
| 80 | } |
| 81 | |
| 82 | mut summary := WasmModuleSummary{} |
| 83 | mut idx := 8 |
| 84 | for idx < wasm_bytes.len { |
| 85 | section_id := wasm_bytes[idx] |
| 86 | idx++ |
| 87 | section_len := read_wasm_u32(wasm_bytes, idx)! |
| 88 | idx = section_len.next_idx |
| 89 | section_end := idx + int(section_len.value) |
| 90 | if section_end > wasm_bytes.len { |
| 91 | return error('wasm section exceeds module bounds') |
| 92 | } |
| 93 | match section_id { |
| 94 | u8(7) { |
| 95 | mut export_idx := idx |
| 96 | exports_len := read_wasm_u32(wasm_bytes, export_idx)! |
| 97 | export_idx = exports_len.next_idx |
| 98 | for _ in 0 .. int(exports_len.value) { |
| 99 | name_len := read_wasm_u32(wasm_bytes, export_idx)! |
| 100 | export_idx = name_len.next_idx |
| 101 | name_end := export_idx + int(name_len.value) |
| 102 | if name_end > section_end { |
| 103 | return error('wasm export name exceeds section bounds') |
| 104 | } |
| 105 | summary.exports << wasm_bytes[export_idx..name_end].bytestr() |
| 106 | export_idx = name_end |
| 107 | if export_idx >= section_end { |
| 108 | return error('wasm export kind is missing') |
| 109 | } |
| 110 | export_idx++ |
| 111 | export_ref := read_wasm_u32(wasm_bytes, export_idx)! |
| 112 | export_idx = export_ref.next_idx |
| 113 | } |
| 114 | } |
| 115 | u8(8) { |
| 116 | summary.has_start_section = true |
| 117 | } |
| 118 | else {} |
| 119 | } |
| 120 | |
| 121 | idx = section_end |
| 122 | } |
| 123 | return summary |
| 124 | } |
| 125 | |
| 126 | fn read_wasm_u32(wasm_bytes []u8, start int) !WasmVarUint { |
| 127 | mut value := u32(0) |
| 128 | mut shift := u32(0) |
| 129 | mut idx := start |
| 130 | for idx < wasm_bytes.len { |
| 131 | b := wasm_bytes[idx] |
| 132 | value |= u32(b & 0x7f) << shift |
| 133 | idx++ |
| 134 | if b & 0x80 == 0 { |
| 135 | return WasmVarUint{ |
| 136 | value: value |
| 137 | next_idx: idx |
| 138 | } |
| 139 | } |
| 140 | shift += 7 |
| 141 | if shift >= 35 { |
| 142 | break |
| 143 | } |
| 144 | } |
| 145 | return error('invalid wasm varuint32 encoding') |
| 146 | } |
| 147 | |