blob 5d5d4e1c (58870B) - Raw
1 const std = @import("std"); 2 const Allocator = std.mem.Allocator; 3 const ArrayList = std.ArrayList; 4 const assert = std.debug.assert; 5 const testing = std.testing; 6 const leb = std.leb; 7 const mem = std.mem; 8 const wasm = std.wasm; 9 10 const Module = @import("../Module.zig"); 11 const Decl = Module.Decl; 12 const Type = @import("../type.zig").Type; 13 const Value = @import("../value.zig").Value; 14 const Compilation = @import("../Compilation.zig"); 15 const LazySrcLoc = Module.LazySrcLoc; 16 const link = @import("../link.zig"); 17 const TypedValue = @import("../TypedValue.zig"); 18 const Air = @import("../Air.zig"); 19 const Liveness = @import("../Liveness.zig"); 20 21 /// Wasm Value, created when generating an instruction 22 const WValue = union(enum) { 23 /// May be referenced but is unused 24 none: void, 25 /// Index of the local variable 26 local: u32, 27 /// Holds a memoized typed value 28 constant: TypedValue, 29 /// Offset position in the list of bytecode instructions 30 code_offset: usize, 31 /// Used for variables that create multiple locals on the stack when allocated 32 /// such as structs and optionals. 33 multi_value: struct { 34 /// The index of the first local variable 35 index: u32, 36 /// The count of local variables this `WValue` consists of. 37 /// i.e. an ErrorUnion has a 'count' of 2. 38 count: u32, 39 }, 40 }; 41 42 /// Wasm ops, but without input/output/signedness information 43 /// Used for `buildOpcode` 44 const Op = enum { 45 @"unreachable", 46 nop, 47 block, 48 loop, 49 @"if", 50 @"else", 51 end, 52 br, 53 br_if, 54 br_table, 55 @"return", 56 call, 57 call_indirect, 58 drop, 59 select, 60 local_get, 61 local_set, 62 local_tee, 63 global_get, 64 global_set, 65 load, 66 store, 67 memory_size, 68 memory_grow, 69 @"const", 70 eqz, 71 eq, 72 ne, 73 lt, 74 gt, 75 le, 76 ge, 77 clz, 78 ctz, 79 popcnt, 80 add, 81 sub, 82 mul, 83 div, 84 rem, 85 @"and", 86 @"or", 87 xor, 88 shl, 89 shr, 90 rotl, 91 rotr, 92 abs, 93 neg, 94 ceil, 95 floor, 96 trunc, 97 nearest, 98 sqrt, 99 min, 100 max, 101 copysign, 102 wrap, 103 convert, 104 demote, 105 promote, 106 reinterpret, 107 extend, 108 }; 109 110 /// Contains the settings needed to create an `Opcode` using `buildOpcode`. 111 /// 112 /// The fields correspond to the opcode name. Here is an example 113 /// i32_trunc_f32_s 114 /// ^ ^ ^ ^ 115 /// | | | | 116 /// valtype1 | | | 117 /// = .i32 | | | 118 /// | | | 119 /// op | | 120 /// = .trunc | | 121 /// | | 122 /// valtype2 | 123 /// = .f32 | 124 /// | 125 /// width | 126 /// = null | 127 /// | 128 /// signed 129 /// = true 130 /// 131 /// There can be missing fields, here are some more examples: 132 /// i64_load8_u 133 /// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false } 134 /// i32_mul 135 /// --> .{ .valtype1 = .i32, .op = .trunc } 136 /// nop 137 /// --> .{ .op = .nop } 138 const OpcodeBuildArguments = struct { 139 /// First valtype in the opcode (usually represents the type of the output) 140 valtype1: ?wasm.Valtype = null, 141 /// The operation (e.g. call, unreachable, div, min, sqrt, etc.) 142 op: Op, 143 /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s) 144 width: ?u8 = null, 145 /// Second valtype in the opcode name (usually represents the type of the input) 146 valtype2: ?wasm.Valtype = null, 147 /// Signedness of the op 148 signedness: ?std.builtin.Signedness = null, 149 }; 150 151 /// Helper function that builds an Opcode given the arguments needed 152 fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode { 153 switch (args.op) { 154 .@"unreachable" => return .@"unreachable", 155 .nop => return .nop, 156 .block => return .block, 157 .loop => return .loop, 158 .@"if" => return .@"if", 159 .@"else" => return .@"else", 160 .end => return .end, 161 .br => return .br, 162 .br_if => return .br_if, 163 .br_table => return .br_table, 164 .@"return" => return .@"return", 165 .call => return .call, 166 .call_indirect => return .call_indirect, 167 .drop => return .drop, 168 .select => return .select, 169 .local_get => return .local_get, 170 .local_set => return .local_set, 171 .local_tee => return .local_tee, 172 .global_get => return .global_get, 173 .global_set => return .global_set, 174 175 .load => if (args.width) |width| switch (width) { 176 8 => switch (args.valtype1.?) { 177 .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u, 178 .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u, 179 .f32, .f64 => unreachable, 180 }, 181 16 => switch (args.valtype1.?) { 182 .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u, 183 .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u, 184 .f32, .f64 => unreachable, 185 }, 186 32 => switch (args.valtype1.?) { 187 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u, 188 .i32, .f32, .f64 => unreachable, 189 }, 190 else => unreachable, 191 } else switch (args.valtype1.?) { 192 .i32 => return .i32_load, 193 .i64 => return .i64_load, 194 .f32 => return .f32_load, 195 .f64 => return .f64_load, 196 }, 197 .store => if (args.width) |width| { 198 switch (width) { 199 8 => switch (args.valtype1.?) { 200 .i32 => return .i32_store8, 201 .i64 => return .i64_store8, 202 .f32, .f64 => unreachable, 203 }, 204 16 => switch (args.valtype1.?) { 205 .i32 => return .i32_store16, 206 .i64 => return .i64_store16, 207 .f32, .f64 => unreachable, 208 }, 209 32 => switch (args.valtype1.?) { 210 .i64 => return .i64_store32, 211 .i32, .f32, .f64 => unreachable, 212 }, 213 else => unreachable, 214 } 215 } else { 216 switch (args.valtype1.?) { 217 .i32 => return .i32_store, 218 .i64 => return .i64_store, 219 .f32 => return .f32_store, 220 .f64 => return .f64_store, 221 } 222 }, 223 224 .memory_size => return .memory_size, 225 .memory_grow => return .memory_grow, 226 227 .@"const" => switch (args.valtype1.?) { 228 .i32 => return .i32_const, 229 .i64 => return .i64_const, 230 .f32 => return .f32_const, 231 .f64 => return .f64_const, 232 }, 233 234 .eqz => switch (args.valtype1.?) { 235 .i32 => return .i32_eqz, 236 .i64 => return .i64_eqz, 237 .f32, .f64 => unreachable, 238 }, 239 .eq => switch (args.valtype1.?) { 240 .i32 => return .i32_eq, 241 .i64 => return .i64_eq, 242 .f32 => return .f32_eq, 243 .f64 => return .f64_eq, 244 }, 245 .ne => switch (args.valtype1.?) { 246 .i32 => return .i32_ne, 247 .i64 => return .i64_ne, 248 .f32 => return .f32_ne, 249 .f64 => return .f64_ne, 250 }, 251 252 .lt => switch (args.valtype1.?) { 253 .i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u, 254 .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u, 255 .f32 => return .f32_lt, 256 .f64 => return .f64_lt, 257 }, 258 .gt => switch (args.valtype1.?) { 259 .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u, 260 .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u, 261 .f32 => return .f32_gt, 262 .f64 => return .f64_gt, 263 }, 264 .le => switch (args.valtype1.?) { 265 .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u, 266 .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u, 267 .f32 => return .f32_le, 268 .f64 => return .f64_le, 269 }, 270 .ge => switch (args.valtype1.?) { 271 .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u, 272 .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u, 273 .f32 => return .f32_ge, 274 .f64 => return .f64_ge, 275 }, 276 277 .clz => switch (args.valtype1.?) { 278 .i32 => return .i32_clz, 279 .i64 => return .i64_clz, 280 .f32, .f64 => unreachable, 281 }, 282 .ctz => switch (args.valtype1.?) { 283 .i32 => return .i32_ctz, 284 .i64 => return .i64_ctz, 285 .f32, .f64 => unreachable, 286 }, 287 .popcnt => switch (args.valtype1.?) { 288 .i32 => return .i32_popcnt, 289 .i64 => return .i64_popcnt, 290 .f32, .f64 => unreachable, 291 }, 292 293 .add => switch (args.valtype1.?) { 294 .i32 => return .i32_add, 295 .i64 => return .i64_add, 296 .f32 => return .f32_add, 297 .f64 => return .f64_add, 298 }, 299 .sub => switch (args.valtype1.?) { 300 .i32 => return .i32_sub, 301 .i64 => return .i64_sub, 302 .f32 => return .f32_sub, 303 .f64 => return .f64_sub, 304 }, 305 .mul => switch (args.valtype1.?) { 306 .i32 => return .i32_mul, 307 .i64 => return .i64_mul, 308 .f32 => return .f32_mul, 309 .f64 => return .f64_mul, 310 }, 311 312 .div => switch (args.valtype1.?) { 313 .i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u, 314 .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u, 315 .f32 => return .f32_div, 316 .f64 => return .f64_div, 317 }, 318 .rem => switch (args.valtype1.?) { 319 .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u, 320 .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u, 321 .f32, .f64 => unreachable, 322 }, 323 324 .@"and" => switch (args.valtype1.?) { 325 .i32 => return .i32_and, 326 .i64 => return .i64_and, 327 .f32, .f64 => unreachable, 328 }, 329 .@"or" => switch (args.valtype1.?) { 330 .i32 => return .i32_or, 331 .i64 => return .i64_or, 332 .f32, .f64 => unreachable, 333 }, 334 .xor => switch (args.valtype1.?) { 335 .i32 => return .i32_xor, 336 .i64 => return .i64_xor, 337 .f32, .f64 => unreachable, 338 }, 339 340 .shl => switch (args.valtype1.?) { 341 .i32 => return .i32_shl, 342 .i64 => return .i64_shl, 343 .f32, .f64 => unreachable, 344 }, 345 .shr => switch (args.valtype1.?) { 346 .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u, 347 .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u, 348 .f32, .f64 => unreachable, 349 }, 350 .rotl => switch (args.valtype1.?) { 351 .i32 => return .i32_rotl, 352 .i64 => return .i64_rotl, 353 .f32, .f64 => unreachable, 354 }, 355 .rotr => switch (args.valtype1.?) { 356 .i32 => return .i32_rotr, 357 .i64 => return .i64_rotr, 358 .f32, .f64 => unreachable, 359 }, 360 361 .abs => switch (args.valtype1.?) { 362 .i32, .i64 => unreachable, 363 .f32 => return .f32_abs, 364 .f64 => return .f64_abs, 365 }, 366 .neg => switch (args.valtype1.?) { 367 .i32, .i64 => unreachable, 368 .f32 => return .f32_neg, 369 .f64 => return .f64_neg, 370 }, 371 .ceil => switch (args.valtype1.?) { 372 .i32, .i64 => unreachable, 373 .f32 => return .f32_ceil, 374 .f64 => return .f64_ceil, 375 }, 376 .floor => switch (args.valtype1.?) { 377 .i32, .i64 => unreachable, 378 .f32 => return .f32_floor, 379 .f64 => return .f64_floor, 380 }, 381 .trunc => switch (args.valtype1.?) { 382 .i32 => switch (args.valtype2.?) { 383 .i32 => unreachable, 384 .i64 => unreachable, 385 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u, 386 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u, 387 }, 388 .i64 => unreachable, 389 .f32 => return .f32_trunc, 390 .f64 => return .f64_trunc, 391 }, 392 .nearest => switch (args.valtype1.?) { 393 .i32, .i64 => unreachable, 394 .f32 => return .f32_nearest, 395 .f64 => return .f64_nearest, 396 }, 397 .sqrt => switch (args.valtype1.?) { 398 .i32, .i64 => unreachable, 399 .f32 => return .f32_sqrt, 400 .f64 => return .f64_sqrt, 401 }, 402 .min => switch (args.valtype1.?) { 403 .i32, .i64 => unreachable, 404 .f32 => return .f32_min, 405 .f64 => return .f64_min, 406 }, 407 .max => switch (args.valtype1.?) { 408 .i32, .i64 => unreachable, 409 .f32 => return .f32_max, 410 .f64 => return .f64_max, 411 }, 412 .copysign => switch (args.valtype1.?) { 413 .i32, .i64 => unreachable, 414 .f32 => return .f32_copysign, 415 .f64 => return .f64_copysign, 416 }, 417 418 .wrap => switch (args.valtype1.?) { 419 .i32 => switch (args.valtype2.?) { 420 .i32 => unreachable, 421 .i64 => return .i32_wrap_i64, 422 .f32, .f64 => unreachable, 423 }, 424 .i64, .f32, .f64 => unreachable, 425 }, 426 .convert => switch (args.valtype1.?) { 427 .i32, .i64 => unreachable, 428 .f32 => switch (args.valtype2.?) { 429 .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u, 430 .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u, 431 .f32, .f64 => unreachable, 432 }, 433 .f64 => switch (args.valtype2.?) { 434 .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u, 435 .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u, 436 .f32, .f64 => unreachable, 437 }, 438 }, 439 .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable, 440 .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable, 441 .reinterpret => switch (args.valtype1.?) { 442 .i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable, 443 .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable, 444 .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable, 445 .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable, 446 }, 447 .extend => switch (args.valtype1.?) { 448 .i32 => switch (args.width.?) { 449 8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable, 450 16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable, 451 else => unreachable, 452 }, 453 .i64 => switch (args.width.?) { 454 8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable, 455 16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable, 456 32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable, 457 else => unreachable, 458 }, 459 .f32, .f64 => unreachable, 460 }, 461 } 462 } 463 464 test "Wasm - buildOpcode" { 465 // Make sure buildOpcode is referenced, and test some examples 466 const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 }); 467 const end = buildOpcode(.{ .op = .end }); 468 const local_get = buildOpcode(.{ .op = .local_get }); 469 const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed }); 470 const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 }); 471 472 try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const); 473 try testing.expectEqual(@as(wasm.Opcode, .end), end); 474 try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get); 475 try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s); 476 try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64); 477 } 478 479 pub const Result = union(enum) { 480 /// The codegen bytes have been appended to `Context.code` 481 appended: void, 482 /// The data is managed externally and are part of the `Result` 483 externally_managed: []const u8, 484 }; 485 486 /// Hashmap to store generated `WValue` for each `Air.Inst.Ref` 487 pub const ValueTable = std.AutoHashMapUnmanaged(Air.Inst.Index, WValue); 488 489 /// Code represents the `Code` section of wasm that 490 /// belongs to a function 491 pub const Context = struct { 492 /// Reference to the function declaration the code 493 /// section belongs to 494 decl: *Decl, 495 air: Air, 496 liveness: Liveness, 497 gpa: *mem.Allocator, 498 /// Table to save `WValue`'s generated by an `Air.Inst` 499 values: ValueTable, 500 /// Mapping from Air.Inst.Index to block ids 501 blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, u32) = .{}, 502 /// `bytes` contains the wasm bytecode belonging to the 'code' section. 503 code: ArrayList(u8), 504 /// Contains the generated function type bytecode for the current function 505 /// found in `decl` 506 func_type_data: ArrayList(u8), 507 /// The index the next local generated will have 508 /// NOTE: arguments share the index with locals therefore the first variable 509 /// will have the index that comes after the last argument's index 510 local_index: u32 = 0, 511 /// If codegen fails, an error messages will be allocated and saved in `err_msg` 512 err_msg: *Module.ErrorMsg, 513 /// Current block depth. Used to calculate the relative difference between a break 514 /// and block 515 block_depth: u32 = 0, 516 /// List of all locals' types generated throughout this declaration 517 /// used to emit locals count at start of 'code' section. 518 locals: std.ArrayListUnmanaged(u8), 519 /// The Target we're emitting (used to call intInfo) 520 target: std.Target, 521 /// Table with the global error set. Consists of every error found in 522 /// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted 523 /// during codegen to determine the error value. 524 global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt), 525 526 const InnerError = error{ 527 OutOfMemory, 528 CodegenFail, 529 /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed 530 AnalysisFail, 531 }; 532 533 pub fn deinit(self: *Context) void { 534 self.values.deinit(self.gpa); 535 self.blocks.deinit(self.gpa); 536 self.locals.deinit(self.gpa); 537 self.* = undefined; 538 } 539 540 /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig 541 fn fail(self: *Context, comptime fmt: []const u8, args: anytype) InnerError { 542 const src: LazySrcLoc = .{ .node_offset = 0 }; 543 const src_loc = src.toSrcLocWithDecl(self.decl); 544 self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args); 545 return error.CodegenFail; 546 } 547 548 /// Resolves the `WValue` for the given instruction `inst` 549 /// When the given instruction has a `Value`, it returns a constant instead 550 fn resolveInst(self: Context, ref: Air.Inst.Ref) WValue { 551 const inst_index = Air.refToIndex(ref) orelse { 552 const tv = Air.Inst.Ref.typed_value_map[@enumToInt(ref)]; 553 if (!tv.ty.hasCodeGenBits()) { 554 return WValue.none; 555 } 556 return WValue{ .constant = tv }; 557 }; 558 559 const inst_type = self.air.typeOfIndex(inst_index); 560 if (!inst_type.hasCodeGenBits()) return .none; 561 562 if (self.air.instructions.items(.tag)[inst_index] == .constant) { 563 const ty_pl = self.air.instructions.items(.data)[inst_index].ty_pl; 564 return WValue{ .constant = .{ .ty = inst_type, .val = self.air.values[ty_pl.payload] } }; 565 } 566 567 return self.values.get(inst_index).?; // Instruction does not dominate all uses! 568 } 569 570 /// Using a given `Type`, returns the corresponding wasm Valtype 571 fn typeToValtype(self: *Context, ty: Type) InnerError!wasm.Valtype { 572 return switch (ty.zigTypeTag()) { 573 .Float => blk: { 574 const bits = ty.floatBits(self.target); 575 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32; 576 if (bits == 64) break :blk wasm.Valtype.f64; 577 return self.fail("Float bit size not supported by wasm: '{d}'", .{bits}); 578 }, 579 .Int => blk: { 580 const info = ty.intInfo(self.target); 581 if (info.bits <= 32) break :blk wasm.Valtype.i32; 582 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64; 583 return self.fail("Integer bit size not supported by wasm: '{d}'", .{info.bits}); 584 }, 585 .Enum => switch (ty.tag()) { 586 .enum_simple => wasm.Valtype.i32, 587 else => self.typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty), 588 }, 589 .Bool, 590 .Pointer, 591 .ErrorSet, 592 => wasm.Valtype.i32, 593 .Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually. 594 else => self.fail("TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}), 595 }; 596 } 597 598 /// Using a given `Type`, returns the byte representation of its wasm value type 599 fn genValtype(self: *Context, ty: Type) InnerError!u8 { 600 return wasm.valtype(try self.typeToValtype(ty)); 601 } 602 603 /// Using a given `Type`, returns the corresponding wasm value type 604 /// Differently from `genValtype` this also allows `void` to create a block 605 /// with no return type 606 fn genBlockType(self: *Context, ty: Type) InnerError!u8 { 607 return switch (ty.tag()) { 608 .void, .noreturn => wasm.block_empty, 609 else => self.genValtype(ty), 610 }; 611 } 612 613 /// Writes the bytecode depending on the given `WValue` in `val` 614 fn emitWValue(self: *Context, val: WValue) InnerError!void { 615 const writer = self.code.writer(); 616 switch (val) { 617 .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually 618 .none, .code_offset => {}, // no-op 619 .local => |idx| { 620 try writer.writeByte(wasm.opcode(.local_get)); 621 try leb.writeULEB128(writer, idx); 622 }, 623 .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack 624 } 625 } 626 627 /// Creates one or multiple locals for a given `Type`. 628 /// Returns a corresponding `Wvalue` that can either be of tag 629 /// local or multi_value 630 fn allocLocal(self: *Context, ty: Type) InnerError!WValue { 631 const initial_index = self.local_index; 632 switch (ty.zigTypeTag()) { 633 .Struct => { 634 // for each struct field, generate a local 635 const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data; 636 const fields_len = @intCast(u32, struct_data.fields.count()); 637 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + fields_len); 638 for (struct_data.fields.values()) |*value| { 639 const val_type = try self.genValtype(value.ty); 640 self.locals.appendAssumeCapacity(val_type); 641 self.local_index += 1; 642 } 643 return WValue{ .multi_value = .{ 644 .index = initial_index, 645 .count = fields_len, 646 } }; 647 }, 648 .ErrorUnion => { 649 const payload_type = ty.errorUnionPayload(); 650 const val_type = try self.genValtype(payload_type); 651 652 // we emit the error value as the first local, and the payload as the following. 653 // The first local is also used to find the index of the error and payload. 654 // 655 // TODO: Add support where the payload is a type that contains multiple locals such as a struct. 656 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + 2); 657 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32 658 self.locals.appendAssumeCapacity(val_type); 659 self.local_index += 2; 660 661 return WValue{ .multi_value = .{ 662 .index = initial_index, 663 .count = 2, 664 } }; 665 }, 666 else => { 667 const valtype = try self.genValtype(ty); 668 try self.locals.append(self.gpa, valtype); 669 self.local_index += 1; 670 return WValue{ .local = initial_index }; 671 }, 672 } 673 } 674 675 fn genFunctype(self: *Context) InnerError!void { 676 assert(self.decl.has_tv); 677 const ty = self.decl.ty; 678 const writer = self.func_type_data.writer(); 679 680 try writer.writeByte(wasm.function_type); 681 682 // param types 683 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); 684 if (ty.fnParamLen() != 0) { 685 const params = try self.gpa.alloc(Type, ty.fnParamLen()); 686 defer self.gpa.free(params); 687 ty.fnParamTypes(params); 688 for (params) |param_type| { 689 // Can we maybe get the source index of each param? 690 const val_type = try self.genValtype(param_type); 691 try writer.writeByte(val_type); 692 } 693 } 694 695 // return type 696 const return_type = ty.fnReturnType(); 697 switch (return_type.zigTypeTag()) { 698 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)), 699 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}), 700 .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}), 701 .ErrorUnion => { 702 const val_type = try self.genValtype(return_type.errorUnionPayload()); 703 704 // write down the amount of return values 705 try leb.writeULEB128(writer, @as(u32, 2)); 706 try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer. 707 try writer.writeByte(val_type); 708 }, 709 else => { 710 try leb.writeULEB128(writer, @as(u32, 1)); 711 // Can we maybe get the source index of the return type? 712 const val_type = try self.genValtype(return_type); 713 try writer.writeByte(val_type); 714 }, 715 } 716 } 717 718 pub fn genFunc(self: *Context) InnerError!Result { 719 try self.genFunctype(); 720 // TODO: check for and handle death of instructions 721 722 // Reserve space to write the size after generating the code as well as space for locals count 723 try self.code.resize(10); 724 725 try self.genBody(self.air.getMainBody()); 726 727 // finally, write our local types at the 'offset' position 728 { 729 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len)); 730 731 // offset into 'code' section where we will put our locals types 732 var local_offset: usize = 10; 733 734 // emit the actual locals amount 735 for (self.locals.items) |local| { 736 var buf: [6]u8 = undefined; 737 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1)); 738 buf[5] = local; 739 try self.code.insertSlice(local_offset, &buf); 740 local_offset += 6; 741 } 742 } 743 744 const writer = self.code.writer(); 745 try writer.writeByte(wasm.opcode(.end)); 746 747 // Fill in the size of the generated code to the reserved space at the 748 // beginning of the buffer. 749 const size = self.code.items.len - 5 + self.decl.fn_link.wasm.idx_refs.items.len * 5; 750 leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size)); 751 752 // codegen data has been appended to `code` 753 return Result.appended; 754 } 755 756 /// Generates the wasm bytecode for the declaration belonging to `Context` 757 pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result { 758 switch (typed_value.ty.zigTypeTag()) { 759 .Fn => { 760 try self.genFunctype(); 761 if (typed_value.val.castTag(.extern_fn)) |_| return Result.appended; // don't need code body for extern functions 762 return self.fail("TODO implement wasm codegen for function pointers", .{}); 763 }, 764 .Array => { 765 if (typed_value.val.castTag(.bytes)) |payload| { 766 if (typed_value.ty.sentinel()) |sentinel| { 767 try self.code.appendSlice(payload.data); 768 769 switch (try self.gen(.{ 770 .ty = typed_value.ty.elemType(), 771 .val = sentinel, 772 })) { 773 .appended => return Result.appended, 774 .externally_managed => |data| { 775 try self.code.appendSlice(data); 776 return Result.appended; 777 }, 778 } 779 } 780 return Result{ .externally_managed = payload.data }; 781 } else return self.fail("TODO implement gen for more kinds of arrays", .{}); 782 }, 783 .Int => { 784 const info = typed_value.ty.intInfo(self.target); 785 if (info.bits == 8 and info.signedness == .unsigned) { 786 const int_byte = typed_value.val.toUnsignedInt(); 787 try self.code.append(@intCast(u8, int_byte)); 788 return Result.appended; 789 } 790 return self.fail("TODO: Implement codegen for int type: '{}'", .{typed_value.ty}); 791 }, 792 else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}), 793 } 794 } 795 796 fn genInst(self: *Context, inst: Air.Inst.Index) !WValue { 797 const air_tags = self.air.instructions.items(.tag); 798 return switch (air_tags[inst]) { 799 .add => self.airBinOp(inst, .add), 800 .sub => self.airBinOp(inst, .sub), 801 .mul => self.airBinOp(inst, .mul), 802 .div => self.airBinOp(inst, .div), 803 .bit_and => self.airBinOp(inst, .@"and"), 804 .bit_or => self.airBinOp(inst, .@"or"), 805 .bool_and => self.airBinOp(inst, .@"and"), 806 .bool_or => self.airBinOp(inst, .@"or"), 807 .xor => self.airBinOp(inst, .xor), 808 809 .cmp_eq => self.airCmp(inst, .eq), 810 .cmp_gte => self.airCmp(inst, .gte), 811 .cmp_gt => self.airCmp(inst, .gt), 812 .cmp_lte => self.airCmp(inst, .lte), 813 .cmp_lt => self.airCmp(inst, .lt), 814 .cmp_neq => self.airCmp(inst, .neq), 815 816 .alloc => self.airAlloc(inst), 817 .arg => self.airArg(inst), 818 .bitcast => self.airBitcast(inst), 819 .block => self.airBlock(inst), 820 .breakpoint => self.airBreakpoint(inst), 821 .br => self.airBr(inst), 822 .call => self.airCall(inst), 823 .cond_br => self.airCondBr(inst), 824 .constant => unreachable, 825 .dbg_stmt => WValue.none, 826 .is_err => self.airIsErr(inst, .i32_ne), 827 .is_non_err => self.airIsErr(inst, .i32_eq), 828 .load => self.airLoad(inst), 829 .loop => self.airLoop(inst), 830 .not => self.airNot(inst), 831 .ret => self.airRet(inst), 832 .store => self.airStore(inst), 833 .struct_field_ptr => self.airStructFieldPtr(inst), 834 .switch_br => self.airSwitchBr(inst), 835 .unreach => self.airUnreachable(inst), 836 .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst), 837 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst), 838 else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), 839 }; 840 } 841 842 fn genBody(self: *Context, body: []const Air.Inst.Index) InnerError!void { 843 for (body) |inst| { 844 const result = try self.genInst(inst); 845 try self.values.putNoClobber(self.gpa, inst, result); 846 } 847 } 848 849 fn airRet(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 850 const un_op = self.air.instructions.items(.data)[inst].un_op; 851 const operand = self.resolveInst(un_op); 852 try self.emitWValue(operand); 853 try self.code.append(wasm.opcode(.@"return")); 854 return .none; 855 } 856 857 fn airCall(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 858 const pl_op = self.air.instructions.items(.data)[inst].pl_op; 859 const extra = self.air.extraData(Air.Call, pl_op.payload); 860 const args = self.air.extra[extra.end..][0..extra.data.args_len]; 861 862 const target: *Decl = blk: { 863 const func_val = self.air.value(pl_op.operand).?; 864 865 if (func_val.castTag(.function)) |func| { 866 break :blk func.data.owner_decl; 867 } else if (func_val.castTag(.extern_fn)) |ext_fn| { 868 break :blk ext_fn.data; 869 } 870 return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()}); 871 }; 872 873 for (args) |arg| { 874 const arg_val = self.resolveInst(@intToEnum(Air.Inst.Ref, arg)); 875 try self.emitWValue(arg_val); 876 } 877 878 try self.code.append(wasm.opcode(.call)); 879 880 // The function index immediate argument will be filled in using this data 881 // in link.Wasm.flush(). 882 try self.decl.fn_link.wasm.idx_refs.append(self.gpa, .{ 883 .offset = @intCast(u32, self.code.items.len), 884 .decl = target, 885 }); 886 887 return .none; 888 } 889 890 fn airAlloc(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 891 const elem_type = self.air.typeOfIndex(inst).elemType(); 892 return self.allocLocal(elem_type); 893 } 894 895 fn airStore(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 896 const bin_op = self.air.instructions.items(.data)[inst].bin_op; 897 const writer = self.code.writer(); 898 899 const lhs = self.resolveInst(bin_op.lhs); 900 const rhs = self.resolveInst(bin_op.rhs); 901 902 switch (lhs) { 903 .multi_value => |multi_value| switch (rhs) { 904 // When assigning a value to a multi_value such as a struct, 905 // we simply assign the local_index to the rhs one. 906 // This allows us to update struct fields without having to individually 907 // set each local as each field's index will be calculated off the struct's base index 908 .multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses! 909 .constant, .none => { 910 // emit all values onto the stack if constant 911 try self.emitWValue(rhs); 912 913 // for each local, pop the stack value into the local 914 // As the last element is on top of the stack, we must populate the locals 915 // in reverse. 916 var i: u32 = multi_value.count; 917 while (i > 0) : (i -= 1) { 918 try writer.writeByte(wasm.opcode(.local_set)); 919 try leb.writeULEB128(writer, multi_value.index + i - 1); 920 } 921 }, 922 else => unreachable, 923 }, 924 .local => |local| { 925 try self.emitWValue(rhs); 926 try writer.writeByte(wasm.opcode(.local_set)); 927 try leb.writeULEB128(writer, local); 928 }, 929 else => unreachable, 930 } 931 return .none; 932 } 933 934 fn airLoad(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 935 const ty_op = self.air.instructions.items(.data)[inst].ty_op; 936 return self.resolveInst(ty_op.operand); 937 } 938 939 fn airArg(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 940 _ = inst; 941 // arguments share the index with locals 942 defer self.local_index += 1; 943 return WValue{ .local = self.local_index }; 944 } 945 946 fn airBinOp(self: *Context, inst: Air.Inst.Index, op: Op) InnerError!WValue { 947 const bin_op = self.air.instructions.items(.data)[inst].bin_op; 948 const lhs = self.resolveInst(bin_op.lhs); 949 const rhs = self.resolveInst(bin_op.rhs); 950 951 // it's possible for both lhs and/or rhs to return an offset as well, 952 // in which case we return the first offset occurance we find. 953 const offset = blk: { 954 if (lhs == .code_offset) break :blk lhs.code_offset; 955 if (rhs == .code_offset) break :blk rhs.code_offset; 956 break :blk self.code.items.len; 957 }; 958 959 try self.emitWValue(lhs); 960 try self.emitWValue(rhs); 961 962 const bin_ty = self.air.typeOf(bin_op.lhs); 963 const opcode: wasm.Opcode = buildOpcode(.{ 964 .op = op, 965 .valtype1 = try self.typeToValtype(bin_ty), 966 .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned, 967 }); 968 try self.code.append(wasm.opcode(opcode)); 969 return WValue{ .code_offset = offset }; 970 } 971 972 fn emitConstant(self: *Context, value: Value, ty: Type) InnerError!void { 973 const writer = self.code.writer(); 974 switch (ty.zigTypeTag()) { 975 .Int => { 976 // write opcode 977 const opcode: wasm.Opcode = buildOpcode(.{ 978 .op = .@"const", 979 .valtype1 = try self.typeToValtype(ty), 980 }); 981 try writer.writeByte(wasm.opcode(opcode)); 982 // write constant 983 switch (ty.intInfo(self.target).signedness) { 984 .signed => try leb.writeILEB128(writer, value.toSignedInt()), 985 .unsigned => try leb.writeILEB128(writer, value.toUnsignedInt()), 986 } 987 }, 988 .Bool => { 989 // write opcode 990 try writer.writeByte(wasm.opcode(.i32_const)); 991 // write constant 992 try leb.writeILEB128(writer, value.toSignedInt()); 993 }, 994 .Float => { 995 // write opcode 996 const opcode: wasm.Opcode = buildOpcode(.{ 997 .op = .@"const", 998 .valtype1 = try self.typeToValtype(ty), 999 }); 1000 try writer.writeByte(wasm.opcode(opcode)); 1001 // write constant 1002 switch (ty.floatBits(self.target)) { 1003 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, value.toFloat(f32))), 1004 64 => try writer.writeIntLittle(u64, @bitCast(u64, value.toFloat(f64))), 1005 else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}), 1006 } 1007 }, 1008 .Pointer => { 1009 if (value.castTag(.decl_ref)) |payload| { 1010 const decl = payload.data; 1011 1012 // offset into the offset table within the 'data' section 1013 const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8; 1014 try writer.writeByte(wasm.opcode(.i32_const)); 1015 try leb.writeULEB128(writer, decl.link.wasm.offset_index * ptr_width); 1016 1017 // memory instruction followed by their memarg immediate 1018 // memarg ::== x:u32, y:u32 => {align x, offset y} 1019 try writer.writeByte(wasm.opcode(.i32_load)); 1020 try leb.writeULEB128(writer, @as(u32, 0)); 1021 try leb.writeULEB128(writer, @as(u32, 0)); 1022 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{value.tag()}); 1023 }, 1024 .Void => {}, 1025 .Enum => { 1026 if (value.castTag(.enum_field_index)) |field_index| { 1027 switch (ty.tag()) { 1028 .enum_simple => { 1029 try writer.writeByte(wasm.opcode(.i32_const)); 1030 try leb.writeULEB128(writer, field_index.data); 1031 }, 1032 .enum_full, .enum_nonexhaustive => { 1033 const enum_full = ty.cast(Type.Payload.EnumFull).?.data; 1034 if (enum_full.values.count() != 0) { 1035 const tag_val = enum_full.values.keys()[field_index.data]; 1036 try self.emitConstant(tag_val, enum_full.tag_ty); 1037 } else { 1038 try writer.writeByte(wasm.opcode(.i32_const)); 1039 try leb.writeULEB128(writer, field_index.data); 1040 } 1041 }, 1042 else => unreachable, 1043 } 1044 } else { 1045 var int_tag_buffer: Type.Payload.Bits = undefined; 1046 const int_tag_ty = ty.intTagType(&int_tag_buffer); 1047 try self.emitConstant(value, int_tag_ty); 1048 } 1049 }, 1050 .ErrorSet => { 1051 const error_index = self.global_error_set.get(value.getError().?).?; 1052 try writer.writeByte(wasm.opcode(.i32_const)); 1053 try leb.writeULEB128(writer, error_index); 1054 }, 1055 .ErrorUnion => { 1056 const data = value.castTag(.error_union).?.data; 1057 const error_type = ty.errorUnionSet(); 1058 const payload_type = ty.errorUnionPayload(); 1059 if (value.getError()) |_| { 1060 // write the error value 1061 try self.emitConstant(data, error_type); 1062 1063 // no payload, so write a '0' const 1064 const opcode: wasm.Opcode = buildOpcode(.{ 1065 .op = .@"const", 1066 .valtype1 = try self.typeToValtype(payload_type), 1067 }); 1068 try writer.writeByte(wasm.opcode(opcode)); 1069 try leb.writeULEB128(writer, @as(u32, 0)); 1070 } else { 1071 // no error, so write a '0' const 1072 try writer.writeByte(wasm.opcode(.i32_const)); 1073 try leb.writeULEB128(writer, @as(u32, 0)); 1074 // after the error code, we emit the payload 1075 try self.emitConstant(data, payload_type); 1076 } 1077 }, 1078 else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), 1079 } 1080 } 1081 1082 fn airBlock(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1083 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; 1084 const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty)); 1085 const extra = self.air.extraData(Air.Block, ty_pl.payload); 1086 const body = self.air.extra[extra.end..][0..extra.data.body_len]; 1087 1088 try self.startBlock(.block, block_ty, null); 1089 // Here we set the current block idx, so breaks know the depth to jump 1090 // to when breaking out. 1091 try self.blocks.putNoClobber(self.gpa, inst, self.block_depth); 1092 try self.genBody(body); 1093 try self.endBlock(); 1094 1095 return .none; 1096 } 1097 1098 /// appends a new wasm block to the code section and increases the `block_depth` by 1 1099 fn startBlock(self: *Context, block_type: wasm.Opcode, valtype: u8, with_offset: ?usize) !void { 1100 self.block_depth += 1; 1101 if (with_offset) |offset| { 1102 try self.code.insert(offset, wasm.opcode(block_type)); 1103 try self.code.insert(offset + 1, valtype); 1104 } else { 1105 try self.code.append(wasm.opcode(block_type)); 1106 try self.code.append(valtype); 1107 } 1108 } 1109 1110 /// Ends the current wasm block and decreases the `block_depth` by 1 1111 fn endBlock(self: *Context) !void { 1112 try self.code.append(wasm.opcode(.end)); 1113 self.block_depth -= 1; 1114 } 1115 1116 fn airLoop(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1117 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; 1118 const loop = self.air.extraData(Air.Block, ty_pl.payload); 1119 const body = self.air.extra[loop.end..][0..loop.data.body_len]; 1120 1121 // result type of loop is always 'noreturn', meaning we can always 1122 // emit the wasm type 'block_empty'. 1123 try self.startBlock(.loop, wasm.block_empty, null); 1124 try self.genBody(body); 1125 1126 // breaking to the index of a loop block will continue the loop instead 1127 try self.code.append(wasm.opcode(.br)); 1128 try leb.writeULEB128(self.code.writer(), @as(u32, 0)); 1129 1130 try self.endBlock(); 1131 1132 return .none; 1133 } 1134 1135 fn airCondBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1136 const pl_op = self.air.instructions.items(.data)[inst].pl_op; 1137 const condition = self.resolveInst(pl_op.operand); 1138 const extra = self.air.extraData(Air.CondBr, pl_op.payload); 1139 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len]; 1140 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; 1141 const writer = self.code.writer(); 1142 1143 // TODO: Handle death instructions for then and else body 1144 1145 // insert blocks at the position of `offset` so 1146 // the condition can jump to it 1147 const offset = switch (condition) { 1148 .code_offset => |offset| offset, 1149 else => blk: { 1150 const offset = self.code.items.len; 1151 try self.emitWValue(condition); 1152 break :blk offset; 1153 }, 1154 }; 1155 1156 // result type is always noreturn, so use `block_empty` as type. 1157 try self.startBlock(.block, wasm.block_empty, offset); 1158 1159 // we inserted the block in front of the condition 1160 // so now check if condition matches. If not, break outside this block 1161 // and continue with the then codepath 1162 try writer.writeByte(wasm.opcode(.br_if)); 1163 try leb.writeULEB128(writer, @as(u32, 0)); 1164 1165 try self.genBody(else_body); 1166 try self.endBlock(); 1167 1168 // Outer block that matches the condition 1169 try self.genBody(then_body); 1170 1171 return .none; 1172 } 1173 1174 fn airCmp(self: *Context, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue { 1175 // save offset, so potential conditions can insert blocks in front of 1176 // the comparison that we can later jump back to 1177 const offset = self.code.items.len; 1178 1179 const data: Air.Inst.Data = self.air.instructions.items(.data)[inst]; 1180 const lhs = self.resolveInst(data.bin_op.lhs); 1181 const rhs = self.resolveInst(data.bin_op.rhs); 1182 const lhs_ty = self.air.typeOf(data.bin_op.lhs); 1183 1184 try self.emitWValue(lhs); 1185 try self.emitWValue(rhs); 1186 1187 const signedness: std.builtin.Signedness = blk: { 1188 // by default we tell the operand type is unsigned (i.e. bools and enum values) 1189 if (lhs_ty.zigTypeTag() != .Int) break :blk .unsigned; 1190 1191 // incase of an actual integer, we emit the correct signedness 1192 break :blk lhs_ty.intInfo(self.target).signedness; 1193 }; 1194 const opcode: wasm.Opcode = buildOpcode(.{ 1195 .valtype1 = try self.typeToValtype(lhs_ty), 1196 .op = switch (op) { 1197 .lt => .lt, 1198 .lte => .le, 1199 .eq => .eq, 1200 .neq => .ne, 1201 .gte => .ge, 1202 .gt => .gt, 1203 }, 1204 .signedness = signedness, 1205 }); 1206 try self.code.append(wasm.opcode(opcode)); 1207 return WValue{ .code_offset = offset }; 1208 } 1209 1210 fn airBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1211 const br = self.air.instructions.items(.data)[inst].br; 1212 1213 // if operand has codegen bits we should break with a value 1214 if (self.air.typeOf(br.operand).hasCodeGenBits()) { 1215 try self.emitWValue(self.resolveInst(br.operand)); 1216 } 1217 1218 // We map every block to its block index. 1219 // We then determine how far we have to jump to it by substracting it from current block depth 1220 const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?; 1221 const writer = self.code.writer(); 1222 try writer.writeByte(wasm.opcode(.br)); 1223 try leb.writeULEB128(writer, idx); 1224 1225 return .none; 1226 } 1227 1228 fn airNot(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1229 const ty_op = self.air.instructions.items(.data)[inst].ty_op; 1230 const offset = self.code.items.len; 1231 1232 const operand = self.resolveInst(ty_op.operand); 1233 try self.emitWValue(operand); 1234 1235 // wasm does not have booleans nor the `not` instruction, therefore compare with 0 1236 // to create the same logic 1237 const writer = self.code.writer(); 1238 try writer.writeByte(wasm.opcode(.i32_const)); 1239 try leb.writeILEB128(writer, @as(i32, 0)); 1240 1241 try writer.writeByte(wasm.opcode(.i32_eq)); 1242 1243 return WValue{ .code_offset = offset }; 1244 } 1245 1246 fn airBreakpoint(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1247 _ = self; 1248 _ = inst; 1249 // unsupported by wasm itself. Can be implemented once we support DWARF 1250 // for wasm 1251 return .none; 1252 } 1253 1254 fn airUnreachable(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1255 _ = inst; 1256 try self.code.append(wasm.opcode(.@"unreachable")); 1257 return .none; 1258 } 1259 1260 fn airBitcast(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1261 const ty_op = self.air.instructions.items(.data)[inst].ty_op; 1262 return self.resolveInst(ty_op.operand); 1263 } 1264 1265 fn airStructFieldPtr(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1266 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; 1267 const extra = self.air.extraData(Air.StructField, ty_pl.payload); 1268 const struct_ptr = self.resolveInst(extra.data.struct_ptr); 1269 1270 return WValue{ .local = struct_ptr.multi_value.index + @intCast(u32, extra.data.field_index) }; 1271 } 1272 1273 fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1274 // result type is always 'noreturn' 1275 const blocktype = wasm.block_empty; 1276 const pl_op = self.air.instructions.items(.data)[inst].pl_op; 1277 const target = self.resolveInst(pl_op.operand); 1278 const target_ty = self.air.typeOf(pl_op.operand); 1279 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload); 1280 var extra_index: usize = switch_br.end; 1281 var case_i: u32 = 0; 1282 1283 // a map that maps each value with its index and body 1284 var map = std.AutoArrayHashMap(i32, struct { 1285 index: u32, 1286 body: []const Air.Inst.Index, 1287 value: Value, 1288 }).init(self.gpa); 1289 defer map.deinit(); 1290 1291 var lowest: i32 = 0; 1292 var highest: i32 = 0; 1293 while (case_i < switch_br.data.cases_len) : (case_i += 1) { 1294 const case = self.air.extraData(Air.SwitchBr.Case, extra_index); 1295 const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]); 1296 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len]; 1297 extra_index = case.end + items.len + case_body.len; 1298 1299 for (items) |ref| { 1300 const item_val = self.air.value(ref).?; 1301 const int_val: i32 = blk: { 1302 if (target_ty.intInfo(self.target).signedness == .signed) { 1303 // safe to truncate the values as we only use them when 1304 // the target's bits is 32 or lower. 1305 break :blk @truncate(i32, item_val.toSignedInt()); 1306 } 1307 1308 break :blk @intCast(i32, @truncate(u31, item_val.toUnsignedInt()) - 1); 1309 }; 1310 if (int_val < lowest) { 1311 lowest = int_val; 1312 } 1313 if (int_val > highest) { 1314 highest = int_val; 1315 } 1316 try map.put(int_val, .{ .index = case_i, .body = case_body, .value = item_val }); 1317 } 1318 1319 try self.startBlock(.block, blocktype, null); 1320 } 1321 1322 // When the highest and lowest values are seperated by '50', 1323 // we define it as sparse and use an if/else-chain, rather than a jump table. 1324 // When the target is an integer size larger than u32, we have no way to use the value 1325 // as an index, therefore we also use an if/else-chain for those cases. 1326 // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'. 1327 const is_sparse = target_ty.intInfo(self.target).bits > 32 or highest - lowest > 50; 1328 1329 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len]; 1330 const has_else_body = else_body.len != 0; 1331 if (has_else_body) { 1332 try self.startBlock(.block, blocktype, null); 1333 } 1334 1335 if (!is_sparse) { 1336 // Generate the jump table 'br_table' when the prongs are not sparse. 1337 // The value 'target' represents the index into the table. 1338 // Each index in the table represents a label to the branch 1339 // to jump to. 1340 try self.startBlock(.block, blocktype, null); 1341 try self.emitWValue(target); 1342 if (lowest < 0) { 1343 // since br_table works using indexes, starting from '0', we must ensure all values 1344 // we put inside, are atleast 0. 1345 try self.code.append(wasm.opcode(.i32_const)); 1346 try leb.writeILEB128(self.code.writer(), lowest * -1); 1347 try self.code.append(wasm.opcode(.i32_add)); 1348 } 1349 try self.code.append(wasm.opcode(.br_table)); 1350 const depth = highest - lowest + @boolToInt(has_else_body); 1351 try leb.writeILEB128(self.code.writer(), depth); 1352 while (lowest <= highest) : (lowest += 1) { 1353 const idx = if (map.get(lowest)) |value| blk: { 1354 break :blk value.index; 1355 } else if (has_else_body) case_i else unreachable; 1356 try leb.writeULEB128(self.code.writer(), idx); 1357 } else if (has_else_body) { 1358 try leb.writeULEB128(self.code.writer(), @as(u32, case_i)); // default branch 1359 } 1360 try self.endBlock(); 1361 } 1362 1363 const signedness: std.builtin.Signedness = blk: { 1364 // by default we tell the operand type is unsigned (i.e. bools and enum values) 1365 if (target_ty.zigTypeTag() != .Int) break :blk .unsigned; 1366 1367 // incase of an actual integer, we emit the correct signedness 1368 break :blk target_ty.intInfo(self.target).signedness; 1369 }; 1370 1371 for (map.values()) |val| { 1372 // when sparse, we use if/else-chain, so emit conditional checks 1373 if (is_sparse) { 1374 try self.emitWValue(target); 1375 try self.emitConstant(val.value, target_ty); 1376 const opcode = buildOpcode(.{ 1377 .valtype1 = try self.typeToValtype(target_ty), 1378 .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition. 1379 .signedness = signedness, 1380 }); 1381 try self.code.append(wasm.opcode(opcode)); 1382 try self.code.append(wasm.opcode(.br_if)); 1383 try leb.writeULEB128(self.code.writer(), @as(u32, 0)); 1384 } 1385 try self.genBody(val.body); 1386 try self.endBlock(); 1387 } 1388 1389 if (has_else_body) { 1390 try self.genBody(else_body); 1391 try self.endBlock(); 1392 } 1393 1394 return .none; 1395 } 1396 1397 fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue { 1398 const un_op = self.air.instructions.items(.data)[inst].un_op; 1399 const operand = self.resolveInst(un_op); 1400 const offset = self.code.items.len; 1401 const writer = self.code.writer(); 1402 1403 // load the error value which is positioned at multi_value's index 1404 try self.emitWValue(.{ .local = operand.multi_value.index }); 1405 // Compare the error value with '0' 1406 try writer.writeByte(wasm.opcode(.i32_const)); 1407 try leb.writeILEB128(writer, @as(i32, 0)); 1408 1409 try writer.writeByte(@enumToInt(opcode)); 1410 1411 return WValue{ .code_offset = offset }; 1412 } 1413 1414 fn airUnwrapErrUnionPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1415 const ty_op = self.air.instructions.items(.data)[inst].ty_op; 1416 const operand = self.resolveInst(ty_op.operand); 1417 // The index of multi_value contains the error code. To get the initial index of the payload we get 1418 // the following index. Next, convert it to a `WValue.local` 1419 // 1420 // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct. 1421 return WValue{ .local = operand.multi_value.index + 1 }; 1422 } 1423 1424 fn airWrapErrUnionPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue { 1425 const ty_op = self.air.instructions.items(.data)[inst].ty_op; 1426 return self.resolveInst(ty_op.operand); 1427 } 1428 };