| 1 | module wasm |
| 2 | |
| 3 | // encode.v is the self-contained WebAssembly binary encoder used by the v3 |
| 4 | // wasm backend. It mirrors the role of the arm64 backend's asm.v + macho.v: |
| 5 | // gen.v walks the flat AST and drives this module, which knows nothing about V |
| 6 | // and only emits raw WASM bytecode (LEB128 + sections). No external module is |
| 7 | // imported, so v3 stays self-contained and self-hostable. |
| 8 | |
| 9 | // WASM value types. |
| 10 | pub const valtype_i32 = u8(0x7f) |
| 11 | pub const valtype_i64 = u8(0x7e) |
| 12 | pub const valtype_f32 = u8(0x7d) |
| 13 | pub const valtype_f64 = u8(0x7c) |
| 14 | |
| 15 | // Export kinds. |
| 16 | pub const export_func = u8(0x00) |
| 17 | pub const export_mem = u8(0x02) |
| 18 | |
| 19 | // FuncType is a single `(params) -> (results)` signature. |
| 20 | struct FuncType { |
| 21 | params []u8 |
| 22 | results []u8 |
| 23 | } |
| 24 | |
| 25 | // ImportFunc is an imported function (only the WASI import is used for now). |
| 26 | struct ImportFunc { |
| 27 | module string |
| 28 | name string |
| 29 | type_idx int |
| 30 | } |
| 31 | |
| 32 | // Func is a locally defined function. `code` already ends with the `end` (0x0b) |
| 33 | // opcode; `locals` lists the value type of each local declared beyond params. |
| 34 | struct Func { |
| 35 | type_idx int |
| 36 | locals []u8 |
| 37 | code []u8 |
| 38 | } |
| 39 | |
| 40 | struct Export { |
| 41 | name string |
| 42 | kind u8 |
| 43 | index int |
| 44 | } |
| 45 | |
| 46 | // DataSeg is an active data segment placed at a fixed linear-memory offset. |
| 47 | struct DataSeg { |
| 48 | offset int |
| 49 | bytes []u8 |
| 50 | } |
| 51 | |
| 52 | // Global is a module-level mutable global variable. |
| 53 | struct Global { |
| 54 | valtype u8 |
| 55 | init []u8 // constant init expression, ending with `end` (0x0b) |
| 56 | } |
| 57 | |
| 58 | @[heap] |
| 59 | pub struct Module { |
| 60 | mut: |
| 61 | types []FuncType |
| 62 | imports []ImportFunc |
| 63 | funcs []Func |
| 64 | exports []Export |
| 65 | datas []DataSeg |
| 66 | globals []Global |
| 67 | mem_min int = 2 |
| 68 | n_import int |
| 69 | } |
| 70 | |
| 71 | pub fn Module.new() &Module { |
| 72 | return &Module{} |
| 73 | } |
| 74 | |
| 75 | // add_type registers a signature, deduplicating identical ones. |
| 76 | pub fn (mut m Module) add_type(params []u8, results []u8) int { |
| 77 | for i, t in m.types { |
| 78 | if t.params == params && t.results == results { |
| 79 | return i |
| 80 | } |
| 81 | } |
| 82 | m.types << FuncType{ |
| 83 | params: params.clone() |
| 84 | results: results.clone() |
| 85 | } |
| 86 | return m.types.len - 1 |
| 87 | } |
| 88 | |
| 89 | // add_import_func registers an imported function and returns its function index. |
| 90 | // Imports must be added before any defined function so indices stay stable. |
| 91 | pub fn (mut m Module) add_import_func(module_ string, name string, type_idx int) int { |
| 92 | m.imports << ImportFunc{ |
| 93 | module: module_ |
| 94 | name: name |
| 95 | type_idx: type_idx |
| 96 | } |
| 97 | m.n_import = m.imports.len |
| 98 | return m.imports.len - 1 |
| 99 | } |
| 100 | |
| 101 | // add_global appends a mutable global and returns its index. |
| 102 | pub fn (mut m Module) add_global(valtype u8, init []u8) int { |
| 103 | m.globals << Global{ |
| 104 | valtype: valtype |
| 105 | init: init.clone() |
| 106 | } |
| 107 | return m.globals.len - 1 |
| 108 | } |
| 109 | |
| 110 | // reserve_func_index returns the function index a later add_func will occupy. |
| 111 | pub fn (m &Module) reserve_func_index(defined_so_far int) int { |
| 112 | return m.n_import + defined_so_far |
| 113 | } |
| 114 | |
| 115 | // add_func appends a defined function and returns its global function index. |
| 116 | pub fn (mut m Module) add_func(type_idx int, locals []u8, code []u8) int { |
| 117 | m.funcs << Func{ |
| 118 | type_idx: type_idx |
| 119 | locals: locals.clone() |
| 120 | code: code.clone() |
| 121 | } |
| 122 | return m.n_import + m.funcs.len - 1 |
| 123 | } |
| 124 | |
| 125 | pub fn (mut m Module) add_export(name string, kind u8, index int) { |
| 126 | m.exports << Export{ |
| 127 | name: name |
| 128 | kind: kind |
| 129 | index: index |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | pub fn (mut m Module) add_data(offset int, bytes []u8) { |
| 134 | m.datas << DataSeg{ |
| 135 | offset: offset |
| 136 | bytes: bytes.clone() |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | pub fn (mut m Module) set_mem_min(pages int) { |
| 141 | m.mem_min = pages |
| 142 | } |
| 143 | |
| 144 | // ---- LEB128 ---- |
| 145 | |
| 146 | fn leb_u(mut out []u8, val_ u64) { |
| 147 | mut val := val_ |
| 148 | for { |
| 149 | mut b := u8(val & 0x7f) |
| 150 | val >>= 7 |
| 151 | if val != 0 { |
| 152 | b |= 0x80 |
| 153 | } |
| 154 | out << b |
| 155 | if val == 0 { |
| 156 | break |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | fn leb_i(mut out []u8, val_ i64) { |
| 162 | mut val := val_ |
| 163 | for { |
| 164 | mut b := u8(val & 0x7f) |
| 165 | val >>= 7 |
| 166 | done := (val == 0 && (b & 0x40) == 0) || (val == -1 && (b & 0x40) != 0) |
| 167 | if !done { |
| 168 | b |= 0x80 |
| 169 | } |
| 170 | out << b |
| 171 | if done { |
| 172 | break |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | fn write_name(mut out []u8, name string) { |
| 178 | leb_u(mut out, u64(name.len)) |
| 179 | for c in name { |
| 180 | out << c |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | // section wraps a section body with its id byte and LEB-encoded length. |
| 185 | fn section(mut out []u8, id u8, body []u8) { |
| 186 | if body.len == 0 { |
| 187 | return |
| 188 | } |
| 189 | out << id |
| 190 | leb_u(mut out, u64(body.len)) |
| 191 | out << body |
| 192 | } |
| 193 | |
| 194 | // compress_locals run-length-encodes the per-local value types into the wasm |
| 195 | // locals vector: count-of-groups, then (count, valtype) groups. |
| 196 | fn compress_locals(locals []u8) []u8 { |
| 197 | mut groups := [][]int{} |
| 198 | for vt in locals { |
| 199 | if groups.len > 0 && groups[groups.len - 1][1] == int(vt) { |
| 200 | groups[groups.len - 1][0]++ |
| 201 | } else { |
| 202 | groups << [1, int(vt)] |
| 203 | } |
| 204 | } |
| 205 | mut out := []u8{} |
| 206 | leb_u(mut out, u64(groups.len)) |
| 207 | for g in groups { |
| 208 | leb_u(mut out, u64(g[0])) |
| 209 | out << u8(g[1]) |
| 210 | } |
| 211 | return out |
| 212 | } |
| 213 | |
| 214 | // compile serializes the whole module into a `.wasm` byte buffer. |
| 215 | pub fn (m &Module) compile() []u8 { |
| 216 | mut out := []u8{} |
| 217 | // magic + version |
| 218 | out << [u8(0x00), 0x61, 0x73, 0x6d] |
| 219 | out << [u8(0x01), 0x00, 0x00, 0x00] |
| 220 | |
| 221 | // type section (1) |
| 222 | mut tsec := []u8{} |
| 223 | leb_u(mut tsec, u64(m.types.len)) |
| 224 | for t in m.types { |
| 225 | tsec << 0x60 |
| 226 | leb_u(mut tsec, u64(t.params.len)) |
| 227 | tsec << t.params |
| 228 | leb_u(mut tsec, u64(t.results.len)) |
| 229 | tsec << t.results |
| 230 | } |
| 231 | section(mut out, 0x01, tsec) |
| 232 | |
| 233 | // import section (2) |
| 234 | if m.imports.len > 0 { |
| 235 | mut isec := []u8{} |
| 236 | leb_u(mut isec, u64(m.imports.len)) |
| 237 | for imp in m.imports { |
| 238 | write_name(mut isec, imp.module) |
| 239 | write_name(mut isec, imp.name) |
| 240 | isec << 0x00 // import kind: func |
| 241 | leb_u(mut isec, u64(imp.type_idx)) |
| 242 | } |
| 243 | section(mut out, 0x02, isec) |
| 244 | } |
| 245 | |
| 246 | // function section (3) |
| 247 | mut fsec := []u8{} |
| 248 | leb_u(mut fsec, u64(m.funcs.len)) |
| 249 | for f in m.funcs { |
| 250 | leb_u(mut fsec, u64(f.type_idx)) |
| 251 | } |
| 252 | section(mut out, 0x03, fsec) |
| 253 | |
| 254 | // memory section (5) |
| 255 | mut msec := []u8{} |
| 256 | leb_u(mut msec, 1) // one memory |
| 257 | msec << 0x00 // limits: min only |
| 258 | leb_u(mut msec, u64(m.mem_min)) |
| 259 | section(mut out, 0x05, msec) |
| 260 | |
| 261 | // global section (6) |
| 262 | if m.globals.len > 0 { |
| 263 | mut gsec := []u8{} |
| 264 | leb_u(mut gsec, u64(m.globals.len)) |
| 265 | for gl in m.globals { |
| 266 | gsec << gl.valtype |
| 267 | gsec << 0x01 // mutable |
| 268 | gsec << gl.init |
| 269 | } |
| 270 | section(mut out, 0x06, gsec) |
| 271 | } |
| 272 | |
| 273 | // export section (7) |
| 274 | mut esec := []u8{} |
| 275 | leb_u(mut esec, u64(m.exports.len)) |
| 276 | for e in m.exports { |
| 277 | write_name(mut esec, e.name) |
| 278 | esec << e.kind |
| 279 | leb_u(mut esec, u64(e.index)) |
| 280 | } |
| 281 | section(mut out, 0x07, esec) |
| 282 | |
| 283 | // code section (10) |
| 284 | mut csec := []u8{} |
| 285 | leb_u(mut csec, u64(m.funcs.len)) |
| 286 | for f in m.funcs { |
| 287 | mut body := compress_locals(f.locals) |
| 288 | body << f.code |
| 289 | leb_u(mut csec, u64(body.len)) |
| 290 | csec << body |
| 291 | } |
| 292 | section(mut out, 0x0a, csec) |
| 293 | |
| 294 | // data section (11) |
| 295 | if m.datas.len > 0 { |
| 296 | mut dsec := []u8{} |
| 297 | leb_u(mut dsec, u64(m.datas.len)) |
| 298 | for d in m.datas { |
| 299 | dsec << 0x00 // active segment, memory 0 |
| 300 | dsec << 0x41 // i32.const offset |
| 301 | leb_i(mut dsec, i64(d.offset)) |
| 302 | dsec << 0x0b // end of offset expr |
| 303 | leb_u(mut dsec, u64(d.bytes.len)) |
| 304 | dsec << d.bytes |
| 305 | } |
| 306 | section(mut out, 0x0b, dsec) |
| 307 | } |
| 308 | |
| 309 | return out |
| 310 | } |
| 311 | |